1package eu.siacs.conversations.services;
2
3import android.content.Context;
4import android.content.SharedPreferences;
5import android.net.Uri;
6import android.os.ParcelFileDescriptor;
7import android.preference.PreferenceManager;
8import android.util.Log;
9
10import net.ypresto.androidtranscoder.MediaTranscoder;
11import net.ypresto.androidtranscoder.format.MediaFormatStrategy;
12
13import java.io.File;
14import java.io.FileDescriptor;
15import java.io.FileNotFoundException;
16import java.util.concurrent.ExecutionException;
17import java.util.concurrent.Future;
18
19import eu.siacs.conversations.Config;
20import eu.siacs.conversations.R;
21import eu.siacs.conversations.crypto.PgpEngine;
22import eu.siacs.conversations.entities.DownloadableFile;
23import eu.siacs.conversations.entities.Message;
24import eu.siacs.conversations.persistance.FileBackend;
25import eu.siacs.conversations.ui.UiCallback;
26import eu.siacs.conversations.utils.Android360pFormatStrategy;
27import eu.siacs.conversations.utils.Android720pFormatStrategy;
28import eu.siacs.conversations.utils.MimeUtils;
29
30public class AttachFileToConversationRunnable implements Runnable, MediaTranscoder.Listener {
31
32 private final XmppConnectionService mXmppConnectionService;
33 private final Message message;
34 private final Uri uri;
35 private final String type;
36 private final UiCallback<Message> callback;
37 private final boolean isVideoMessage;
38 private final long originalFileSize;
39 private int currentProgress = -1;
40
41 AttachFileToConversationRunnable(XmppConnectionService xmppConnectionService, Uri uri, String type, Message message, UiCallback<Message> callback) {
42 this.uri = uri;
43 this.type = type;
44 this.mXmppConnectionService = xmppConnectionService;
45 this.message = message;
46 this.callback = callback;
47 final String mimeType = MimeUtils.guessMimeTypeFromUriAndMime(mXmppConnectionService, uri, type);
48 final int autoAcceptFileSize = mXmppConnectionService.getResources().getInteger(R.integer.auto_accept_filesize);
49 this.originalFileSize = FileBackend.getFileSize(mXmppConnectionService,uri);
50 this.isVideoMessage = (mimeType != null && mimeType.startsWith("video/")) && originalFileSize > autoAcceptFileSize && !"uncompressed".equals(getVideoCompression());
51 }
52
53 boolean isVideoMessage() {
54 return this.isVideoMessage;
55 }
56
57 private void processAsFile() {
58 final String path = mXmppConnectionService.getFileBackend().getOriginalPath(uri);
59 if (path != null && !FileBackend.isPathBlacklisted(path)) {
60 message.setRelativeFilePath(path);
61 mXmppConnectionService.getFileBackend().updateFileParams(message);
62 if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
63 mXmppConnectionService.getPgpEngine().encrypt(message, callback);
64 } else {
65 mXmppConnectionService.sendMessage(message);
66 callback.success(message);
67 }
68 } else {
69 try {
70 mXmppConnectionService.getFileBackend().copyFileToPrivateStorage(message, uri, type);
71 mXmppConnectionService.getFileBackend().updateFileParams(message);
72 if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
73 final PgpEngine pgpEngine = mXmppConnectionService.getPgpEngine();
74 if (pgpEngine != null) {
75 pgpEngine.encrypt(message, callback);
76 } else if (callback != null) {
77 callback.error(R.string.unable_to_connect_to_keychain, null);
78 }
79 } else {
80 mXmppConnectionService.sendMessage(message);
81 callback.success(message);
82 }
83 } catch (FileBackend.FileCopyException e) {
84 callback.error(e.getResId(), message);
85 }
86 }
87 }
88
89 private void processAsVideo() throws FileNotFoundException {
90 Log.d(Config.LOGTAG,"processing file as video");
91 mXmppConnectionService.startForcingForegroundNotification();
92 message.setRelativeFilePath(message.getUuid() + ".mp4");
93 final DownloadableFile file = mXmppConnectionService.getFileBackend().getFile(message);
94 final MediaFormatStrategy formatStrategy = "720".equals(getVideoCompression()) ? new Android720pFormatStrategy() : new Android360pFormatStrategy();
95 file.getParentFile().mkdirs();
96 final ParcelFileDescriptor parcelFileDescriptor = mXmppConnectionService.getContentResolver().openFileDescriptor(uri, "r");
97 if (parcelFileDescriptor == null) {
98 throw new FileNotFoundException("Parcel File Descriptor was null");
99 }
100 FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
101 Future<Void> future = MediaTranscoder.getInstance().transcodeVideo(fileDescriptor, file.getAbsolutePath(), formatStrategy, this);
102 try {
103 future.get();
104 } catch (InterruptedException e) {
105 throw new AssertionError(e);
106 } catch (ExecutionException e) {
107 if (e.getCause() instanceof Error) {
108 mXmppConnectionService.stopForcingForegroundNotification();
109 processAsFile();
110 } else {
111 Log.d(Config.LOGTAG, "ignoring execution exception. Should get handled by onTranscodeFiled() instead", e);
112 }
113 }
114 }
115
116 @Override
117 public void onTranscodeProgress(double progress) {
118 final int p = (int) Math.round(progress * 100);
119 if (p > currentProgress) {
120 currentProgress = p;
121 mXmppConnectionService.getNotificationService().updateFileAddingNotification(p,message);
122 }
123 }
124
125 @Override
126 public void onTranscodeCompleted() {
127 mXmppConnectionService.stopForcingForegroundNotification();
128 final File file = mXmppConnectionService.getFileBackend().getFile(message);
129 long convertedFileSize = mXmppConnectionService.getFileBackend().getFile(message).getSize();
130 Log.d(Config.LOGTAG,"originalFileSize="+originalFileSize+" convertedFileSize="+convertedFileSize);
131 if (originalFileSize != 0 && convertedFileSize >= originalFileSize) {
132 if (file.delete()) {
133 Log.d(Config.LOGTAG,"original file size was smaller. deleting and processing as file");
134 processAsFile();
135 return;
136 } else {
137 Log.d(Config.LOGTAG,"unable to delete converted file");
138 }
139 }
140 mXmppConnectionService.getFileBackend().updateFileParams(message);
141 if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
142 mXmppConnectionService.getPgpEngine().encrypt(message, callback);
143 } else {
144 mXmppConnectionService.sendMessage(message);
145 callback.success(message);
146 }
147 }
148
149 @Override
150 public void onTranscodeCanceled() {
151 mXmppConnectionService.stopForcingForegroundNotification();
152 processAsFile();
153 }
154
155 @Override
156 public void onTranscodeFailed(Exception e) {
157 mXmppConnectionService.stopForcingForegroundNotification();
158 Log.d(Config.LOGTAG,"video transcoding failed",e);
159 processAsFile();
160 }
161
162 @Override
163 public void run() {
164 if (this.isVideoMessage()) {
165 try {
166 processAsVideo();
167 } catch (FileNotFoundException e) {
168 processAsFile();
169 }
170 } else {
171 processAsFile();
172 }
173 }
174
175 private String getVideoCompression() {
176 return getVideoCompression(mXmppConnectionService);
177 }
178
179 public static String getVideoCompression(final Context context) {
180 final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
181 return preferences.getString("video_compression", context.getResources().getString(R.string.video_compression));
182 }
183}