show permanent notification while transcoding video

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/services/AttachFileToConversationRunnable.java | 134 
src/main/java/eu/siacs/conversations/services/NotificationService.java              |  12 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java            | 123 
src/main/java/eu/siacs/conversations/utils/SerialSingleThreadExecutor.java          |   6 
src/main/res/drawable-hdpi/ic_hourglass_empty_white_24dp.png                        |   0 
src/main/res/drawable-mdpi/ic_hourglass_empty_white_24dp.png                        |   0 
src/main/res/drawable-xhdpi/ic_hourglass_empty_white_24dp.png                       |   0 
src/main/res/drawable-xxhdpi/ic_hourglass_empty_white_24dp.png                      |   0 
src/main/res/drawable-xxxhdpi/ic_hourglass_empty_white_24dp.png                     |   0 
src/main/res/values/strings.xml                                                     |   2 
10 files changed, 170 insertions(+), 107 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/services/AttachFileToConversationRunnable.java 🔗

@@ -0,0 +1,134 @@
+package eu.siacs.conversations.services;
+
+import android.net.Uri;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import net.ypresto.androidtranscoder.MediaTranscoder;
+import net.ypresto.androidtranscoder.format.MediaFormatStrategy;
+import net.ypresto.androidtranscoder.format.MediaFormatStrategyPresets;
+
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.crypto.PgpEngine;
+import eu.siacs.conversations.entities.DownloadableFile;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.persistance.FileBackend;
+import eu.siacs.conversations.ui.UiCallback;
+import eu.siacs.conversations.utils.MimeUtils;
+
+public class AttachFileToConversationRunnable implements Runnable, MediaTranscoder.Listener {
+
+	private final XmppConnectionService mXmppConnectionService;
+	private final Message message;
+	private final Uri uri;
+	private final UiCallback<Message> callback;
+	private final boolean isVideoMessage;
+	private int currentProgress = -1;
+
+	public AttachFileToConversationRunnable(XmppConnectionService xmppConnectionService, Uri uri, Message message, UiCallback<Message> callback) {
+		this.uri = uri;
+		this.mXmppConnectionService = xmppConnectionService;
+		this.message = message;
+		this.callback = callback;
+		final String mimeType = MimeUtils.guessMimeTypeFromUri(mXmppConnectionService, uri);
+		this.isVideoMessage = (mimeType != null && mimeType.startsWith("video/") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2);
+	}
+
+
+	private void processAsFile() {
+		final String path = mXmppConnectionService.getFileBackend().getOriginalPath(uri);
+		if (path != null) {
+			message.setRelativeFilePath(path);
+			mXmppConnectionService.getFileBackend().updateFileParams(message);
+			if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
+				mXmppConnectionService.getPgpEngine().encrypt(message, callback);
+			} else {
+				callback.success(message);
+			}
+		} else {
+			try {
+				mXmppConnectionService.getFileBackend().copyFileToPrivateStorage(message, uri);
+				mXmppConnectionService.getFileBackend().updateFileParams(message);
+				if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
+					final PgpEngine pgpEngine = mXmppConnectionService.getPgpEngine();
+					if (pgpEngine != null) {
+						pgpEngine.encrypt(message, callback);
+					} else if (callback != null) {
+						callback.error(R.string.unable_to_connect_to_keychain, null);
+					}
+				} else {
+					callback.success(message);
+				}
+			} catch (FileBackend.FileCopyException e) {
+				callback.error(e.getResId(), message);
+			}
+		}
+	}
+
+	private void processAsVideo() throws FileNotFoundException {
+		Log.d(Config.LOGTAG,"processing file as video");
+		mXmppConnectionService.startForcingForegroundNotification();
+		message.setRelativeFilePath(message.getUuid() + ".mp4");
+		final DownloadableFile file = mXmppConnectionService.getFileBackend().getFile(message);
+		final int runtime = mXmppConnectionService.getFileBackend().getMediaRuntime(uri);
+		MediaFormatStrategy formatStrategy = runtime >= 8000 ? MediaFormatStrategyPresets.createExportPreset960x540Strategy() : MediaFormatStrategyPresets.createAndroid720pStrategy();
+		Log.d(Config.LOGTAG,"runtime "+runtime);
+		file.getParentFile().mkdirs();
+		ParcelFileDescriptor parcelFileDescriptor = mXmppConnectionService.getContentResolver().openFileDescriptor(uri, "r");
+		FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
+		MediaTranscoder.getInstance().transcodeVideo(fileDescriptor, file.getAbsolutePath(), formatStrategy, this);
+	}
+
+	@Override
+	public void onTranscodeProgress(double progress) {
+		final int p = (int) Math.round(progress * 100);
+		if (p > currentProgress) {
+			currentProgress = p;
+			mXmppConnectionService.getNotificationService().updateFileAddingNotification(p,message);
+		}
+	}
+
+	@Override
+	public void onTranscodeCompleted() {
+		mXmppConnectionService.stopForcingForegroundNotification();
+		mXmppConnectionService.getFileBackend().updateFileParams(message);
+		if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
+			mXmppConnectionService.getPgpEngine().encrypt(message, callback);
+		} else {
+			callback.success(message);
+		}
+	}
+
+	@Override
+	public void onTranscodeCanceled() {
+		mXmppConnectionService.stopForcingForegroundNotification();
+		processAsFile();
+	}
+
+	@Override
+	public void onTranscodeFailed(Exception e) {
+		mXmppConnectionService.stopForcingForegroundNotification();
+		Log.d(Config.LOGTAG,"video transcoding failed "+e.getMessage());
+		processAsFile();
+	}
+
+	@Override
+	public void run() {
+		if (isVideoMessage) {
+			try {
+				processAsVideo();
+			} catch (Throwable e) {
+				processAsFile();
+			}
+		} else {
+			processAsFile();
+		}
+	}
+
+}

