diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index dd6a02e9ab..7afc0acef2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -91,6 +91,7 @@ import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger; import org.thoughtcrime.securesms.logging.PersistentLogger; import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogActivity; import org.thoughtcrime.securesms.messageprocessingalarm.RoutineMessageFetchReceiver; +import org.thoughtcrime.securesms.messages.IncomingMessageObserver; import org.thoughtcrime.securesms.migrations.ApplicationMigrations; import org.thoughtcrime.securesms.mms.SignalGlideModule; import org.thoughtcrime.securesms.providers.BlobProvider; @@ -454,22 +455,27 @@ public class ApplicationContext extends Application implements AppForegroundObse PlayServicesUtil.PlayServicesStatus playServicesStatus = PlayServicesUtil.getPlayServicesStatus(this); if (playServicesStatus == PlayServicesUtil.PlayServicesStatus.SUCCESS && !SignalStore.account().isFcmEnabled()) { - Log.i(TAG, "Play Services are newly-available. Enabling FCM and updating server."); + Log.w(TAG, "Play Services are newly-available. Enabling FCM and updating server."); SignalStore.account().setFcmEnabled(true); AppDependencies.getJobManager().startChain(new FcmRefreshJob()) .then(new RefreshAttributesJob()) .enqueue(); + AppDependencies.resetNetwork(); + AppDependencies.startNetwork(); + IncomingMessageObserver.stopForegroundService(this); } else if (playServicesStatus == PlayServicesUtil.PlayServicesStatus.MISSING && SignalStore.account().isFcmEnabled()) { - Log.w(TAG, "Play Services are no longer available. Disabling FCM and updating server."); - SignalStore.account().setFcmEnabled(false); - SignalStore.account().setFcmToken(null); - AppDependencies.getJobManager().add(new RefreshAttributesJob()); + Log.w(TAG, "Play Services are no longer available. Attempting to get an FCM token anyway."); + AppDependencies.getJobManager().add(new FcmRefreshJob()); + } else if (playServicesStatus == PlayServicesUtil.PlayServicesStatus.MISSING && (System.currentTimeMillis() - SignalStore.misc().getLastMissingPlayServicesFcmVerificationTime()) > TimeUnit.DAYS.toMillis(3)) { + Log.i(TAG, "Play Services are unavailable, but it's been long enough that we should check and see if we can get an FCM token anyway."); + AppDependencies.getJobManager().add(new FcmRefreshJob()); } else if (SignalStore.account().isFcmEnabled()) { long lastSetTime = SignalStore.account().getFcmTokenLastSetTime(); long nextSetTime = lastSetTime + TimeUnit.HOURS.toMillis(6); long now = System.currentTimeMillis(); if (SignalStore.account().getFcmToken() == null || nextSetTime <= now || lastSetTime > now) { + Log.i(TAG, "Time for routine FCM token refresh."); AppDependencies.getJobManager().add(new FcmRefreshJob()); } } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsFragment.kt index 0160d557ea..34113a9c11 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsFragment.kt @@ -17,6 +17,7 @@ import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager import org.signal.core.ui.compose.ComposeFragment import org.signal.core.ui.compose.DayNightPreviews +import org.signal.core.ui.compose.Dialogs import org.signal.core.ui.compose.Dividers import org.signal.core.ui.compose.Previews import org.signal.core.ui.compose.Rows @@ -90,6 +91,18 @@ class DataAndStorageSettingsFragment : ComposeFragment() { override fun onRoamingDataAutoDownloadSelectionChanged(selection: Array) { viewModel.setRoamingAutoDownloadValues(selection.toSet()) } + + override fun onForceWebsocketModeChanged(enabled: Boolean) { + viewModel.onForceWebsocketModeToggled(enabled) + } + + override fun onConfirmStayConnectedInBackground() { + viewModel.confirmStayConnectedInBackground() + } + + override fun onDismissStayConnectedInBackgroundDialog() { + viewModel.dismissStayConnectedInBackgroundDialog() + } } } @@ -102,6 +115,9 @@ private interface DataAndStorageSettingsCallbacks { fun onMobileDataAutoDownloadSelectionChanged(selection: Array) = Unit fun onWifiDataAutoDownloadSelectionChanged(selection: Array) = Unit fun onRoamingDataAutoDownloadSelectionChanged(selection: Array) = Unit + fun onForceWebsocketModeChanged(enabled: Boolean) = Unit + fun onConfirmStayConnectedInBackground() = Unit + fun onDismissStayConnectedInBackgroundDialog() = Unit object Empty : DataAndStorageSettingsCallbacks } @@ -252,6 +268,19 @@ private fun DataAndStorageSettingsScreen( Dividers.Default() } + item { + Rows.ToggleRow( + checked = state.forceWebsocketMode || !state.playServicesAvailable, + text = stringResource(R.string.DataAndStorageSettingsFragment__stay_connected_in_background), + enabled = state.playServicesAvailable, + onCheckChanged = callbacks::onForceWebsocketModeChanged + ) + } + + item { + Dividers.Default() + } + item { Texts.SectionHeader(stringResource(R.string.preferences_proxy)) } @@ -264,6 +293,17 @@ private fun DataAndStorageSettingsScreen( ) } } + + if (state.showStayConnectedDialog) { + Dialogs.SimpleAlertDialog( + title = "", + body = stringResource(R.string.DataAndStorageSettingsFragment__staying_connected_while_in_the_background_will_likely_result_in_increased_battery_usage), + confirm = stringResource(R.string.DataAndStorageSettingsFragment__enable), + dismiss = stringResource(android.R.string.cancel), + onConfirm = callbacks::onConfirmStayConnectedInBackground, + onDismiss = callbacks::onDismissStayConnectedInBackgroundDialog + ) + } } } @@ -279,7 +319,10 @@ private fun DataAndStorageSettingsScreenPreview() { roamingAutoDownloadValues = setOf(), callDataMode = CallDataMode.HIGH_ALWAYS, isProxyEnabled = false, - sentMediaQuality = SentMediaQuality.STANDARD + sentMediaQuality = SentMediaQuality.STANDARD, + forceWebsocketMode = false, + playServicesAvailable = true, + showStayConnectedDialog = false ), callbacks = DataAndStorageSettingsCallbacks.Empty ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsState.kt index 3659cc32f6..57acd3dd8f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsState.kt @@ -10,5 +10,8 @@ data class DataAndStorageSettingsState( val roamingAutoDownloadValues: Set, val callDataMode: CallDataMode, val isProxyEnabled: Boolean, - val sentMediaQuality: SentMediaQuality + val sentMediaQuality: SentMediaQuality, + val forceWebsocketMode: Boolean, + val playServicesAvailable: Boolean, + val showStayConnectedDialog: Boolean ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsViewModel.kt index 4cf4226b26..ad54f97069 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsViewModel.kt @@ -7,8 +7,11 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import org.thoughtcrime.securesms.dependencies.AppDependencies +import org.thoughtcrime.securesms.keyvalue.SettingsValues.ForceWebsocketMode import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.messages.IncomingMessageObserver import org.thoughtcrime.securesms.mms.SentMediaQuality +import org.thoughtcrime.securesms.util.PlayServicesUtil import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.webrtc.CallDataMode @@ -23,7 +26,7 @@ class DataAndStorageSettingsViewModel( fun refresh() { repository.getTotalStorageUse { totalStorageUse -> - store.update { getState().copy(totalStorageUse = totalStorageUse) } + store.update { getState().copy(totalStorageUse = totalStorageUse, showStayConnectedDialog = it.showStayConnectedDialog) } } } @@ -53,8 +56,34 @@ class DataAndStorageSettingsViewModel( getStateAndCopyStorageUsage() } + fun onForceWebsocketModeToggled(enabled: Boolean) { + if (enabled) { + store.update { it.copy(showStayConnectedDialog = true) } + } else { + applyForceWebsocketMode(false) + } + } + + fun confirmStayConnectedInBackground() { + applyForceWebsocketMode(true) + } + + fun dismissStayConnectedInBackgroundDialog() { + store.update { it.copy(showStayConnectedDialog = false) } + } + + private fun applyForceWebsocketMode(enabled: Boolean) { + SignalStore.settings.forceWebsocketMode = if (enabled) ForceWebsocketMode.ENABLED_BY_USER else ForceWebsocketMode.DISABLED + if (!enabled) { + IncomingMessageObserver.stopForegroundService(AppDependencies.application) + } + AppDependencies.resetNetwork() + AppDependencies.startNetwork() + getStateAndCopyStorageUsage() + } + private fun getStateAndCopyStorageUsage() { - store.update { getState().copy(totalStorageUse = it.totalStorageUse) } + store.update { getState().copy(totalStorageUse = it.totalStorageUse, showStayConnectedDialog = it.showStayConnectedDialog) } } private fun getState() = DataAndStorageSettingsState( @@ -70,7 +99,10 @@ class DataAndStorageSettingsViewModel( ), callDataMode = SignalStore.settings.callDataMode, isProxyEnabled = SignalStore.proxy.isProxyEnabled, - sentMediaQuality = SignalStore.settings.sentMediaQuality + sentMediaQuality = SignalStore.settings.sentMediaQuality, + forceWebsocketMode = SignalStore.settings.forceWebsocketMode.isEnabled, + playServicesAvailable = PlayServicesUtil.getPlayServicesStatus(AppDependencies.application) == PlayServicesUtil.PlayServicesStatus.SUCCESS, + showStayConnectedDialog = false ) class Factory( 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 3aa0e728ff..a69cc0f9ae 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 @@ -83,7 +83,6 @@ 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) { @@ -511,25 +510,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/InternalSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsState.kt index ef7a6f7e51..be038bcda4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsState.kt @@ -10,7 +10,6 @@ data class InternalSettingsState( val gv2forceInvites: Boolean, val gv2ignoreP2PChanges: Boolean, val allowCensorshipSetting: Boolean, - val forceWebsocketMode: Boolean, val callingServer: String, val callingDataMode: CallManager.DataMode, val callingDisableTelecom: Boolean, 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 123d83da7c..53ff99ef7f 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 @@ -70,11 +70,6 @@ class InternalSettingsViewModel(private val repository: InternalSettingsReposito refresh() } - fun setForceWebsocketMode(enabled: Boolean) { - preferenceDataStore.putBoolean(InternalValues.FORCE_WEBSOCKET_MODE, enabled) - refresh() - } - fun resetPnpInitializedState() { SignalStore.misc.hasPniInitializedDevices = false refresh() @@ -182,7 +177,6 @@ class InternalSettingsViewModel(private val repository: InternalSettingsReposito gv2forceInvites = SignalStore.internal.gv2ForceInvites, gv2ignoreP2PChanges = SignalStore.internal.gv2IgnoreP2PChanges, allowCensorshipSetting = SignalStore.internal.allowChangingCensorshipSetting, - forceWebsocketMode = SignalStore.internal.isWebsocketModeForced, callingServer = SignalStore.internal.groupCallingServer, callingDataMode = SignalStore.internal.callingDataMode, callingDisableTelecom = SignalStore.internal.callingDisableTelecom, diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java index 7fe0880d3f..a6e624b16a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java @@ -380,7 +380,7 @@ public class ApplicationDependencyProvider implements AppDependencies.Provider { @Override public @NonNull SignalWebSocket.AuthenticatedWebSocket provideAuthWebSocket(@NonNull Supplier signalServiceConfigurationSupplier, @NonNull Supplier libSignalNetworkSupplier) { - SleepTimer sleepTimer = !SignalStore.account().isFcmEnabled() || SignalStore.internal().isWebsocketModeForced() ? new AlarmSleepTimer(context) : new UptimeSleepTimer(); + SleepTimer sleepTimer = !SignalStore.account().isFcmEnabled() || SignalStore.settings().getForceWebsocketMode().isEnabled() ? new AlarmSleepTimer(context) : new UptimeSleepTimer(); SignalWebSocketHealthMonitor healthMonitor = new SignalWebSocketHealthMonitor(sleepTimer, true); WebSocketFactory authFactory = () -> { @@ -413,7 +413,7 @@ public class ApplicationDependencyProvider implements AppDependencies.Provider { @Override public @NonNull SignalWebSocket.UnauthenticatedWebSocket provideUnauthWebSocket(@NonNull Supplier signalServiceConfigurationSupplier, @NonNull Supplier libSignalNetworkSupplier) { - SleepTimer sleepTimer = !SignalStore.account().isFcmEnabled() || SignalStore.internal().isWebsocketModeForced() ? new AlarmSleepTimer(context) : new UptimeSleepTimer(); + SleepTimer sleepTimer = !SignalStore.account().isFcmEnabled() || SignalStore.settings().getForceWebsocketMode().isEnabled() ? new AlarmSleepTimer(context) : new UptimeSleepTimer(); SignalWebSocketHealthMonitor healthMonitor = new SignalWebSocketHealthMonitor(sleepTimer, false); WebSocketFactory unauthFactory = () -> { 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 84c712b2ed..c809e22a22 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/FcmRefreshJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/FcmRefreshJob.java @@ -27,9 +27,12 @@ import org.thoughtcrime.securesms.dependencies.AppDependencies; import org.thoughtcrime.securesms.gcm.FcmUtil; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; +import org.thoughtcrime.securesms.keyvalue.SettingsValues.ForceWebsocketMode; import org.thoughtcrime.securesms.keyvalue.SignalStore; +import org.thoughtcrime.securesms.messages.IncomingMessageObserver; import org.thoughtcrime.securesms.net.SignalNetwork; import org.thoughtcrime.securesms.transport.RetryLaterException; +import org.thoughtcrime.securesms.util.PlayServicesUtil; import org.whispersystems.signalservice.api.NetworkResultUtil; import org.signal.network.exceptions.NonSuccessfulResponseCodeException; @@ -69,20 +72,20 @@ public class FcmRefreshJob extends BaseJob { @Override public void onRun() throws Exception { - if (!SignalStore.account().isFcmEnabled()) return; - Log.i(TAG, "Reregistering FCM..."); - int result = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context); - - if (result != ConnectionResult.SUCCESS) { - Log.w(TAG, "Play Services are unavailable. Skipping FCM refresh."); - return; + boolean playServicesMissing = PlayServicesUtil.getPlayServicesStatus(context) == PlayServicesUtil.PlayServicesStatus.MISSING ; + if (playServicesMissing) { + Log.w(TAG, "Play Services are unavailable."); } Optional token = FcmUtil.getToken(context); if (token.isPresent()) { + if (playServicesMissing) { + Log.w(TAG, "We were able to get a token despite Play Services being missing!"); + } + String oldToken = SignalStore.account().getFcmToken(); if (!token.get().equals(oldToken)) { @@ -94,6 +97,23 @@ public class FcmRefreshJob extends BaseJob { NetworkResultUtil.toBasicLegacy(SignalNetwork.account().setFcmToken(token.get())); SignalStore.account().setFcmToken(token.get()); + + if (!SignalStore.account().isFcmEnabled()) { + Log.w(TAG, "We had no Play Services, but were still able to get an FCM token! Re-enabling."); + SignalStore.account().setFcmEnabled(true); + AppDependencies.getJobManager().add(new RefreshAttributesJob()); + AppDependencies.resetNetwork(); + AppDependencies.startNetwork(); + IncomingMessageObserver.stopForegroundService(context); + } + + if (SignalStore.settings().getForceWebsocketMode() == ForceWebsocketMode.ENABLED_AUTOMATICALLY) { + Log.i(TAG, "FCM succeeded while in auto-enabled websocket mode. Reverting to disabled."); + SignalStore.settings().setForceWebsocketMode(ForceWebsocketMode.DISABLED); + IncomingMessageObserver.stopForegroundService(context); + AppDependencies.resetNetwork(); + AppDependencies.startNetwork(); + } } else { throw new RetryLaterException(new IOException("Failed to retrieve a token.")); } @@ -102,6 +122,30 @@ public class FcmRefreshJob extends BaseJob { @Override public void onFailure() { Log.w(TAG, "FCM reregistration failed after retry attempt exhaustion!"); + + PlayServicesUtil.PlayServicesStatus status = PlayServicesUtil.getPlayServicesStatus(context); + + if (status == PlayServicesUtil.PlayServicesStatus.MISSING) { + Log.w(TAG, "This was a check where we tried to get a token despite having no Play Services. We failed. Marking down the time."); + SignalStore.misc().setLastMissingPlayServicesFcmVerificationTime(System.currentTimeMillis()); + + if (SignalStore.account().isFcmEnabled()) { + Log.w(TAG, "Play Services are no longer available, and we failed to fetch a token. Disabling FCM."); + SignalStore.account().setFcmEnabled(false); + SignalStore.account().setFcmToken(null); + AppDependencies.getJobManager().add(new RefreshAttributesJob()); + AppDependencies.resetNetwork(); + AppDependencies.startNetwork(); + } + } else if (status == PlayServicesUtil.PlayServicesStatus.SUCCESS && + SignalStore.settings().getForceWebsocketMode() == ForceWebsocketMode.DISABLED && + System.currentTimeMillis() - SignalStore.account().getFcmTokenLastSetTime() > TimeUnit.DAYS.toMillis(3)) + { + Log.w(TAG, "FCM has been failing for over 3 days despite Play Services being available. Auto-enabling forced websocket mode so the user can still get messages."); + SignalStore.settings().setForceWebsocketMode(ForceWebsocketMode.ENABLED_AUTOMATICALLY); + AppDependencies.resetNetwork(); + AppDependencies.startNetwork(); + } } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java index b8e6a36d62..cbe982b447 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java @@ -96,7 +96,7 @@ public class RefreshAttributesJob extends BaseJob { } int registrationId = SignalStore.account().getRegistrationId(); - boolean fetchesMessages = !SignalStore.account().isFcmEnabled() || SignalStore.internal().isWebsocketModeForced(); + boolean fetchesMessages = !SignalStore.account().isFcmEnabled() || SignalStore.settings().getForceWebsocketMode().isEnabled(); byte[] unidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(ProfileKeyUtil.getSelfProfileKey()); boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(context); String registrationLockV2 = null; diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.kt index 17925b558f..3afb62dc67 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.kt @@ -26,7 +26,6 @@ class InternalValues internal constructor(store: KeyValueStore) : SignalStoreVal const val CALLING_USE_INPUT_VOICE_COMM: String = "internal.calling_use_input_voice_comm" const val SHAKE_TO_REPORT: String = "internal.shake_to_report" const val DISABLE_STORAGE_SERVICE: String = "internal.disable_storage_service" - const val FORCE_WEBSOCKET_MODE: String = "internal.force_websocket_mode" const val LAST_SCROLL_POSITION: String = "internal.last_scroll_position" const val CONVERSATION_ITEM_V2_MEDIA: String = "internal.conversation_item_v2_media" const val WEB_SOCKET_SHADOWING_STATS: String = "internal.web_socket_shadowing_stats" @@ -170,11 +169,6 @@ class InternalValues internal constructor(store: KeyValueStore) : SignalStoreVal */ var callingUseInputVoiceComm by booleanValue(CALLING_USE_INPUT_VOICE_COMM, true).defaultForExternalUsers() - /** - * 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. - */ - var isWebsocketModeForced: Boolean by booleanValue(FORCE_WEBSOCKET_MODE, false).defaultForExternalUsers() - var hevcEncoding by booleanValue(ENCODE_HEVC, false).defaultForExternalUsers() var lastScrollPosition: Int by integerValue(LAST_SCROLL_POSITION, 0).defaultForExternalUsers() diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.kt index 2011ba90e6..4a576a871b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.kt @@ -53,6 +53,7 @@ class MiscellaneousValues internal constructor(store: KeyValueStore) : SignalSto private const val CALLING_ASSETS_VERSION = "misc.calling_assets_version" private const val LAST_SYNC_MESSAGE_SEEN_TIME_MS = "misc.last_sync_message_seen_time" private const val LAST_APPLIED_PNI_CHANGE_SERVER_TIMESTAMP = "misc.last_applied_pni_change_server_timestamp" + private const val LAST_MISSING_PLAY_SERVICES_FCM_VERIFICATION_TIME = "misc.last_missing_play_services_fcm_verification_time" } public override fun onFirstEverAppLaunch() { @@ -348,4 +349,9 @@ class MiscellaneousValues internal constructor(store: KeyValueStore) : SignalSto * if new assets need to be fetched. */ var callingAssetsVersion: Int by integerValue(CALLING_ASSETS_VERSION, 0) + + /** + * The last time we tried to get an FCM token for a user reporting missing Play Services. + */ + var lastMissingPlayServicesFcmVerificationTime: Long by longValue(LAST_MISSING_PLAY_SERVICES_FCM_VERIFICATION_TIME, 0) } 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 853d62bdf4..e5338de961 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java @@ -76,6 +76,7 @@ public final class SettingsValues extends SignalStoreValues { private static final String SCREEN_LOCK_ENABLED = "settings.screen.lock.enabled"; private static final String SCREEN_LOCK_TIMEOUT = "settings.screen.lock.timeout"; private static final String AUTOMATIC_VERIFICATION_ENABLED = "settings.automatic.verification.enabled"; + private static final String FORCE_WEBSOCKET_MODE = "settings.force.websocket.mode.2"; public static final int BACKUP_DEFAULT_HOUR = 2; public static final int BACKUP_DEFAULT_MINUTE = 0; @@ -570,6 +571,17 @@ public final class SettingsValues extends SignalStoreValues { putBoolean(AUTOMATIC_VERIFICATION_ENABLED, enabled); } + public @NonNull ForceWebsocketMode getForceWebsocketMode() { + if (getStore().containsKey(FORCE_WEBSOCKET_MODE)) { + return ForceWebsocketMode.deserialize(getInteger(FORCE_WEBSOCKET_MODE, ForceWebsocketMode.DISABLED.serialize())); + } + return getBoolean(FORCE_WEBSOCKET_MODE, false) ? ForceWebsocketMode.ENABLED_BY_USER : ForceWebsocketMode.DISABLED; + } + + public void setForceWebsocketMode(@NonNull ForceWebsocketMode mode) { + putInteger(FORCE_WEBSOCKET_MODE, mode.serialize()); + } + private @Nullable Uri getUri(@NonNull String key) { String uri = getString(key, ""); @@ -607,6 +619,37 @@ public final class SettingsValues extends SignalStoreValues { } } + public enum ForceWebsocketMode { + DISABLED(0), ENABLED_BY_USER(1), ENABLED_AUTOMATICALLY(2); + + private final int value; + + ForceWebsocketMode(int value) { + this.value = value; + } + + public boolean isEnabled() { + return this != DISABLED; + } + + public int serialize() { + return value; + } + + public static ForceWebsocketMode deserialize(int value) { + switch (value) { + case 0: + return DISABLED; + case 1: + return ENABLED_BY_USER; + case 2: + return ENABLED_AUTOMATICALLY; + default: + throw new IllegalArgumentException("Bad value: " + value); + } + } + } + public enum Theme { SYSTEM("system"), LIGHT("light"), DARK("dark"); 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 65fe17882e..b7ad1731dc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.kt @@ -93,6 +93,14 @@ class IncomingMessageObserver( private val censored: Boolean get() = AppDependencies.signalServiceNetworkAccess.isCensored() + + /** + * Stops the foreground service for websocket users. + */ + @JvmStatic + fun stopForegroundService(context: Context) { + context.stopService(Intent(context, ForegroundService::class.java)) + } } private val decryptionDrainedListeners: MutableList = CopyOnWriteArrayList() @@ -147,7 +155,7 @@ class IncomingMessageObserver( MessageRetrievalThread().start() - if (!SignalStore.account.fcmEnabled || SignalStore.internal.isWebsocketModeForced) { + if (!SignalStore.account.fcmEnabled || SignalStore.settings.forceWebsocketMode.isEnabled) { try { ForegroundServiceUtil.start(context, Intent(context, ForegroundService::class.java)) } catch (e: UnableToStartException) { @@ -244,7 +252,7 @@ class IncomingMessageObserver( val fcmEnabled = SignalStore.account.fcmEnabled val hasNetwork = NetworkConstraint.isMet(context) val hasProxy = SignalStore.proxy.isProxyEnabled - val forceWebsocket = SignalStore.internal.isWebsocketModeForced + val forceWebsocket = SignalStore.settings.forceWebsocketMode.isEnabled val websocketAlreadyOpen = isConnectionAvailable() val lastInteractionString = if (appVisibleSnapshot) "N/A" else timeIdle.toString() + " ms (" + (if (timeIdle < maxBackgroundTime) "within limit" else "over limit") + ")" @@ -431,7 +439,7 @@ class IncomingMessageObserver( Log.i(TAG, "Initializing! (${this.hashCode()})") uncaughtExceptionHandler = this - sleepTimer = if (!SignalStore.account.fcmEnabled || SignalStore.internal.isWebsocketModeForced) AlarmSleepTimer(context) else UptimeSleepTimer() + sleepTimer = if (!SignalStore.account.fcmEnabled || SignalStore.settings.forceWebsocketMode.isEnabled) AlarmSleepTimer(context) else UptimeSleepTimer() canProcessMessages = !SignalStore.registration.restoreDecisionState.isDecisionPending } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1d2b1b293e..445cb87c19 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4363,7 +4363,7 @@ Never WiFi and mobile data Mobile data only - Using less data may improve calls on bad networks + Using less data may improve calls on bad networks. In-chat sounds Show Ringtone @@ -6056,6 +6056,12 @@ Standard Calls + + Stay connected in background + + Staying connected while in the background will likely result in increased battery usage. This is not necessary for most devices. Only enable this if you\'ve been experiencing notification issues. + + Enable