diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index bad7691565..af552ae0f5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider; import org.thoughtcrime.securesms.emoji.EmojiSource; import org.thoughtcrime.securesms.emoji.JumboEmoji; +import org.thoughtcrime.securesms.gcm.FcmFetchManager; import org.thoughtcrime.securesms.gcm.FcmJobService; import org.thoughtcrime.securesms.jobs.AccountConsistencyWorkerJob; import org.thoughtcrime.securesms.jobs.CheckServiceReachabilityJob; @@ -230,6 +231,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr ApplicationDependencies.getMegaphoneRepository().onAppForegrounded(); ApplicationDependencies.getDeadlockDetector().start(); SubscriptionKeepAliveJob.enqueueAndTrackTimeIfNecessary(); + FcmFetchManager.onForeground(this); SignalExecutors.BOUNDED.execute(() -> { FeatureFlags.refreshIfNecessary(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchManager.kt b/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchManager.kt index d72329f679..7129da1c44 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchManager.kt @@ -1,13 +1,23 @@ package org.thoughtcrime.securesms.gcm +import android.app.Notification +import android.app.PendingIntent import android.content.Context import android.content.Intent import android.os.Build +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import org.signal.core.util.PendingIntentFlags.mutable import org.signal.core.util.concurrent.SignalExecutors import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.MainActivity +import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob import org.thoughtcrime.securesms.messages.WebSocketStrategy +import org.thoughtcrime.securesms.notifications.NotificationChannels +import org.thoughtcrime.securesms.notifications.NotificationIds +import org.thoughtcrime.securesms.util.FeatureFlags import org.thoughtcrime.securesms.util.SignalLocalMetrics import org.thoughtcrime.securesms.util.concurrent.SerialMonoLifoExecutor import kotlin.time.Duration.Companion.minutes @@ -38,6 +48,9 @@ object FcmFetchManager { @Volatile private var activeCount = 0 + @Volatile + private var highPriority = false + /** * @return True if a service was successfully started, otherwise false. */ @@ -56,13 +69,41 @@ object FcmFetchManager { FcmFetchForegroundService.startServiceIfNecessary(context) } + private fun postMayHaveMessagesNotification(context: Context) { + if (FeatureFlags.fcmMayHaveMessagesNotificationKillSwitch()) { + Log.w(TAG, "May have messages notification kill switch") + return + } + val mayHaveMessagesNotification: Notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().ADDITIONAL_MESSAGE_NOTIFICATIONS) + .setSmallIcon(R.drawable.ic_notification) + .setContentTitle(context.getString(R.string.FcmFetchManager__you_may_have_messages)) + .setCategory(NotificationCompat.CATEGORY_MESSAGE) + .setContentIntent(PendingIntent.getActivity(context, 0, MainActivity.clearTop(context), mutable())) + .setVibrate(longArrayOf(0)) + .setOnlyAlertOnce(true) + .build() + + NotificationManagerCompat.from(context) + .notify(NotificationIds.MAY_HAVE_MESSAGES_NOTIFICATION_ID, mayHaveMessagesNotification) + } + + private fun cancelMayHaveMessagesNotification(context: Context) { + NotificationManagerCompat.from(context).cancel(NotificationIds.MAY_HAVE_MESSAGES_NOTIFICATION_ID) + } + private fun fetch(context: Context) { + val hasHighPriorityContext = highPriority + val metricId = SignalLocalMetrics.PushWebsocketFetch.startFetch() val success = retrieveMessages(context) - if (!success) { - SignalLocalMetrics.PushWebsocketFetch.onTimedOut(metricId) - } else { + if (success) { SignalLocalMetrics.PushWebsocketFetch.onDrained(metricId) + cancelMayHaveMessagesNotification(context) + } else { + SignalLocalMetrics.PushWebsocketFetch.onTimedOut(metricId) + if (hasHighPriorityContext) { + postMayHaveMessagesNotification(context) + } } synchronized(this) { @@ -72,13 +113,22 @@ object FcmFetchManager { Log.i(TAG, "No more active. Stopping.") context.stopService(Intent(context, FcmFetchBackgroundService::class.java)) FcmFetchForegroundService.stopServiceIfNecessary(context) + highPriority = false } } } @JvmStatic - fun enqueueFetch(context: Context) { + fun onForeground(context: Context) { + cancelMayHaveMessagesNotification(context) + } + + @JvmStatic + fun enqueueFetch(context: Context, highPriority: Boolean) { synchronized(this) { + if (highPriority) { + this.highPriority = true + } val performedReplace = EXECUTOR.enqueue { fetch(context) } if (performedReplace) { Log.i(TAG, "Already have one running and one enqueued. Ignoring.") diff --git a/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmReceiveService.java b/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmReceiveService.java index 550a48b7b9..60a28d9f48 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmReceiveService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmReceiveService.java @@ -74,9 +74,8 @@ public class FcmReceiveService extends FirebaseMessagingService { } private static void handleReceivedNotification(Context context, @Nullable RemoteMessage remoteMessage) { + boolean highPriority = remoteMessage != null && remoteMessage.getPriority() == RemoteMessage.PRIORITY_HIGH; try { - boolean highPriority = remoteMessage != null && remoteMessage.getPriority() == RemoteMessage.PRIORITY_HIGH; - Log.d(TAG, String.format(Locale.US, "[handleReceivedNotification] API: %s, RemoteMessagePriority: %s", Build.VERSION.SDK_INT, remoteMessage != null ? remoteMessage.getPriority() : "n/a")); if (highPriority) { @@ -88,7 +87,7 @@ public class FcmReceiveService extends FirebaseMessagingService { Log.w(TAG, "Failed to start service.", e); } - FcmFetchManager.enqueueFetch(context); + FcmFetchManager.enqueueFetch(context, highPriority); } private static void handleRegistrationPushChallenge(@NonNull String challenge) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java index 2006ca7f3a..3eb9fb0a9b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java @@ -68,17 +68,18 @@ public class NotificationChannels { private static final String CONTACT_PREFIX = "contact_"; private static final String MESSAGES_PREFIX = "messages_"; - public final String CALLS = "calls_v3"; - public final String FAILURES = "failures"; - public final String APP_UPDATES = "app_updates"; - public final String BACKUPS = "backups_v2"; - public final String LOCKED_STATUS = "locked_status_v2"; - public final String OTHER = "other_v3"; - public final String VOICE_NOTES = "voice_notes"; - public final String JOIN_EVENTS = "join_events"; - public final String BACKGROUND = "background_connection"; - public final String CALL_STATUS = "call_status"; - public final String APP_ALERTS = "app_alerts"; + public final String CALLS = "calls_v3"; + public final String FAILURES = "failures"; + public final String APP_UPDATES = "app_updates"; + public final String BACKUPS = "backups_v2"; + public final String LOCKED_STATUS = "locked_status_v2"; + public final String OTHER = "other_v3"; + public final String VOICE_NOTES = "voice_notes"; + public final String JOIN_EVENTS = "join_events"; + public final String BACKGROUND = "background_connection"; + public final String CALL_STATUS = "call_status"; + public final String APP_ALERTS = "app_alerts"; + public final String ADDITIONAL_MESSAGE_NOTIFICATIONS = "additional_message_notifications"; private static volatile NotificationChannels instance; @@ -621,17 +622,18 @@ public class NotificationChannels { NotificationChannelGroup messagesGroup = new NotificationChannelGroup(CATEGORY_MESSAGES, context.getResources().getString(R.string.NotificationChannel_group_chats)); notificationManager.createNotificationChannelGroup(messagesGroup); - NotificationChannel messages = new NotificationChannel(getMessagesChannel(), context.getString(R.string.NotificationChannel_channel_messages), NotificationManager.IMPORTANCE_HIGH); - NotificationChannel calls = new NotificationChannel(CALLS, context.getString(R.string.NotificationChannel_calls), NotificationManager.IMPORTANCE_HIGH); - NotificationChannel failures = new NotificationChannel(FAILURES, context.getString(R.string.NotificationChannel_failures), NotificationManager.IMPORTANCE_HIGH); - NotificationChannel backups = new NotificationChannel(BACKUPS, context.getString(R.string.NotificationChannel_backups), NotificationManager.IMPORTANCE_LOW); - NotificationChannel lockedStatus = new NotificationChannel(LOCKED_STATUS, context.getString(R.string.NotificationChannel_locked_status), NotificationManager.IMPORTANCE_LOW); - NotificationChannel other = new NotificationChannel(OTHER, context.getString(R.string.NotificationChannel_other), NotificationManager.IMPORTANCE_LOW); - NotificationChannel voiceNotes = new NotificationChannel(VOICE_NOTES, context.getString(R.string.NotificationChannel_voice_notes), NotificationManager.IMPORTANCE_LOW); - NotificationChannel joinEvents = new NotificationChannel(JOIN_EVENTS, context.getString(R.string.NotificationChannel_contact_joined_signal), NotificationManager.IMPORTANCE_DEFAULT); - NotificationChannel background = new NotificationChannel(BACKGROUND, context.getString(R.string.NotificationChannel_background_connection), getDefaultBackgroundChannelImportance(notificationManager)); - NotificationChannel callStatus = new NotificationChannel(CALL_STATUS, context.getString(R.string.NotificationChannel_call_status), NotificationManager.IMPORTANCE_LOW); - NotificationChannel appAlerts = new NotificationChannel(APP_ALERTS, context.getString(R.string.NotificationChannel_critical_app_alerts), NotificationManager.IMPORTANCE_HIGH); + NotificationChannel messages = new NotificationChannel(getMessagesChannel(), context.getString(R.string.NotificationChannel_channel_messages), NotificationManager.IMPORTANCE_HIGH); + NotificationChannel calls = new NotificationChannel(CALLS, context.getString(R.string.NotificationChannel_calls), NotificationManager.IMPORTANCE_HIGH); + NotificationChannel failures = new NotificationChannel(FAILURES, context.getString(R.string.NotificationChannel_failures), NotificationManager.IMPORTANCE_HIGH); + NotificationChannel backups = new NotificationChannel(BACKUPS, context.getString(R.string.NotificationChannel_backups), NotificationManager.IMPORTANCE_LOW); + NotificationChannel lockedStatus = new NotificationChannel(LOCKED_STATUS, context.getString(R.string.NotificationChannel_locked_status), NotificationManager.IMPORTANCE_LOW); + NotificationChannel other = new NotificationChannel(OTHER, context.getString(R.string.NotificationChannel_other), NotificationManager.IMPORTANCE_LOW); + NotificationChannel voiceNotes = new NotificationChannel(VOICE_NOTES, context.getString(R.string.NotificationChannel_voice_notes), NotificationManager.IMPORTANCE_LOW); + NotificationChannel joinEvents = new NotificationChannel(JOIN_EVENTS, context.getString(R.string.NotificationChannel_contact_joined_signal), NotificationManager.IMPORTANCE_DEFAULT); + NotificationChannel background = new NotificationChannel(BACKGROUND, context.getString(R.string.NotificationChannel_background_connection), getDefaultBackgroundChannelImportance(notificationManager)); + NotificationChannel callStatus = new NotificationChannel(CALL_STATUS, context.getString(R.string.NotificationChannel_call_status), NotificationManager.IMPORTANCE_LOW); + NotificationChannel appAlerts = new NotificationChannel(APP_ALERTS, context.getString(R.string.NotificationChannel_critical_app_alerts), NotificationManager.IMPORTANCE_HIGH); + NotificationChannel additionalMessageNotifications = new NotificationChannel(ADDITIONAL_MESSAGE_NOTIFICATIONS, context.getString(R.string.NotificationChannel_additional_message_notifications), NotificationManager.IMPORTANCE_HIGH); messages.setGroup(CATEGORY_MESSAGES); setVibrationEnabled(messages, SignalStore.settings().isMessageVibrateEnabled()); @@ -649,7 +651,7 @@ public class NotificationChannels { callStatus.setShowBadge(false); appAlerts.setShowBadge(false); - notificationManager.createNotificationChannels(Arrays.asList(messages, calls, failures, backups, lockedStatus, other, voiceNotes, joinEvents, background, callStatus, appAlerts)); + notificationManager.createNotificationChannels(Arrays.asList(messages, calls, failures, backups, lockedStatus, other, voiceNotes, joinEvents, background, callStatus, appAlerts, additionalMessageNotifications)); if (BuildConfig.PLAY_STORE_DISABLED) { NotificationChannel appUpdates = new NotificationChannel(APP_UPDATES, context.getString(R.string.NotificationChannel_app_updates), NotificationManager.IMPORTANCE_DEFAULT); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationIds.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationIds.java index 92b4f6d65e..bad7fc4396 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationIds.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationIds.java @@ -6,25 +6,26 @@ import org.thoughtcrime.securesms.notifications.v2.ConversationId; public final class NotificationIds { - public static final int FCM_FAILURE = 12; - public static final int PENDING_MESSAGES = 1111; - public static final int MESSAGE_SUMMARY = 1338; - public static final int APPLICATION_MIGRATION = 4242; - public static final int SMS_IMPORT_COMPLETE = 31337; - public static final int PRE_REGISTRATION_SMS = 5050; - public static final int THREAD = 50000; - public static final int INTERNAL_ERROR = 258069; - public static final int LEGACY_SQLCIPHER_MIGRATION = 494949; - public static final int USER_NOTIFICATION_MIGRATION = 525600; - public static final int DEVICE_TRANSFER = 625420; - public static final int DONOR_BADGE_FAILURE = 630001; - public static final int FCM_FETCH = 630002; - public static final int SMS_EXPORT_SERVICE = 630003; - public static final int SMS_EXPORT_COMPLETE = 630004; - public static final int STORY_THREAD = 700000; - public static final int MESSAGE_DELIVERY_FAILURE = 800000; - public static final int STORY_MESSAGE_DELIVERY_FAILURE = 900000; - public static final int UNREGISTERED_NOTIFICATION_ID = 20230102; + public static final int FCM_FAILURE = 12; + public static final int PENDING_MESSAGES = 1111; + public static final int MESSAGE_SUMMARY = 1338; + public static final int APPLICATION_MIGRATION = 4242; + public static final int SMS_IMPORT_COMPLETE = 31337; + public static final int MAY_HAVE_MESSAGES_NOTIFICATION_ID = 31365; + public static final int PRE_REGISTRATION_SMS = 5050; + public static final int THREAD = 50000; + public static final int INTERNAL_ERROR = 258069; + public static final int LEGACY_SQLCIPHER_MIGRATION = 494949; + public static final int USER_NOTIFICATION_MIGRATION = 525600; + public static final int DEVICE_TRANSFER = 625420; + public static final int DONOR_BADGE_FAILURE = 630001; + public static final int FCM_FETCH = 630002; + public static final int SMS_EXPORT_SERVICE = 630003; + public static final int SMS_EXPORT_COMPLETE = 630004; + public static final int STORY_THREAD = 700000; + public static final int MESSAGE_DELIVERY_FAILURE = 800000; + public static final int STORY_MESSAGE_DELIVERY_FAILURE = 900000; + public static final int UNREGISTERED_NOTIFICATION_ID = 20230102; private NotificationIds() { } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java index 05b773350e..8ce381294a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java @@ -108,7 +108,7 @@ public final class FeatureFlags { private static final String SVR2_KILLSWITCH = "android.svr2.killSwitch"; private static final String CDS_COMPAT_MODE = "global.cds.return_acis_without_uaks"; private static final String CONVERSATION_FRAGMENT_V2 = "android.conversationFragmentV2.2"; - + private static final String FCM_MAY_HAVE_MESSAGES_KILL_SWITCH = "android.fcmNotificationFallbackKillSwitch"; private static final String SAFETY_NUMBER_ACI = "global.safetyNumberAci"; /** * We will only store remote values for flags in this set. If you want a flag to be controllable @@ -169,7 +169,8 @@ public final class FeatureFlags { SVR2_KILLSWITCH, CDS_COMPAT_MODE, CONVERSATION_FRAGMENT_V2, - SAFETY_NUMBER_ACI + SAFETY_NUMBER_ACI, + FCM_MAY_HAVE_MESSAGES_KILL_SWITCH ); @VisibleForTesting @@ -236,7 +237,8 @@ public final class FeatureFlags { SVR2_KILLSWITCH, CDS_COMPAT_MODE, CONVERSATION_FRAGMENT_V2, - SAFETY_NUMBER_ACI + SAFETY_NUMBER_ACI, + FCM_MAY_HAVE_MESSAGES_KILL_SWITCH ); /** @@ -245,7 +247,8 @@ public final class FeatureFlags { @VisibleForTesting static final Set STICKY = SetUtil.newHashSet( VERIFY_V2, - SVR2_KILLSWITCH + SVR2_KILLSWITCH, + FCM_MAY_HAVE_MESSAGES_KILL_SWITCH ); /** @@ -573,6 +576,13 @@ public final class FeatureFlags { return getBoolean(ANY_ADDRESS_PORTS_KILL_SWITCH, false); } + /** + * Enable/disable for notification when we cannot fetch messages despite receiving an urgent push. + */ + public static boolean fcmMayHaveMessagesNotificationKillSwitch() { + return getBoolean(FCM_MAY_HAVE_MESSAGES_KILL_SWITCH, false); + } + public static boolean editMessageSending() { return getBoolean(EDIT_MESSAGE_SEND, false); } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e0e010c43f..3a47aee9e9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -106,6 +106,10 @@ Checking for messages… + + + You may have new messages + Blocked users Add blocked user @@ -2267,6 +2271,8 @@ Call status Critical app alerts + + Additional message notifications