src/main/java/eu/siacs/conversations/services/NotificationService.java 🔗

@@ -739,4 +739,16 @@ public class NotificationService {
 				PendingIntent.FLAG_UPDATE_CURRENT));
 		notificationManager.notify(ERROR_NOTIFICATION_ID, mBuilder.build());
 	}
+
+	public Notification updateFileAddingNotification(int current, Message message) {
+		final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(mXmppConnectionService);
+		NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService);
+		mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.transcoding_video));
+		mBuilder.setProgress(100, current, false);
+		mBuilder.setSmallIcon(R.drawable.ic_hourglass_empty_white_24dp);
+		mBuilder.setContentIntent(createContentIntent(message.getConversation()));
+		Notification notification = mBuilder.build();
+		notificationManager.notify(FOREGROUND_NOTIFICATION_ID, notification);
+		return notification;
+	}
 }

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -189,6 +189,7 @@ public class XmppConnectionService extends Service {
 	private NotificationService mNotificationService = new NotificationService(this);
 	private ShortcutService mShortcutService = new ShortcutService(this);
 	private AtomicBoolean mInitialAddressbookSyncCompleted = new AtomicBoolean(false);
+	private AtomicBoolean mForceForegroundService = new AtomicBoolean(false);
 	private OnMessagePacketReceived mMessageParser = new MessageParser(this);
 	private OnPresencePacketReceived mPresenceParser = new PresenceParser(this);
 	private IqParser mIqParser = new IqParser(this);
@@ -398,6 +399,16 @@ public class XmppConnectionService extends Service {
 		}
 	}
 
+	public void startForcingForegroundNotification() {
+		mForceForegroundService.set(true);
+		toggleForegroundService();
+	}
+
+	public void stopForcingForegroundNotification() {
+		mForceForegroundService.set(false);
+		toggleForegroundService();
+	}
+
 	private OpenPgpServiceConnection pgpServiceConnection;
 	private PgpEngine mPgpEngine = null;
 	private WakeLock wakeLock;
@@ -483,106 +494,8 @@ public class XmppConnectionService extends Service {
 		}
 		message.setCounterpart(conversation.getNextCounterpart());
 		message.setType(Message.TYPE_FILE);
