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