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