AttachFileToConversationRunnable.java

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