Improve handling of devices without Play Services.

This commit is contained in:
Greyson Parrelli
2026-05-27 11:12:21 -04:00
committed by Michelle Tang
parent 0beda1e615
commit baa4dd3c86
15 changed files with 215 additions and 57 deletions
@@ -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 {
@@ -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<String>) {
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<String>) = Unit
fun onWifiDataAutoDownloadSelectionChanged(selection: Array<String>) = Unit
fun onRoamingDataAutoDownloadSelectionChanged(selection: Array<String>) = 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
)
@@ -10,5 +10,8 @@ data class DataAndStorageSettingsState(
val roamingAutoDownloadValues: Set<String>,
val callDataMode: CallDataMode,
val isProxyEnabled: Boolean,
val sentMediaQuality: SentMediaQuality
val sentMediaQuality: SentMediaQuality,
val forceWebsocketMode: Boolean,
val playServicesAvailable: Boolean,
val showStayConnectedDialog: Boolean
)
@@ -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(
@@ -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."),
@@ -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,
@@ -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,
@@ -380,7 +380,7 @@ public class ApplicationDependencyProvider implements AppDependencies.Provider {
@Override
public @NonNull SignalWebSocket.AuthenticatedWebSocket provideAuthWebSocket(@NonNull Supplier<SignalServiceConfiguration> signalServiceConfigurationSupplier, @NonNull Supplier<Network> 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<SignalServiceConfiguration> signalServiceConfigurationSupplier, @NonNull Supplier<Network> 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 = () -> {
@@ -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<String> 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
@@ -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;
@@ -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()
@@ -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)
}
@@ -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");
@@ -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<Runnable> = 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
}