From 4fe5ff41f30f2ea2850ea73880c4e4102813fe4c Mon Sep 17 00:00:00 2001 From: Oscar Mira Date: Fri, 11 Oct 2024 07:39:18 +0200 Subject: [PATCH] Add FCM or WebSocket option in notification settings --- .../securesms/ApplicationContext.java | 43 +++++++----- .../app/internal/InternalSettingsFragment.kt | 21 ------ .../app/internal/InternalSettingsViewModel.kt | 5 -- .../NotificationsSettingsFragment.kt | 69 +++++++++++++++++++ .../NotificationsSettingsState.kt | 6 +- .../NotificationsSettingsViewModel.kt | 18 ++++- .../securesms/events/PushServiceEvent.kt | 3 + .../securesms/jobs/FcmRefreshJob.java | 23 +++++-- .../securesms/keyvalue/AccountValues.kt | 5 +- .../securesms/keyvalue/InternalValues.java | 7 +- .../securesms/keyvalue/SettingsValues.java | 29 ++++++++ .../messages/IncomingMessageObserver.kt | 6 +- .../securesms/util/PlayServicesUtil.java | 4 +- app/src/main/res/values/strings2.xml | 5 ++ 14 files changed, 183 insertions(+), 61 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/events/PushServiceEvent.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 4dca518bd4..8d75471a25 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -27,6 +27,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.annotation.WorkerThread; +import androidx.core.app.NotificationManagerCompat; import androidx.core.content.ContextCompat; import com.bumptech.glide.Glide; @@ -89,6 +90,7 @@ import org.thoughtcrime.securesms.mms.SignalGlideModule; import org.thoughtcrime.securesms.net.NetworkManager; import org.thoughtcrime.securesms.notifications.MessageNotifier; +import org.thoughtcrime.securesms.notifications.NotificationIds; import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.ratelimit.RateLimitUtil; import org.thoughtcrime.securesms.recipients.Recipient; @@ -111,7 +113,6 @@ import org.thoughtcrime.securesms.util.RemoteConfig; import org.thoughtcrime.securesms.util.FileUtils; import org.thoughtcrime.securesms.util.PlayServicesUtil; -import org.thoughtcrime.securesms.util.RemoteConfig; import org.thoughtcrime.securesms.util.SignalLocalMetrics; import org.thoughtcrime.securesms.util.SignalUncaughtExceptionHandler; import org.thoughtcrime.securesms.util.StorageUtil; @@ -201,7 +202,7 @@ private void onCreateUnlock() { .addBlocking("scrubber", () -> Scrubber.setIdentifierHmacKeyProvider(() -> SignalStore.svr().getOrCreateMasterKey().deriveLoggingKey())) .addBlocking("network-settings", this::initializeNetworkSettings) .addBlocking("first-launch", this::initializeFirstEverAppLaunch) - .addBlocking("gcm-check", this::initializeFcmCheck) + .addBlocking("push", this::updatePushNotificationServices) .addBlocking("app-migrations", this::initializeApplicationMigrations) .addBlocking("lifecycle-observer", () -> AppForegroundObserver.addListener(this)) .addBlocking("message-retriever", this::initializeMessageRetrieval) @@ -427,11 +428,11 @@ private void initializeApplicationMigrations() { ApplicationMigrations.onApplicationCreate(this, AppDependencies.getJobManager()); } - public void initializeMessageRetrieval() { + private void initializeMessageRetrieval() { AppDependencies.getIncomingMessageObserver(); } - public void finalizeMessageRetrieval() { + private void finalizeMessageRetrieval() { AppDependencies.resetNetwork(false); } @@ -495,31 +496,29 @@ private void initializeNetworkSettings() { } } - private void initializeFcmCheck() { + @MainThread + public void updatePushNotificationServices() { if (!SignalStore.account().isRegistered()) { return; } PlayServicesUtil.PlayServicesStatus fcmStatus = PlayServicesUtil.getPlayServicesStatus(this); - if (fcmStatus == PlayServicesUtil.PlayServicesStatus.DISABLED) { - if (SignalStore.account().isFcmEnabled()) { + boolean fcmEnabled = SignalStore.account().isFcmEnabled(); + boolean forceWebSocket = SignalStore.internal().isWebsocketModeForced(); + + if (forceWebSocket || fcmStatus == PlayServicesUtil.PlayServicesStatus.DISABLED) { + if (fcmEnabled) { Log.i(TAG, "Play Services are disabled. Disabling FCM."); - SignalStore.account().setFcmEnabled(false); - SignalStore.account().setFcmToken(null); - SignalStore.account().setFcmTokenLastSetTime(-1); - AppDependencies.getJobManager().add(new RefreshAttributesJob()); + updateFcmStatus(false); } else { + Log.d(TAG, "FCM is disabled."); SignalStore.account().setFcmTokenLastSetTime(-1); } - } else if (fcmStatus == PlayServicesUtil.PlayServicesStatus.SUCCESS && - !SignalStore.account().isFcmEnabled() && + } else if (fcmStatus == PlayServicesUtil.PlayServicesStatus.SUCCESS && !fcmEnabled && SignalStore.account().getFcmTokenLastSetTime() < 0) { Log.i(TAG, "Play Services are newly-available. Updating to use FCM."); - SignalStore.account().setFcmEnabled(true); - AppDependencies.getJobManager().startChain(new FcmRefreshJob()) - .then(new RefreshAttributesJob()) - .enqueue(); + updateFcmStatus(true); } else { long lastSetTime = SignalStore.account().getFcmTokenLastSetTime(); long nextSetTime = lastSetTime + TimeUnit.HOURS.toMillis(6); @@ -537,6 +536,16 @@ private void initializeFcmCheck() { } } + private void updateFcmStatus(boolean fcmEnabled) { + SignalStore.account().setFcmEnabled(fcmEnabled); + if (!fcmEnabled) { + NotificationManagerCompat.from(this).cancel(NotificationIds.FCM_FAILURE); + } + AppDependencies.getJobManager().startChain(new FcmRefreshJob()) + .then(new RefreshAttributesJob()) + .enqueue(); + } + private void initializeExpiringMessageManager() { AppDependencies.getExpiringMessageManager().checkSchedule(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt index 3682768911..676a403ed8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt @@ -59,8 +59,6 @@ import java.util.UUID import java.util.concurrent.TimeUnit import kotlin.math.max import kotlin.random.Random -import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.Duration.Companion.seconds class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__internal_preferences) { @@ -340,25 +338,6 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter sectionHeaderPref(DSLSettingsText.from("Network")) - switchPref( - title = DSLSettingsText.from("Force websocket mode"), - summary = DSLSettingsText.from("Pretend you have no Play Services. Ignores websocket messages and keeps the websocket open in a foreground service. You have to manually force-stop the app for changes to take effect."), - isChecked = state.forceWebsocketMode, - onClick = { - viewModel.setForceWebsocketMode(!state.forceWebsocketMode) - SimpleTask.run({ - val jobState = AppDependencies.jobManager.runSynchronously(RefreshAttributesJob(), 10.seconds.inWholeMilliseconds) - return@run jobState.isPresent && jobState.get().isComplete - }, { success -> - if (success) { - Toast.makeText(context, "Successfully refreshed attributes. Force-stop the app for changes to take effect.", Toast.LENGTH_SHORT).show() - } else { - Toast.makeText(context, "Failed to refresh attributes.", Toast.LENGTH_SHORT).show() - } - }) - } - ) - switchPref( title = DSLSettingsText.from("Allow censorship circumvention toggle"), summary = DSLSettingsText.from("Allow changing the censorship circumvention toggle regardless of network connectivity."), diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsViewModel.kt index b627765cc3..50c662cf38 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsViewModel.kt @@ -50,11 +50,6 @@ class InternalSettingsViewModel(private val repository: InternalSettingsReposito refresh() } - fun setForceWebsocketMode(enabled: Boolean) { - preferenceDataStore.putBoolean(InternalValues.FORCE_WEBSOCKET_MODE, enabled) - refresh() - } - fun setUseBuiltInEmoji(enabled: Boolean) { preferenceDataStore.putBoolean(InternalValues.FORCE_BUILT_IN_EMOJI, enabled) refresh() diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsFragment.kt index ce38f777c0..0dd3e7b4c3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsFragment.kt @@ -19,12 +19,17 @@ import androidx.core.content.ContextCompat import androidx.lifecycle.ViewModelProvider import androidx.navigation.fragment.findNavController import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode import org.signal.core.util.getParcelableExtraCompat import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.BuildConfig import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.PromptBatterySaverDialogFragment import org.thoughtcrime.securesms.components.settings.DSLConfiguration import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment +import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon import org.thoughtcrime.securesms.components.settings.DSLSettingsText import org.thoughtcrime.securesms.components.settings.PreferenceModel import org.thoughtcrime.securesms.components.settings.PreferenceViewHolder @@ -32,10 +37,14 @@ import org.thoughtcrime.securesms.components.settings.RadioListPreference import org.thoughtcrime.securesms.components.settings.RadioListPreferenceViewHolder import org.thoughtcrime.securesms.components.settings.configure import org.thoughtcrime.securesms.components.settings.models.Banner +import org.thoughtcrime.securesms.conversation.v2.registerForLifecycle +import org.thoughtcrime.securesms.events.PushServiceEvent +import org.thoughtcrime.securesms.keyvalue.SettingsValues.NotificationDeliveryMethod import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.notifications.NotificationChannels import org.thoughtcrime.securesms.notifications.TurnOnNotificationsBottomSheet import org.thoughtcrime.securesms.util.BottomSheetUtil +import org.thoughtcrime.securesms.util.PlayServicesUtil import org.thoughtcrime.securesms.util.RingtoneUtil import org.thoughtcrime.securesms.util.SecurePreferenceManager import org.thoughtcrime.securesms.util.ViewUtil @@ -64,6 +73,11 @@ class NotificationsSettingsFragment : DSLSettingsFragment(R.string.preferences__ private val ledBlinkValues by lazy { resources.getStringArray(R.array.pref_led_blink_pattern_values) } private val ledBlinkLabels by lazy { resources.getStringArray(R.array.pref_led_blink_pattern_entries) } + private val notificationMethodValues = NotificationDeliveryMethod.entries.filterNot { method -> + method == NotificationDeliveryMethod.FCM && !BuildConfig.USE_PLAY_SERVICES + } + private val notificationMethodLabels by lazy { notificationMethodValues.map { resources.getString(it.stringId) }.toTypedArray() } + private lateinit var viewModel: NotificationsSettingsViewModel override fun onResume() { @@ -97,6 +111,13 @@ class NotificationsSettingsFragment : DSLSettingsFragment(R.string.preferences__ viewModel.state.observe(viewLifecycleOwner) { adapter.submitList(getConfiguration(it).toMappingModelList()) } + + EventBus.getDefault().registerForLifecycle(subscriber = this, lifecycleOwner = viewLifecycleOwner) + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onPushServiceEvent(event: PushServiceEvent) { + viewModel.refresh() } private fun getConfiguration(state: NotificationsSettingsState): DSLConfiguration { @@ -314,6 +335,54 @@ class NotificationsSettingsFragment : DSLSettingsFragment(R.string.preferences__ viewModel.setNotifyWhenContactJoinsSignal(!state.notifyWhenContactJoinsSignal) } ) + + dividerPref() + + sectionHeaderPref(R.string.NotificationsSettingsFragment__push_notifications) + + textPref( + summary = DSLSettingsText.from(R.string.NotificationsSettingsFragment__preferred_method_for_receiving_notifications_from_the_signal_service) + ) + + val showAlertIcon = when (state.preferredNotificationMethod) { + NotificationDeliveryMethod.FCM -> !state.canReceiveFcm + NotificationDeliveryMethod.WEBSOCKET -> false + } + radioListPref( + title = DSLSettingsText.from(R.string.NotificationsSettingsFragment__delivery_method), + listItems = notificationMethodLabels, + selected = notificationMethodValues.indexOf(state.preferredNotificationMethod), + isEnabled = !state.isLinkedDevice, // MOLLY: TODO + iconEnd = if (showAlertIcon) DSLSettingsIcon.from(R.drawable.ic_alert) else null, + onSelected = { + onNotificationMethodChanged(notificationMethodValues[it], state.preferredNotificationMethod) + } + ) + } + } + + private fun onNotificationMethodChanged( + method: NotificationDeliveryMethod, + previousMethod: NotificationDeliveryMethod + ) { + when (method) { + NotificationDeliveryMethod.FCM -> { + viewModel.setPreferredNotificationMethod(method) + val msgId = when (viewModel.fcmState) { + PlayServicesUtil.PlayServicesStatus.SUCCESS -> null + PlayServicesUtil.PlayServicesStatus.DISABLED -> R.string.RegistrationActivity_missing_google_play_services + PlayServicesUtil.PlayServicesStatus.MISSING -> R.string.RegistrationActivity_missing_google_play_services + PlayServicesUtil.PlayServicesStatus.NEEDS_UPDATE -> R.string.RegistrationActivity_google_play_services_is_updating_or_unavailable + PlayServicesUtil.PlayServicesStatus.TRANSIENT_ERROR -> R.string.RegistrationActivity_play_services_error + } + if (msgId != null) { + Toast.makeText(requireContext(), getString(msgId), Toast.LENGTH_LONG).show() + } + } + + NotificationDeliveryMethod.WEBSOCKET -> { + viewModel.setPreferredNotificationMethod(method) + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsState.kt index c090260f75..24f958ce9f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsState.kt @@ -1,13 +1,17 @@ package org.thoughtcrime.securesms.components.settings.app.notifications import android.net.Uri +import org.thoughtcrime.securesms.keyvalue.SettingsValues.NotificationDeliveryMethod data class NotificationsSettingsState( val messageNotificationsState: MessageNotificationsState, val callNotificationsState: CallNotificationsState, val notifyWhileLocked: Boolean, val canEnableNotifyWhileLocked: Boolean, - val notifyWhenContactJoinsSignal: Boolean + val notifyWhenContactJoinsSignal: Boolean, + val isLinkedDevice: Boolean, + val preferredNotificationMethod: NotificationDeliveryMethod, + val canReceiveFcm: Boolean ) data class MessageNotificationsState( diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsViewModel.kt index 6e4269a5b7..7c7996a281 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsViewModel.kt @@ -6,12 +6,16 @@ import android.os.Build import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider + +import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.dependencies.AppDependencies +import org.thoughtcrime.securesms.keyvalue.SettingsValues.NotificationDeliveryMethod import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.notifications.DeviceSpecificNotificationConfig import org.thoughtcrime.securesms.notifications.NotificationChannels import org.thoughtcrime.securesms.notifications.SlowNotificationHeuristics import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference +import org.thoughtcrime.securesms.util.PlayServicesUtil import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.livedata.Store @@ -108,6 +112,15 @@ class NotificationsSettingsViewModel(private val sharedPreferences: SharedPrefer refresh() } + fun setPreferredNotificationMethod(method: NotificationDeliveryMethod) { + SignalStore.settings.preferredNotificationMethod = method + ApplicationContext.getInstance().updatePushNotificationServices() + AppDependencies.resetNetwork(true) + refresh() + } + + val fcmState get() = PlayServicesUtil.getPlayServicesStatus(AppDependencies.application) + /** * @param currentState If provided and [calculateSlowNotifications] = false, then we will copy the slow notification state from it * @param calculateSlowNotifications If true, calculate the true slow notification state (this is not main-thread safe). Otherwise, it will copy from @@ -142,7 +155,10 @@ class NotificationsSettingsViewModel(private val sharedPreferences: SharedPrefer ), notifyWhileLocked = TextSecurePreferences.isPassphraseLockNotificationsEnabled(AppDependencies.application) && SignalStore.account.pushAvailable, canEnableNotifyWhileLocked = SignalStore.account.pushAvailable, - notifyWhenContactJoinsSignal = SignalStore.settings.isNotifyWhenContactJoinsSignal + notifyWhenContactJoinsSignal = SignalStore.settings.isNotifyWhenContactJoinsSignal, + isLinkedDevice = SignalStore.account.isLinkedDevice, + preferredNotificationMethod = SignalStore.settings.preferredNotificationMethod, + canReceiveFcm = SignalStore.account.canReceiveFcm, ) private fun canEnableNotifications(): Boolean { diff --git a/app/src/main/java/org/thoughtcrime/securesms/events/PushServiceEvent.kt b/app/src/main/java/org/thoughtcrime/securesms/events/PushServiceEvent.kt new file mode 100644 index 0000000000..f25eb309b0 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/events/PushServiceEvent.kt @@ -0,0 +1,3 @@ +package org.thoughtcrime.securesms.events + +data object PushServiceEvent diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/FcmRefreshJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/FcmRefreshJob.java index 5d7e3c1a93..8c69c6a4dc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/FcmRefreshJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/FcmRefreshJob.java @@ -29,11 +29,13 @@ import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GoogleApiAvailability; +import org.greenrobot.eventbus.EventBus; import org.signal.core.util.PendingIntentFlags; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.PlayServicesProblemActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.dependencies.AppDependencies; +import org.thoughtcrime.securesms.events.PushServiceEvent; import org.thoughtcrime.securesms.gcm.FcmUtil; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; @@ -50,12 +52,13 @@ public class FcmRefreshJob extends BaseJob { public static final String KEY = "FcmRefreshJob"; + public static final String QUEUE_KEY = KEY; private static final String TAG = Log.tag(FcmRefreshJob.class); public FcmRefreshJob() { this(new Job.Parameters.Builder() - .setQueue("FcmRefreshJob") + .setQueue(QUEUE_KEY) .addConstraint(NetworkConstraint.KEY) .setMaxAttempts(3) .setLifespan(TimeUnit.HOURS.toMillis(6)) @@ -79,7 +82,17 @@ private FcmRefreshJob(@NonNull Job.Parameters parameters) { @Override public void onRun() throws Exception { - if (!SignalStore.account().isFcmEnabled()) return; + String oldToken = SignalStore.account().getFcmToken(); + + if (!SignalStore.account().isFcmEnabled()) { + if (oldToken != null) { + Log.i(TAG, "FCM not allowed: clearing existing token..."); + AppDependencies.getSignalServiceAccountManager().setGcmId(Optional.empty()); + SignalStore.account().setFcmToken(null); + SignalStore.account().setFcmTokenLastSetTime(-1); + } + return; + } Log.i(TAG, "Reregistering FCM..."); @@ -91,8 +104,6 @@ public void onRun() throws Exception { Optional token = FcmUtil.getToken(context); if (token.isPresent()) { - String oldToken = SignalStore.account().getFcmToken(); - if (!token.get().equals(oldToken)) { int oldLength = oldToken != null ? oldToken.length() : -1; Log.i(TAG, "Token changed. oldLength: " + oldLength + " newLength: " + token.get().length()); @@ -102,6 +113,10 @@ public void onRun() throws Exception { AppDependencies.getSignalServiceAccountManager().setGcmId(token); SignalStore.account().setFcmToken(token.get()); + if (oldToken == null) { + AppDependencies.resetNetwork(true); + } + EventBus.getDefault().post(PushServiceEvent.INSTANCE); } else { throw new RetryLaterException(new IOException("Failed to retrieve a token.")); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/AccountValues.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/AccountValues.kt index 236929c0c4..83013d578f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/AccountValues.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/AccountValues.kt @@ -312,9 +312,12 @@ class AccountValues internal constructor(store: KeyValueStore, context: Context) @get:JvmName("isFcmEnabled") var fcmEnabled: Boolean by booleanValue(KEY_FCM_ENABLED, false) + val canReceiveFcm: Boolean + get() = fcmEnabled && fcmToken != null + @get:JvmName("isPushAvailable") val pushAvailable: Boolean - get() = fcmEnabled + get() = canReceiveFcm /** The FCM token, which allows the server to send us FCM messages. */ var fcmToken: String? diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.java index a02b204ef8..10b5807183 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.java @@ -26,7 +26,6 @@ public final class InternalValues extends SignalStoreValues { public static final String CALLING_DISABLE_TELECOM = "internal.calling_disable_telecom"; public static final String CALLING_ENABLE_OBOE_ADM = "internal.calling_enable_oboe_adm"; public static final String DISABLE_STORAGE_SERVICE = "internal.disable_storage_service"; - public static final String FORCE_WEBSOCKET_MODE = "internal.force_websocket_mode"; public static final String LAST_SCROLL_POSITION = "internal.last_scroll_position"; public static final String CONVERSATION_ITEM_V2_MEDIA = "internal.conversation_item_v2_media"; public static final String FORCE_ENTER_RESTORE_V2_FLOW = "internal.force_enter_restore_v2_flow"; @@ -170,11 +169,7 @@ public synchronized boolean callingEnableOboeAdm() { * Whether or not the system is forced to be in 'websocket mode', where FCM is ignored and we use a foreground service to keep the app alive. */ public boolean isWebsocketModeForced() { - if (RemoteConfig.internalUser()) { - return getBoolean(FORCE_WEBSOCKET_MODE, false); - } else { - return false; - } + return SignalStore.settings().getPreferredNotificationMethod() == SettingsValues.NotificationDeliveryMethod.WEBSOCKET; } public void setHevcEncoding(boolean enabled) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java index 8f3c3b2e24..993915d4dc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java @@ -7,6 +7,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.StringRes; import androidx.lifecycle.LiveData; import org.signal.core.util.logging.Log; @@ -67,6 +68,7 @@ public final class SettingsValues extends SignalStoreValues { private static final String KEEP_MUTED_CHATS_ARCHIVED = "settings.keepMutedChatsArchived"; private static final String USE_COMPACT_NAVIGATION_BAR = "settings.useCompactNavigationBar"; private static final String THREAD_TRIM_SYNC_TO_LINKED_DEVICES = "settings.storage.syncThreadTrimDeletes"; + private static final String MOLLY_NOTIFICATION_METHOD = "molly.notificationMethod"; public static final int BACKUP_DEFAULT_HOUR = 2; public static final int BACKUP_DEFAULT_MINUTE = 0; @@ -476,6 +478,14 @@ public boolean getUseCompactNavigationBar() { return getBoolean(USE_COMPACT_NAVIGATION_BAR, false); } + public NotificationDeliveryMethod getPreferredNotificationMethod() { + return NotificationDeliveryMethod.deserialize(getString(MOLLY_NOTIFICATION_METHOD, NotificationDeliveryMethod.FCM.serialize())); + } + + public void setPreferredNotificationMethod(NotificationDeliveryMethod method) { + putString(MOLLY_NOTIFICATION_METHOD, method.serialize()); + } + private @Nullable Uri getUri(@NonNull String key) { String uri = getString(key, ""); @@ -539,4 +549,23 @@ public enum Theme { } } } + + public enum NotificationDeliveryMethod { + FCM, WEBSOCKET; + + public @NonNull String serialize() { + return name(); + } + + public static @NonNull NotificationDeliveryMethod deserialize(@NonNull String value) { + return NotificationDeliveryMethod.valueOf(value); + } + + public @StringRes int getStringId() { + return switch (this) { + case FCM -> R.string.NotificationDeliveryMethod__fcm; + case WEBSOCKET -> R.string.NotificationDeliveryMethod__websocket; + }; + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.kt index 3d59b5efc4..91c32e4955 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.kt @@ -192,7 +192,6 @@ class IncomingMessageObserver(private val context: Application) { } val registered = SignalStore.account.isRegistered - val fcmEnabled = SignalStore.account.fcmEnabled val pushAvailable = SignalStore.account.pushAvailable val hasNetwork = NetworkConstraint.isMet(context) val hasProxy = AppDependencies.networkManager.isProxyEnabled @@ -223,8 +222,7 @@ class IncomingMessageObserver(private val context: Application) { val needsConnectionString = if (conclusion) "Needs Connection" else "Does Not Need Connection" - Log.d(TAG, "[$needsConnectionString] Network: $hasNetwork, Foreground: $appVisibleSnapshot, Time Since Last Interaction: $lastInteractionString, FCM: $fcmEnabled, Stay open requests: $keepAliveEntries, Registered: $registered, Proxy: $hasProxy, Force websocket: $forceWebsocket" + - "PushAvailable: $pushAvailable") + Log.d(TAG, "[$needsConnectionString] Network: $hasNetwork, Foreground: $appVisibleSnapshot, Time Since Last Interaction: $lastInteractionString, PushAvailable: $pushAvailable, Stay open requests: $keepAliveEntries, Registered: $registered, Proxy: $hasProxy, Force websocket: $forceWebsocket") return conclusion } @@ -376,7 +374,7 @@ class IncomingMessageObserver(private val context: Application) { Log.i(TAG, "Initializing! (${this.hashCode()})") uncaughtExceptionHandler = this - sleepTimer = if (!SignalStore.account.fcmEnabled || SignalStore.internal.isWebsocketModeForced) AlarmSleepTimer(context) else UptimeSleepTimer() + sleepTimer = if (!SignalStore.account.pushAvailable || SignalStore.internal.isWebsocketModeForced) AlarmSleepTimer(context) else UptimeSleepTimer() } override fun run() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/PlayServicesUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/PlayServicesUtil.java index b7ab9f0842..97de9e0a15 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/PlayServicesUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/PlayServicesUtil.java @@ -5,6 +5,8 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import androidx.annotation.NonNull; + import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GoogleApiAvailability; @@ -22,7 +24,7 @@ public enum PlayServicesStatus { TRANSIENT_ERROR } - public static PlayServicesStatus getPlayServicesStatus(Context context) { + public static @NonNull PlayServicesStatus getPlayServicesStatus(Context context) { int gcmStatus = 0; try { diff --git a/app/src/main/res/values/strings2.xml b/app/src/main/res/values/strings2.xml index bd0bd74758..65c2b5a43a 100644 --- a/app/src/main/res/values/strings2.xml +++ b/app/src/main/res/values/strings2.xml @@ -124,4 +124,9 @@ Once linked, new messages sync across devices, but previous message history will not be transferred. Molly supports linking to other Android devices running Molly, as well as desktops and iPads running Signal. For Android devices: Visit %s to install Molly. + Push notifications + Preferred method for receiving notifications from the Signal service. If your selected method is unavailable, the app will automatically switch to WebSocket. + Delivery method + Google Services (FCM) + WebSocket