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.Android480pFormatStrategy;
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 public 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 = type != null ? type : MimeUtils.guessMimeTypeFromUri(mXmppConnectionService, uri);
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;
51 }
52
53 public 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 int runtime = mXmppConnectionService.getFileBackend().getMediaRuntime(uri);
95 final MediaFormatStrategy formatStrategy = runtime >= 20000 ? new Android360pFormatStrategy() : new Android480pFormatStrategy();
96 file.getParentFile().mkdirs();
97 final ParcelFileDescriptor parcelFileDescriptor = mXmppConnectionService.getContentResolver().openFileDescriptor(uri, "r");
98 if (parcelFileDescriptor == null) {
99 throw new FileNotFoundException("Parcel File Descriptor was null");
100 }
101 FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
102 Future<Void> future = MediaTranscoder.getInstance().transcodeVideo(fileDescriptor, file.getAbsolutePath(), formatStrategy, this);
103 try {
104 future.get();
105 } catch (InterruptedException e) {
106 throw new AssertionError(e);
107 } catch (ExecutionException e) {
108 Log.d(Config.LOGTAG,"ignoring execution exception. Should get handled by onTranscodeFiled() instead",e);
109 }
110 }
111
112 @Override
113 public void onTranscodeProgress(double progress) {
114 final int p = (int) Math.round(progress * 100);
115 if (p > currentProgress) {
116 currentProgress = p;
117 mXmppConnectionService.getNotificationService().updateFileAddingNotification(p,message);
118 }
119 }
120
121 @Override
122 public void onTranscodeCompleted() {
123 mXmppConnectionService.stopForcingForegroundNotification();
124 final File file = mXmppConnectionService.getFileBackend().getFile(message);
125 long convertedFileSize = mXmppConnectionService.getFileBackend().getFile(message).getSize();
126 Log.d(Config.LOGTAG,"originalFileSize="+originalFileSize+" convertedFileSize="+convertedFileSize);
127 if (originalFileSize != 0 && convertedFileSize >= originalFileSize) {
128 if (file.delete()) {
129 Log.d(Config.LOGTAG,"original file size was smaller. deleting and processing as file");
130 processAsFile();
131 return;
132 } else {
133 Log.d(Config.LOGTAG,"unable to delete converted file");
134 }
135 }
136 mXmppConnectionService.getFileBackend().updateFileParams(message);
137 if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
138 mXmppConnectionService.getPgpEngine().encrypt(message, callback);
139 } else {
140 mXmppConnectionService.sendMessage(message);
141 callback.success(message);
142 }
143 }
144
145 @Override
146 public void onTranscodeCanceled() {
147 mXmppConnectionService.stopForcingForegroundNotification();
148 processAsFile();
149 }
150
151 @Override
152 public void onTranscodeFailed(Exception e) {
153 mXmppConnectionService.stopForcingForegroundNotification();
154 Log.d(Config.LOGTAG,"video transcoding failed",e);
155 processAsFile();
156 }
157
158 @Override
159 public void run() {
160 if (isVideoMessage) {
161 try {
162 processAsVideo();
163 } catch (FileNotFoundException e) {
164 processAsFile();
165 }
166 } else {
167 processAsFile();
168 }
169 }
170
171}