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.FileDescriptor;
13import java.io.FileNotFoundException;
14import java.util.concurrent.ExecutionException;
15import java.util.concurrent.Future;
16import java.util.concurrent.atomic.AtomicInteger;
17
18import eu.siacs.conversations.Config;
19import eu.siacs.conversations.R;
20import eu.siacs.conversations.crypto.PgpEngine;
21import eu.siacs.conversations.entities.DownloadableFile;
22import eu.siacs.conversations.entities.Message;
23import eu.siacs.conversations.persistance.FileBackend;
24import eu.siacs.conversations.ui.UiCallback;
25import eu.siacs.conversations.utils.MimeUtils;
26
27public class AttachFileToConversationRunnable implements Runnable, MediaTranscoder.Listener {
28
29 private final XmppConnectionService mXmppConnectionService;
30 private final Message message;
31 private final Uri uri;
32 private final UiCallback<Message> callback;
33 private final boolean isVideoMessage;
34 private int currentProgress = -1;
35
36 public AttachFileToConversationRunnable(XmppConnectionService xmppConnectionService, Uri uri, Message message, UiCallback<Message> callback) {
37 this.uri = uri;
38 this.mXmppConnectionService = xmppConnectionService;
39 this.message = message;
40 this.callback = callback;
41 final String mimeType = MimeUtils.guessMimeTypeFromUri(mXmppConnectionService, uri);
42 this.isVideoMessage = (mimeType != null && mimeType.startsWith("video/") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2);
43 }
44
45 public boolean isVideoMessage() {
46 return this.isVideoMessage;
47 }
48
49 private void processAsFile() {
50 final String path = mXmppConnectionService.getFileBackend().getOriginalPath(uri);
51 if (path != null) {
52 message.setRelativeFilePath(path);
53 mXmppConnectionService.getFileBackend().updateFileParams(message);
54 if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
55 mXmppConnectionService.getPgpEngine().encrypt(message, callback);
56 } else {
57 callback.success(message);
58 }
59 } else {
60 try {
61 mXmppConnectionService.getFileBackend().copyFileToPrivateStorage(message, uri);
62 mXmppConnectionService.getFileBackend().updateFileParams(message);
63 if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
64 final PgpEngine pgpEngine = mXmppConnectionService.getPgpEngine();
65 if (pgpEngine != null) {
66 pgpEngine.encrypt(message, callback);
67 } else if (callback != null) {
68 callback.error(R.string.unable_to_connect_to_keychain, null);
69 }
70 } else {
71 callback.success(message);
72 }
73 } catch (FileBackend.FileCopyException e) {
74 callback.error(e.getResId(), message);
75 }
76 }
77 }
78
79 private void processAsVideo() throws FileNotFoundException {
80 Log.d(Config.LOGTAG,"processing file as video");
81 mXmppConnectionService.startForcingForegroundNotification();
82 message.setRelativeFilePath(message.getUuid() + ".mp4");
83 final DownloadableFile file = mXmppConnectionService.getFileBackend().getFile(message);
84 final int runtime = mXmppConnectionService.getFileBackend().getMediaRuntime(uri);
85 MediaFormatStrategy formatStrategy = runtime >= 8000 ? MediaFormatStrategyPresets.createExportPreset960x540Strategy() : MediaFormatStrategyPresets.createAndroid720pStrategy();
86 file.getParentFile().mkdirs();
87 final ParcelFileDescriptor parcelFileDescriptor = mXmppConnectionService.getContentResolver().openFileDescriptor(uri, "r");
88 if (parcelFileDescriptor == null) {
89 throw new FileNotFoundException("Parcel File Descriptor was null");
90 }
91 FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
92 Future<Void> future = MediaTranscoder.getInstance().transcodeVideo(fileDescriptor, file.getAbsolutePath(), formatStrategy, this);
93 try {
94 future.get();
95 } catch (InterruptedException e) {
96 throw new AssertionError(e);
97 } catch (ExecutionException e) {
98 Log.d(Config.LOGTAG,"ignoring execution exception. Should get handled by onTranscodeFiled() instead",e);
99 }
100 }
101
102 @Override
103 public void onTranscodeProgress(double progress) {
104 final int p = (int) Math.round(progress * 100);
105 if (p > currentProgress) {
106 currentProgress = p;
107 mXmppConnectionService.getNotificationService().updateFileAddingNotification(p,message);
108 }
109 }
110
111 @Override
112 public void onTranscodeCompleted() {
113 mXmppConnectionService.stopForcingForegroundNotification();
114 mXmppConnectionService.getFileBackend().updateFileParams(message);
115 if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
116 mXmppConnectionService.getPgpEngine().encrypt(message, callback);
117 } else {
118 callback.success(message);
119 }
120 }
121
122 @Override
123 public void onTranscodeCanceled() {
124 mXmppConnectionService.stopForcingForegroundNotification();
125 processAsFile();
126 }
127
128 @Override
129 public void onTranscodeFailed(Exception e) {
130 mXmppConnectionService.stopForcingForegroundNotification();
131 Log.d(Config.LOGTAG,"video transcoding failed",e);
132 processAsFile();
133 }
134
135 @Override
136 public void run() {
137 if (isVideoMessage) {
138 try {
139 processAsVideo();
140 } catch (FileNotFoundException e) {
141 processAsFile();
142 }
143 } else {
144 processAsFile();
145 }
146 }
147
148}