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