-		mFileAddingExecutor.execute(new Runnable() {
-
-			private void processAsFile() {
-				final String path = getFileBackend().getOriginalPath(uri);
-				if (path != null) {
-					message.setRelativeFilePath(path);
-					getFileBackend().updateFileParams(message);
-					if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
-						getPgpEngine().encrypt(message, callback);
-					} else {
-						callback.success(message);
-					}
-				} else {
-					try {
-						getFileBackend().copyFileToPrivateStorage(message, uri);
-						getFileBackend().updateFileParams(message);
-						if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
-							final PgpEngine pgpEngine = getPgpEngine();
-							if (pgpEngine != null) {
-								pgpEngine.encrypt(message, callback);
-							} else if (callback != null) {
-								callback.error(R.string.unable_to_connect_to_keychain, null);
-							}
-						} else {
-							callback.success(message);
-						}
-					} catch (FileBackend.FileCopyException e) {
-						callback.error(e.getResId(), message);
-					}
-				}
-			}
-
-			private void processAsVideo() throws FileNotFoundException {
-				Log.d(Config.LOGTAG,"processing file as video");
-				message.setRelativeFilePath(message.getUuid() + ".mp4");
-				final DownloadableFile file = getFileBackend().getFile(message);
-				final int runtime = getFileBackend().getMediaRuntime(uri);
-				MediaFormatStrategy formatStrategy = runtime >= 8000 ? MediaFormatStrategyPresets.createExportPreset960x540Strategy() : MediaFormatStrategyPresets.createAndroid720pStrategy();
-				Log.d(Config.LOGTAG,"runtime "+runtime);
-				file.getParentFile().mkdirs();
-				ParcelFileDescriptor parcelFileDescriptor = getContentResolver().openFileDescriptor(uri, "r");
-				FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
-				final ArrayList<Integer> progressTracker = new ArrayList<>();
-				final UiInformableCallback<Message> informableCallback;
-				if (callback instanceof UiInformableCallback) {
-					informableCallback = (UiInformableCallback<Message>) callback;
-				} else {
-					informableCallback = null;
-				}
-				MediaTranscoder.Listener listener = new MediaTranscoder.Listener() {
-					@Override
-					public void onTranscodeProgress(double progress) {
-						int p = ((int) Math.round(progress * 100) / 20) * 20;
-						if (!progressTracker.contains(p) && p != 100 && p != 0) {
-							progressTracker.add(p);
-							if (informableCallback != null) {
-								informableCallback.inform(getString(R.string.transcoding_video_progress, String.valueOf(p)));
-							}
-						}
-					}
-
-					@Override
-					public void onTranscodeCompleted() {
-						getFileBackend().updateFileParams(message);
-						if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
-							getPgpEngine().encrypt(message, callback);
-						} else {
-							callback.success(message);
-						}
-					}
-
-					@Override
-					public void onTranscodeCanceled() {
-						processAsFile();
-					}
-
-					@Override
-					public void onTranscodeFailed(Exception e) {
-						Log.d(Config.LOGTAG,"video transcoding failed "+e.getMessage());
-						processAsFile();
-					}
-				};
-				MediaTranscoder.getInstance().transcodeVideo(fileDescriptor, file.getAbsolutePath(), formatStrategy, listener);
-			}
-
-			@Override
-			public void run() {
-				final String mimeType = MimeUtils.guessMimeTypeFromUri(XmppConnectionService.this, uri);
-				if (mimeType != null && mimeType.startsWith("video/") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
-					try {
-						processAsVideo();
-					} catch (Throwable e) {
-						processAsFile();
-					}
-				} else {
-					processAsFile();
-				}
-
-			}
-		});
+		AttachFileToConversationRunnable runnable = new AttachFileToConversationRunnable(this,uri,message,callback);
+		mFileAddingExecutor.execute(runnable);
 	}
 
 	public void attachImageToConversation(final Conversation conversation, final Uri uri, final UiCallback<Message> callback) {
@@ -1095,10 +1008,12 @@ public class XmppConnectionService extends Service {
 	}
 
 	public void toggleForegroundService() {
-		if (keepForegroundService() && hasEnabledAccounts()) {
+		if (mForceForegroundService.get() || (keepForegroundService() && hasEnabledAccounts())) {
 			startForeground(NotificationService.FOREGROUND_NOTIFICATION_ID, this.mNotificationService.createForegroundNotification());
+			Log.d(Config.LOGTAG,"started foreground service");
 		} else {
 			stopForeground(true);
+			Log.d(Config.LOGTAG,"stopped foreground service");
 		}
 	}
 
@@ -1109,10 +1024,10 @@ public class XmppConnectionService extends Service {
 	@Override
 	public void onTaskRemoved(final Intent rootIntent) {
 		super.onTaskRemoved(rootIntent);
-		if (!keepForegroundService()) {
-			this.logoutAndSave(false);
-		} else {
+		if (keepForegroundService() || mForceForegroundService.get()) {
 			Log.d(Config.LOGTAG,"ignoring onTaskRemoved because foreground service is activated");
+		} else {
+			this.logoutAndSave(false);
 		}
 	}
 

src/main/java/eu/siacs/conversations/utils/SerialSingleThreadExecutor.java 🔗

@@ -7,11 +7,13 @@ import java.util.Queue;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 
+import eu.siacs.conversations.services.AttachFileToConversationRunnable;
+
 public class SerialSingleThreadExecutor implements Executor {
 
 	final Executor executor = Executors.newSingleThreadExecutor();
-	protected final Queue<Runnable> tasks = new ArrayDeque();
-	Runnable active;
+	protected final ArrayDeque<Runnable> tasks = new ArrayDeque<>();
+	private Runnable active;
 
 	public SerialSingleThreadExecutor() {
 		this(false);

src/main/res/values/strings.xml 🔗

@@ -700,7 +700,7 @@
 	<string name="pref_automatically_delete_messages_description">Automatically delete messages from this device that are older than the configured time frame.</string>
 	<string name="encrypting_message">Encrypting message</string>
 	<string name="not_fetching_history_retention_period">Not fetching messages due to local retention period.</string>
-	<string name="transcoding_video_progress">Compressing video (%s%% completed)</string>
+	<string name="transcoding_video">Compressing video</string>
 	<string name="corresponding_conversations_closed">Corresponding conversations closed.</string>
 	<string name="contact_blocked_past_tense">Contact blocked.</string>
 	<string name="pref_notifications_from_strangers">Notifications from strangers</string>