From 143713ba57f803e6b0c2aff4fe22f70367dfc8da Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Fri, 20 Sep 2024 16:16:13 +0200 Subject: [PATCH 1/9] add backgroundFetch() api --- jni/dc_wrapper.c | 6 ++++++ src/main/java/com/b44t/messenger/DcAccounts.java | 1 + 2 files changed, 7 insertions(+) diff --git a/jni/dc_wrapper.c b/jni/dc_wrapper.c index 4c87009816..25a8e3ecd1 100644 --- a/jni/dc_wrapper.c +++ b/jni/dc_wrapper.c @@ -276,6 +276,12 @@ JNIEXPORT void Java_com_b44t_messenger_DcAccounts_setPushDeviceToken(JNIEnv *env } +JNIEXPORT jboolean Java_com_b44t_messenger_DcAccounts_backgroundFetch(JNIEnv *env, jobject obj, jint timeout_seconds) +{ + return dc_accounts_background_fetch(get_dc_accounts(env, obj), timeout_seconds) != 0; +} + + JNIEXPORT jint Java_com_b44t_messenger_DcAccounts_addAccount(JNIEnv *env, jobject obj) { return dc_accounts_add_account(get_dc_accounts(env, obj)); diff --git a/src/main/java/com/b44t/messenger/DcAccounts.java b/src/main/java/com/b44t/messenger/DcAccounts.java index de50793a92..c5a7df232c 100644 --- a/src/main/java/com/b44t/messenger/DcAccounts.java +++ b/src/main/java/com/b44t/messenger/DcAccounts.java @@ -25,6 +25,7 @@ public void unref() { public native void stopIo (); public native void maybeNetwork (); public native void setPushDeviceToken (String token); + public native boolean backgroundFetch (int timeoutSeconds); public native int addAccount (); public native int migrateAccount (String dbfile); From 87d515f34f29d3444624baea75dd3a8b316d2fc8 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Fri, 20 Sep 2024 16:16:26 +0200 Subject: [PATCH 2/9] call backgroundFetch() from FCM --- .../securesms/notifications/FcmReceiveService.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java b/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java index c14416160d..5ae2b3610f 100644 --- a/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java +++ b/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java @@ -95,9 +95,8 @@ public static String getToken() { @Override public void onMessageReceived(@NonNull RemoteMessage remoteMessage) { Log.i(TAG, "FCM push notification received"); - // the app is running (again) now and fetching and notifications should be processed as usual. - // to support accounts that do not send PUSH notifications and for simplicity, - // we just let the app run as long as possible. + ApplicationContext.dcAccounts.backgroundFetch(120); + Log.i(TAG, "background fetch done"); } @Override From ab73a74eee2d2efcf5ba7fe0cd19e985cac4ab24 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sat, 21 Sep 2024 00:53:11 +0200 Subject: [PATCH 3/9] show a foreground service notification --- .../notifications/FcmReceiveService.java | 23 +++++++++++++++++-- .../java/com/b44t/messenger/DcContext.java | 1 + .../securesms/connect/DcEventCenter.java | 5 ++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java b/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java index 5ae2b3610f..999b762f72 100644 --- a/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java +++ b/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java @@ -17,15 +17,20 @@ import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.BuildConfig; -import org.thoughtcrime.securesms.util.Prefs; +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.service.GenericForegroundService; +import org.thoughtcrime.securesms.service.NotificationController; import org.thoughtcrime.securesms.util.Util; public class FcmReceiveService extends FirebaseMessagingService { private static final String TAG = FcmReceiveService.class.getSimpleName(); private static final Object INIT_LOCK = new Object(); + private static final Object NOTIFICATION_CONTROLLER_LOCK = new Object(); + private static boolean initialized; private static volatile boolean triedRegistering; private static volatile String prefixedToken; + private static NotificationController notificationController; public static void register(Context context) { if (Build.VERSION.SDK_INT < 19) { @@ -92,13 +97,27 @@ public static String getToken() { return prefixedToken; } + @WorkerThread @Override public void onMessageReceived(@NonNull RemoteMessage remoteMessage) { Log.i(TAG, "FCM push notification received"); - ApplicationContext.dcAccounts.backgroundFetch(120); + synchronized (NOTIFICATION_CONTROLLER_LOCK) { + notificationController = GenericForegroundService.startForegroundTask(this, getString(R.string.connectivity_updating)); + if (!ApplicationContext.dcAccounts.backgroundFetch(19)) { // we should complete within 20 seconds + notificationController.close(); + notificationController = null; + } + } Log.i(TAG, "background fetch done"); } + public static void backgroundFetchDone() { + synchronized (NOTIFICATION_CONTROLLER_LOCK) { + notificationController.close(); + notificationController = null; + } + } + @Override public void onDeletedMessages() { Log.i(TAG, "FCM push notifications dropped"); diff --git a/src/main/java/com/b44t/messenger/DcContext.java b/src/main/java/com/b44t/messenger/DcContext.java index df59a248d5..b02b4dc22b 100644 --- a/src/main/java/com/b44t/messenger/DcContext.java +++ b/src/main/java/com/b44t/messenger/DcContext.java @@ -31,6 +31,7 @@ public class DcContext { public final static int DC_EVENT_WEBXDC_STATUS_UPDATE = 2120; public final static int DC_EVENT_WEBXDC_INSTANCE_DELETED = 2121; public final static int DC_EVENT_WEBXDC_REALTIME_DATA = 2150; + public final static int DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE = 2200; public final static int DC_IMEX_EXPORT_SELF_KEYS = 1; public final static int DC_IMEX_IMPORT_SELF_KEYS = 2; diff --git a/src/main/java/org/thoughtcrime/securesms/connect/DcEventCenter.java b/src/main/java/org/thoughtcrime/securesms/connect/DcEventCenter.java index fe20ac10db..72ec618d21 100644 --- a/src/main/java/org/thoughtcrime/securesms/connect/DcEventCenter.java +++ b/src/main/java/org/thoughtcrime/securesms/connect/DcEventCenter.java @@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.notifications.FcmReceiveService; import org.thoughtcrime.securesms.util.Util; import java.util.ArrayList; @@ -177,6 +178,10 @@ public long handleEvent(@NonNull DcEvent event) { DcHelper.getNotificationCenter(context).removeNotifications(accountId, event.getData1Int()); break; + case DcContext.DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE: + FcmReceiveService.backgroundFetchDone(); + break; + case DcContext.DC_EVENT_IMEX_PROGRESS: sendToCurrentAccountObservers(event); return 0; From b7e5bee655bb839153a9229e0035eb6c711fb723 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sat, 21 Sep 2024 14:33:25 +0200 Subject: [PATCH 4/9] add pinActivity parameter to GenericForegroundService --- .../notifications/FcmReceiveService.java | 2 +- .../PassphraseRequiredActionBarActivity.java | 2 +- .../securesms/WelcomeActivity.java | 2 +- .../ListSummaryPreferenceFragment.java | 2 +- .../securesms/qr/BackupTransferActivity.java | 2 +- .../service/GenericForegroundService.java | 52 ++++++++----------- 6 files changed, 28 insertions(+), 34 deletions(-) diff --git a/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java b/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java index 999b762f72..b52d2ca0a3 100644 --- a/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java +++ b/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java @@ -102,7 +102,7 @@ public static String getToken() { public void onMessageReceived(@NonNull RemoteMessage remoteMessage) { Log.i(TAG, "FCM push notification received"); synchronized (NOTIFICATION_CONTROLLER_LOCK) { - notificationController = GenericForegroundService.startForegroundTask(this, getString(R.string.connectivity_updating)); + notificationController = GenericForegroundService.startForegroundTask(this, getString(R.string.connectivity_updating), false); if (!ApplicationContext.dcAccounts.backgroundFetch(19)) { // we should complete within 20 seconds notificationController.close(); notificationController = null; diff --git a/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java b/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java index c043a046b3..4ede775c53 100644 --- a/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java @@ -20,7 +20,7 @@ protected final void onCreate(Bundle savedInstanceState) { return; } - if (GenericForegroundService.isForegroundTaskStarted()) { + if (GenericForegroundService.hasPinnedActivity()) { // this does not prevent intent set by onNewIntent(), // however, at least during onboarding, // this catches a lot of situations with otherwise weird app states. diff --git a/src/main/java/org/thoughtcrime/securesms/WelcomeActivity.java b/src/main/java/org/thoughtcrime/securesms/WelcomeActivity.java index 54d45c075b..202a05db91 100644 --- a/src/main/java/org/thoughtcrime/securesms/WelcomeActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/WelcomeActivity.java @@ -202,7 +202,7 @@ private void startImportBackup() { private void startImport(@Nullable final String backupFile, final @Nullable Uri backupFileUri) { - notificationController = GenericForegroundService.startForegroundTask(this, getString(R.string.import_backup_title)); + notificationController = GenericForegroundService.startForegroundTask(this, getString(R.string.import_backup_title), true); if( progressDialog!=null ) { progressDialog.dismiss(); diff --git a/src/main/java/org/thoughtcrime/securesms/preferences/ListSummaryPreferenceFragment.java b/src/main/java/org/thoughtcrime/securesms/preferences/ListSummaryPreferenceFragment.java index 361c427a18..d60df44f00 100644 --- a/src/main/java/org/thoughtcrime/securesms/preferences/ListSummaryPreferenceFragment.java +++ b/src/main/java/org/thoughtcrime/securesms/preferences/ListSummaryPreferenceFragment.java @@ -151,7 +151,7 @@ private void stopOngoingProcess() { } private void showProgressDialog() { - notificationController = GenericForegroundService.startForegroundTask(getContext(), getString(R.string.export_backup_desktop)); + notificationController = GenericForegroundService.startForegroundTask(getContext(), getString(R.string.export_backup_desktop), true); if( progressDialog!=null ) { progressDialog.dismiss(); progressDialog = null; diff --git a/src/main/java/org/thoughtcrime/securesms/qr/BackupTransferActivity.java b/src/main/java/org/thoughtcrime/securesms/qr/BackupTransferActivity.java index 1b203b3584..6d29fff0d5 100644 --- a/src/main/java/org/thoughtcrime/securesms/qr/BackupTransferActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/qr/BackupTransferActivity.java @@ -67,7 +67,7 @@ protected void onCreate(Bundle icicle) { DcHelper.getAccounts(this).stopIo(); String title = getString(transferMode == TransferMode.RECEIVER_SCAN_QR ? R.string.multidevice_receiver_title : R.string.multidevice_title); - notificationController = GenericForegroundService.startForegroundTask(this, title); + notificationController = GenericForegroundService.startForegroundTask(this, title, true); setContentView(R.layout.backup_provider_activity); diff --git a/src/main/java/org/thoughtcrime/securesms/service/GenericForegroundService.java b/src/main/java/org/thoughtcrime/securesms/service/GenericForegroundService.java index 60b5b912c4..96e6e67666 100644 --- a/src/main/java/org/thoughtcrime/securesms/service/GenericForegroundService.java +++ b/src/main/java/org/thoughtcrime/securesms/service/GenericForegroundService.java @@ -12,7 +12,6 @@ import android.os.IBinder; import android.util.Log; -import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat.Builder; @@ -38,7 +37,7 @@ public final class GenericForegroundService extends Service { private static final String EXTRA_TITLE = "extra_title"; private static final String EXTRA_CONTENT_TEXT = "extra_content_text"; private static final String EXTRA_CHANNEL_ID = "extra_channel_id"; - private static final String EXTRA_ICON_RES = "extra_icon_res"; + private static final String EXTRA_PIN_ACTIVITY = "extra_pin_activity"; private static final String EXTRA_ID = "extra_id"; private static final String EXTRA_PROGRESS = "extra_progress"; private static final String EXTRA_PROGRESS_MAX = "extra_progress_max"; @@ -50,11 +49,11 @@ public final class GenericForegroundService extends Service { private static final AtomicInteger NEXT_ID = new AtomicInteger(); private static final AtomicBoolean CHANNEL_CREATED = new AtomicBoolean(false); - private static int startedCounter = 0; + private static int pinnedActivityCounter = 0; private final LinkedHashMap allActiveMessages = new LinkedHashMap<>(); - private static final Entry DEFAULTS = new Entry("", "", NotificationCenter.CH_GENERIC, R.drawable.icon_notification, -1, 0, 0, false); + private static final Entry DEFAULTS = new Entry("", "", NotificationCenter.CH_GENERIC, true, -1, 0, 0, false); private @Nullable Entry lastPosted; @@ -92,28 +91,24 @@ private synchronized void updateNotification() { private synchronized void handleStart(@NonNull Intent intent) { Entry entry = Entry.fromIntent(intent); - - Log.i(TAG, String.format(Locale.ENGLISH, "handleStart() %s", entry)); - allActiveMessages.put(entry.id, entry); + if (entry.pinActivity) { + pinnedActivityCounter++; + } } private synchronized void handleStop(@NonNull Intent intent) { - Log.i(TAG, "handleStop()"); - int id = intent.getIntExtra(EXTRA_ID, -1); - Entry removed = allActiveMessages.remove(id); - - if (removed == null) { - Log.w(TAG, "Could not find entry to remove"); + if (removed != null && removed.pinActivity) { + pinnedActivityCounter = Math.max(pinnedActivityCounter-1, 0); } } private void postObligatoryForegroundNotification(@NonNull Entry active) { lastPosted = active; startForeground(NotificationCenter.ID_GENERIC, new Builder(this, active.channelId) - .setSmallIcon(active.iconRes) + .setSmallIcon(R.drawable.notification_permanent) .setContentTitle(active.title) .setTicker(active.contentText) .setContentText(active.contentText) @@ -127,9 +122,9 @@ public IBinder onBind(Intent intent) { return binder; } - - public static NotificationController startForegroundTask(@NonNull Context context, @NonNull String task) { - startedCounter++; + // pinActivity makes the recent activity stay on top + // and tries to avoid it being replaced eg. by the chatlist when tapping the app icon. + public static NotificationController startForegroundTask(@NonNull Context context, @NonNull String task, boolean pinActivity) { final int id = NEXT_ID.getAndIncrement(); createFgNotificationChannel(context); @@ -137,7 +132,7 @@ public static NotificationController startForegroundTask(@NonNull Context contex intent.setAction(ACTION_START); intent.putExtra(EXTRA_TITLE, task); intent.putExtra(EXTRA_CHANNEL_ID, NotificationCenter.CH_GENERIC); - intent.putExtra(EXTRA_ICON_RES, R.drawable.notification_permanent); + intent.putExtra(EXTRA_PIN_ACTIVITY, pinActivity); intent.putExtra(EXTRA_ID, id); ContextCompat.startForegroundService(context, intent); @@ -151,11 +146,10 @@ public static void stopForegroundTask(@NonNull Context context, int id) { intent.putExtra(EXTRA_ID, id); ContextCompat.startForegroundService(context, intent); - startedCounter = Math.max(startedCounter-1, 0); } - public static boolean isForegroundTaskStarted() { - return startedCounter > 0; + public static boolean hasPinnedActivity() { + return pinnedActivityCounter > 0; } synchronized void replaceProgress(int id, int progressMax, int progress, boolean indeterminate, String message) { @@ -170,7 +164,7 @@ synchronized void replaceProgress(int id, int progressMax, int progress, boolean message = oldEntry.contentText; } - Entry newEntry = new Entry(oldEntry.title, message, oldEntry.channelId, oldEntry.iconRes, oldEntry.id, progressMax, progress, indeterminate); + Entry newEntry = new Entry(oldEntry.title, message, oldEntry.channelId, oldEntry.pinActivity, oldEntry.id, progressMax, progress, indeterminate); if (oldEntry.equals(newEntry)) { Log.d(TAG, String.format("handleReplace() skip, no change %s", newEntry)); @@ -202,16 +196,16 @@ private static class Entry { final @NonNull String contentText; final @NonNull String channelId; final int id; - final @DrawableRes int iconRes; + final boolean pinActivity; final int progress; final int progressMax; final boolean indeterminate; - private Entry(@NonNull String title, @NonNull String contentText, @NonNull String channelId, @DrawableRes int iconRes, int id, int progressMax, int progress, boolean indeterminate) { + private Entry(@NonNull String title, @NonNull String contentText, @NonNull String channelId, boolean pinActivity, int id, int progressMax, int progress, boolean indeterminate) { this.title = title; this.contentText = contentText; this.channelId = channelId; - this.iconRes = iconRes; + this.pinActivity = pinActivity; this.id = id; this.progress = progress; this.progressMax = progressMax; @@ -230,12 +224,12 @@ private static Entry fromIntent(@NonNull Intent intent) { String channelId = intent.getStringExtra(EXTRA_CHANNEL_ID); if (channelId == null) channelId = DEFAULTS.channelId; - int iconRes = intent.getIntExtra(EXTRA_ICON_RES, DEFAULTS.iconRes); + boolean pinActivity = intent.getBooleanExtra(EXTRA_PIN_ACTIVITY, DEFAULTS.pinActivity); int progress = intent.getIntExtra(EXTRA_PROGRESS, DEFAULTS.progress); int progressMax = intent.getIntExtra(EXTRA_PROGRESS_MAX, DEFAULTS.progressMax); boolean indeterminate = intent.getBooleanExtra(EXTRA_PROGRESS_INDETERMINATE, DEFAULTS.indeterminate); - return new Entry(title, contentText, channelId, iconRes, id, progressMax, progress, indeterminate); + return new Entry(title, contentText, channelId, pinActivity, id, progressMax, progress, indeterminate); } @Override @@ -250,7 +244,7 @@ public boolean equals(Object o) { Entry entry = (Entry) o; return id == entry.id && - iconRes == entry.iconRes && + pinActivity == entry.pinActivity && progress == entry.progress && progressMax == entry.progressMax && indeterminate == entry.indeterminate && @@ -267,7 +261,7 @@ public int hashCode() { hashCode *= 31; hashCode += id; hashCode *= 31; - hashCode += iconRes; + hashCode += pinActivity ? 1 : 0; hashCode *= 31; hashCode += progress; hashCode *= 31; From f0da58ba475a827e638fd5aef0e6d5982488c3c1 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sat, 21 Sep 2024 22:39:27 +0200 Subject: [PATCH 5/9] Revert "add pinActivity parameter to GenericForegroundService" This reverts commit b7e5bee655bb839153a9229e0035eb6c711fb723. --- .../notifications/FcmReceiveService.java | 2 +- .../PassphraseRequiredActionBarActivity.java | 2 +- .../securesms/WelcomeActivity.java | 2 +- .../ListSummaryPreferenceFragment.java | 2 +- .../securesms/qr/BackupTransferActivity.java | 2 +- .../service/GenericForegroundService.java | 52 +++++++++++-------- 6 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java b/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java index b52d2ca0a3..999b762f72 100644 --- a/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java +++ b/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java @@ -102,7 +102,7 @@ public static String getToken() { public void onMessageReceived(@NonNull RemoteMessage remoteMessage) { Log.i(TAG, "FCM push notification received"); synchronized (NOTIFICATION_CONTROLLER_LOCK) { - notificationController = GenericForegroundService.startForegroundTask(this, getString(R.string.connectivity_updating), false); + notificationController = GenericForegroundService.startForegroundTask(this, getString(R.string.connectivity_updating)); if (!ApplicationContext.dcAccounts.backgroundFetch(19)) { // we should complete within 20 seconds notificationController.close(); notificationController = null; diff --git a/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java b/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java index 4ede775c53..c043a046b3 100644 --- a/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java @@ -20,7 +20,7 @@ protected final void onCreate(Bundle savedInstanceState) { return; } - if (GenericForegroundService.hasPinnedActivity()) { + if (GenericForegroundService.isForegroundTaskStarted()) { // this does not prevent intent set by onNewIntent(), // however, at least during onboarding, // this catches a lot of situations with otherwise weird app states. diff --git a/src/main/java/org/thoughtcrime/securesms/WelcomeActivity.java b/src/main/java/org/thoughtcrime/securesms/WelcomeActivity.java index 202a05db91..54d45c075b 100644 --- a/src/main/java/org/thoughtcrime/securesms/WelcomeActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/WelcomeActivity.java @@ -202,7 +202,7 @@ private void startImportBackup() { private void startImport(@Nullable final String backupFile, final @Nullable Uri backupFileUri) { - notificationController = GenericForegroundService.startForegroundTask(this, getString(R.string.import_backup_title), true); + notificationController = GenericForegroundService.startForegroundTask(this, getString(R.string.import_backup_title)); if( progressDialog!=null ) { progressDialog.dismiss(); diff --git a/src/main/java/org/thoughtcrime/securesms/preferences/ListSummaryPreferenceFragment.java b/src/main/java/org/thoughtcrime/securesms/preferences/ListSummaryPreferenceFragment.java index d60df44f00..361c427a18 100644 --- a/src/main/java/org/thoughtcrime/securesms/preferences/ListSummaryPreferenceFragment.java +++ b/src/main/java/org/thoughtcrime/securesms/preferences/ListSummaryPreferenceFragment.java @@ -151,7 +151,7 @@ private void stopOngoingProcess() { } private void showProgressDialog() { - notificationController = GenericForegroundService.startForegroundTask(getContext(), getString(R.string.export_backup_desktop), true); + notificationController = GenericForegroundService.startForegroundTask(getContext(), getString(R.string.export_backup_desktop)); if( progressDialog!=null ) { progressDialog.dismiss(); progressDialog = null; diff --git a/src/main/java/org/thoughtcrime/securesms/qr/BackupTransferActivity.java b/src/main/java/org/thoughtcrime/securesms/qr/BackupTransferActivity.java index 6d29fff0d5..1b203b3584 100644 --- a/src/main/java/org/thoughtcrime/securesms/qr/BackupTransferActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/qr/BackupTransferActivity.java @@ -67,7 +67,7 @@ protected void onCreate(Bundle icicle) { DcHelper.getAccounts(this).stopIo(); String title = getString(transferMode == TransferMode.RECEIVER_SCAN_QR ? R.string.multidevice_receiver_title : R.string.multidevice_title); - notificationController = GenericForegroundService.startForegroundTask(this, title, true); + notificationController = GenericForegroundService.startForegroundTask(this, title); setContentView(R.layout.backup_provider_activity); diff --git a/src/main/java/org/thoughtcrime/securesms/service/GenericForegroundService.java b/src/main/java/org/thoughtcrime/securesms/service/GenericForegroundService.java index 96e6e67666..60b5b912c4 100644 --- a/src/main/java/org/thoughtcrime/securesms/service/GenericForegroundService.java +++ b/src/main/java/org/thoughtcrime/securesms/service/GenericForegroundService.java @@ -12,6 +12,7 @@ import android.os.IBinder; import android.util.Log; +import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat.Builder; @@ -37,7 +38,7 @@ public final class GenericForegroundService extends Service { private static final String EXTRA_TITLE = "extra_title"; private static final String EXTRA_CONTENT_TEXT = "extra_content_text"; private static final String EXTRA_CHANNEL_ID = "extra_channel_id"; - private static final String EXTRA_PIN_ACTIVITY = "extra_pin_activity"; + private static final String EXTRA_ICON_RES = "extra_icon_res"; private static final String EXTRA_ID = "extra_id"; private static final String EXTRA_PROGRESS = "extra_progress"; private static final String EXTRA_PROGRESS_MAX = "extra_progress_max"; @@ -49,11 +50,11 @@ public final class GenericForegroundService extends Service { private static final AtomicInteger NEXT_ID = new AtomicInteger(); private static final AtomicBoolean CHANNEL_CREATED = new AtomicBoolean(false); - private static int pinnedActivityCounter = 0; + private static int startedCounter = 0; private final LinkedHashMap allActiveMessages = new LinkedHashMap<>(); - private static final Entry DEFAULTS = new Entry("", "", NotificationCenter.CH_GENERIC, true, -1, 0, 0, false); + private static final Entry DEFAULTS = new Entry("", "", NotificationCenter.CH_GENERIC, R.drawable.icon_notification, -1, 0, 0, false); private @Nullable Entry lastPosted; @@ -91,24 +92,28 @@ private synchronized void updateNotification() { private synchronized void handleStart(@NonNull Intent intent) { Entry entry = Entry.fromIntent(intent); + + Log.i(TAG, String.format(Locale.ENGLISH, "handleStart() %s", entry)); + allActiveMessages.put(entry.id, entry); - if (entry.pinActivity) { - pinnedActivityCounter++; - } } private synchronized void handleStop(@NonNull Intent intent) { + Log.i(TAG, "handleStop()"); + int id = intent.getIntExtra(EXTRA_ID, -1); + Entry removed = allActiveMessages.remove(id); - if (removed != null && removed.pinActivity) { - pinnedActivityCounter = Math.max(pinnedActivityCounter-1, 0); + + if (removed == null) { + Log.w(TAG, "Could not find entry to remove"); } } private void postObligatoryForegroundNotification(@NonNull Entry active) { lastPosted = active; startForeground(NotificationCenter.ID_GENERIC, new Builder(this, active.channelId) - .setSmallIcon(R.drawable.notification_permanent) + .setSmallIcon(active.iconRes) .setContentTitle(active.title) .setTicker(active.contentText) .setContentText(active.contentText) @@ -122,9 +127,9 @@ public IBinder onBind(Intent intent) { return binder; } - // pinActivity makes the recent activity stay on top - // and tries to avoid it being replaced eg. by the chatlist when tapping the app icon. - public static NotificationController startForegroundTask(@NonNull Context context, @NonNull String task, boolean pinActivity) { + + public static NotificationController startForegroundTask(@NonNull Context context, @NonNull String task) { + startedCounter++; final int id = NEXT_ID.getAndIncrement(); createFgNotificationChannel(context); @@ -132,7 +137,7 @@ public static NotificationController startForegroundTask(@NonNull Context contex intent.setAction(ACTION_START); intent.putExtra(EXTRA_TITLE, task); intent.putExtra(EXTRA_CHANNEL_ID, NotificationCenter.CH_GENERIC); - intent.putExtra(EXTRA_PIN_ACTIVITY, pinActivity); + intent.putExtra(EXTRA_ICON_RES, R.drawable.notification_permanent); intent.putExtra(EXTRA_ID, id); ContextCompat.startForegroundService(context, intent); @@ -146,10 +151,11 @@ public static void stopForegroundTask(@NonNull Context context, int id) { intent.putExtra(EXTRA_ID, id); ContextCompat.startForegroundService(context, intent); + startedCounter = Math.max(startedCounter-1, 0); } - public static boolean hasPinnedActivity() { - return pinnedActivityCounter > 0; + public static boolean isForegroundTaskStarted() { + return startedCounter > 0; } synchronized void replaceProgress(int id, int progressMax, int progress, boolean indeterminate, String message) { @@ -164,7 +170,7 @@ synchronized void replaceProgress(int id, int progressMax, int progress, boolean message = oldEntry.contentText; } - Entry newEntry = new Entry(oldEntry.title, message, oldEntry.channelId, oldEntry.pinActivity, oldEntry.id, progressMax, progress, indeterminate); + Entry newEntry = new Entry(oldEntry.title, message, oldEntry.channelId, oldEntry.iconRes, oldEntry.id, progressMax, progress, indeterminate); if (oldEntry.equals(newEntry)) { Log.d(TAG, String.format("handleReplace() skip, no change %s", newEntry)); @@ -196,16 +202,16 @@ private static class Entry { final @NonNull String contentText; final @NonNull String channelId; final int id; - final boolean pinActivity; + final @DrawableRes int iconRes; final int progress; final int progressMax; final boolean indeterminate; - private Entry(@NonNull String title, @NonNull String contentText, @NonNull String channelId, boolean pinActivity, int id, int progressMax, int progress, boolean indeterminate) { + private Entry(@NonNull String title, @NonNull String contentText, @NonNull String channelId, @DrawableRes int iconRes, int id, int progressMax, int progress, boolean indeterminate) { this.title = title; this.contentText = contentText; this.channelId = channelId; - this.pinActivity = pinActivity; + this.iconRes = iconRes; this.id = id; this.progress = progress; this.progressMax = progressMax; @@ -224,12 +230,12 @@ private static Entry fromIntent(@NonNull Intent intent) { String channelId = intent.getStringExtra(EXTRA_CHANNEL_ID); if (channelId == null) channelId = DEFAULTS.channelId; - boolean pinActivity = intent.getBooleanExtra(EXTRA_PIN_ACTIVITY, DEFAULTS.pinActivity); + int iconRes = intent.getIntExtra(EXTRA_ICON_RES, DEFAULTS.iconRes); int progress = intent.getIntExtra(EXTRA_PROGRESS, DEFAULTS.progress); int progressMax = intent.getIntExtra(EXTRA_PROGRESS_MAX, DEFAULTS.progressMax); boolean indeterminate = intent.getBooleanExtra(EXTRA_PROGRESS_INDETERMINATE, DEFAULTS.indeterminate); - return new Entry(title, contentText, channelId, pinActivity, id, progressMax, progress, indeterminate); + return new Entry(title, contentText, channelId, iconRes, id, progressMax, progress, indeterminate); } @Override @@ -244,7 +250,7 @@ public boolean equals(Object o) { Entry entry = (Entry) o; return id == entry.id && - pinActivity == entry.pinActivity && + iconRes == entry.iconRes && progress == entry.progress && progressMax == entry.progressMax && indeterminate == entry.indeterminate && @@ -261,7 +267,7 @@ public int hashCode() { hashCode *= 31; hashCode += id; hashCode *= 31; - hashCode += pinActivity ? 1 : 0; + hashCode += iconRes; hashCode *= 31; hashCode += progress; hashCode *= 31; From dff11ad6f242aac1fc1ed4a336bfdfb4b313c595 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sat, 21 Sep 2024 23:39:34 +0200 Subject: [PATCH 6/9] use explicit FetchForegroundService this avoids potential issues with GenericForegroundService which eg. may block app start. --- .../notifications/FcmReceiveService.java | 23 ++----- src/main/AndroidManifest.xml | 4 ++ .../securesms/connect/DcEventCenter.java | 4 +- .../notifications/NotificationCenter.java | 1 + .../service/FetchForegroundService.java | 61 +++++++++++++++++++ .../service/GenericForegroundService.java | 2 +- 6 files changed, 73 insertions(+), 22 deletions(-) create mode 100644 src/main/java/org/thoughtcrime/securesms/service/FetchForegroundService.java diff --git a/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java b/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java index 999b762f72..ebaaf604cf 100644 --- a/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java +++ b/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java @@ -17,20 +17,15 @@ import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.BuildConfig; -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.service.GenericForegroundService; -import org.thoughtcrime.securesms.service.NotificationController; +import org.thoughtcrime.securesms.service.FetchForegroundService; import org.thoughtcrime.securesms.util.Util; public class FcmReceiveService extends FirebaseMessagingService { private static final String TAG = FcmReceiveService.class.getSimpleName(); private static final Object INIT_LOCK = new Object(); - private static final Object NOTIFICATION_CONTROLLER_LOCK = new Object(); - private static boolean initialized; private static volatile boolean triedRegistering; private static volatile String prefixedToken; - private static NotificationController notificationController; public static void register(Context context) { if (Build.VERSION.SDK_INT < 19) { @@ -101,23 +96,13 @@ public static String getToken() { @Override public void onMessageReceived(@NonNull RemoteMessage remoteMessage) { Log.i(TAG, "FCM push notification received"); - synchronized (NOTIFICATION_CONTROLLER_LOCK) { - notificationController = GenericForegroundService.startForegroundTask(this, getString(R.string.connectivity_updating)); - if (!ApplicationContext.dcAccounts.backgroundFetch(19)) { // we should complete within 20 seconds - notificationController.close(); - notificationController = null; - } + FetchForegroundService.start(this); + if (!ApplicationContext.dcAccounts.backgroundFetch(19)) { // we should complete within 20 seconds + FetchForegroundService.stop(this); } Log.i(TAG, "background fetch done"); } - public static void backgroundFetchDone() { - synchronized (NOTIFICATION_CONTROLLER_LOCK) { - notificationController.close(); - notificationController = null; - } - } - @Override public void onDeletedMessages() { Log.i(TAG, "FCM push notifications dropped"); diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index ec201402cc..486980fcdc 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -380,6 +380,10 @@ android:name=".service.GenericForegroundService" android:foregroundServiceType="dataSync" /> + + = Build.VERSION_CODES.O) { CHANNEL_CREATED.set(true); NotificationChannel channel = new NotificationChannel(NotificationCenter.CH_GENERIC, From 4dc53245f860cfcc4d371b51eb2f15886ef1917d Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sun, 22 Sep 2024 12:22:01 +0200 Subject: [PATCH 7/9] add reference for the 20 seconds time span --- .../securesms/notifications/FcmReceiveService.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java b/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java index ebaaf604cf..85953bbf41 100644 --- a/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java +++ b/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java @@ -97,9 +97,13 @@ public static String getToken() { public void onMessageReceived(@NonNull RemoteMessage remoteMessage) { Log.i(TAG, "FCM push notification received"); FetchForegroundService.start(this); - if (!ApplicationContext.dcAccounts.backgroundFetch(19)) { // we should complete within 20 seconds + + // we should complete within 20 seconds, + // see https://firebase.google.com/docs/cloud-messaging/android/receive + if (!ApplicationContext.dcAccounts.backgroundFetch(19)) { FetchForegroundService.stop(this); } + Log.i(TAG, "background fetch done"); } From dff2d9822202d0566adb3636a9c017b184b48a4b Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sun, 22 Sep 2024 12:37:01 +0200 Subject: [PATCH 8/9] move backgroundFetch() to FetchForegroundService --- .../notifications/FcmReceiveService.java | 8 -------- .../service/FetchForegroundService.java | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java b/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java index 85953bbf41..8aecf54a8b 100644 --- a/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java +++ b/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java @@ -97,14 +97,6 @@ public static String getToken() { public void onMessageReceived(@NonNull RemoteMessage remoteMessage) { Log.i(TAG, "FCM push notification received"); FetchForegroundService.start(this); - - // we should complete within 20 seconds, - // see https://firebase.google.com/docs/cloud-messaging/android/receive - if (!ApplicationContext.dcAccounts.backgroundFetch(19)) { - FetchForegroundService.stop(this); - } - - Log.i(TAG, "background fetch done"); } @Override diff --git a/src/main/java/org/thoughtcrime/securesms/service/FetchForegroundService.java b/src/main/java/org/thoughtcrime/securesms/service/FetchForegroundService.java index ca10d3277e..ddb97310bb 100644 --- a/src/main/java/org/thoughtcrime/securesms/service/FetchForegroundService.java +++ b/src/main/java/org/thoughtcrime/securesms/service/FetchForegroundService.java @@ -5,15 +5,20 @@ import android.content.Context; import android.content.Intent; import android.os.IBinder; +import android.util.Log; import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; import androidx.core.content.ContextCompat; +import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.notifications.FcmReceiveService; import org.thoughtcrime.securesms.notifications.NotificationCenter; +import org.thoughtcrime.securesms.util.Util; public final class FetchForegroundService extends Service { + private static final String TAG = FcmReceiveService.class.getSimpleName(); private static final Object SERVICE_LOCK = new Object(); private static Intent service; @@ -38,6 +43,7 @@ public static void stop(Context context) { @Override public void onCreate() { + Log.i(TAG, "Creating fetch service"); super.onCreate(); Notification notification = new NotificationCompat.Builder(this, NotificationCenter.CH_GENERIC) @@ -46,6 +52,18 @@ public void onCreate() { .build(); startForeground(NotificationCenter.ID_FETCH, notification); + + // Start explicit fetch only after we marked ourselves as requiring foreground; + // this may help we on getting network and time adequately + // Fetch is started in background to not block the UI. + // We then run not longer than the max. of 20 seconds, + // see https://firebase.google.com/docs/cloud-messaging/android/receive . + Util.runOnAnyBackgroundThread(() -> { + Log.i(TAG, "Starting fetch"); + if (!ApplicationContext.dcAccounts.backgroundFetch(19)) { + FetchForegroundService.stop(this); + } + }); } @Override From b827643d9d6f0a6194d1c15eed6651cc52e4bcd0 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Mon, 23 Sep 2024 13:29:03 +0200 Subject: [PATCH 9/9] as we called startForeground(), longer timeouts should be fine --- .../securesms/service/FetchForegroundService.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/service/FetchForegroundService.java b/src/main/java/org/thoughtcrime/securesms/service/FetchForegroundService.java index ddb97310bb..71967f559e 100644 --- a/src/main/java/org/thoughtcrime/securesms/service/FetchForegroundService.java +++ b/src/main/java/org/thoughtcrime/securesms/service/FetchForegroundService.java @@ -53,16 +53,11 @@ public void onCreate() { startForeground(NotificationCenter.ID_FETCH, notification); - // Start explicit fetch only after we marked ourselves as requiring foreground; - // this may help we on getting network and time adequately - // Fetch is started in background to not block the UI. - // We then run not longer than the max. of 20 seconds, - // see https://firebase.google.com/docs/cloud-messaging/android/receive . Util.runOnAnyBackgroundThread(() -> { Log.i(TAG, "Starting fetch"); - if (!ApplicationContext.dcAccounts.backgroundFetch(19)) { + if (!ApplicationContext.dcAccounts.backgroundFetch(300)) { // as startForeground() was called, there is time FetchForegroundService.stop(this); - } + } // else we stop FetchForegroundService on DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE }); }