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