diff --git a/app/src/main/java/org/thoughtcrime/securesms/banner/banners/ServiceOutageBanner.kt b/app/src/main/java/org/thoughtcrime/securesms/banner/banners/ServiceOutageBanner.kt index bec0f1cdc1..59bcba18d4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/banner/banners/ServiceOutageBanner.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/banner/banners/ServiceOutageBanner.kt @@ -9,7 +9,9 @@ import android.content.Context import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.map +import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.banner.Banner import org.thoughtcrime.securesms.banner.ui.compose.DefaultBanner @@ -31,17 +33,24 @@ class ServiceOutageBanner(outageInProgress: Boolean) : Banner() { ) } - companion object { + /** + * A class that can be held by a listener but still produce new [ServiceOutageBanner] in its flow. + * Designed for being called upon by a listener that is listening to changes in [TextSecurePreferences] + */ + class Producer(private val context: Context) { + private val _flow = MutableSharedFlow(replay = 1) + val flow: Flow = _flow.map { ServiceOutageBanner(context) } - @JvmStatic - fun createOneShotFlow(context: Context): Flow = createAndEmit { - ServiceOutageBanner(context) + init { + queryAndEmit() } - /** - * Take a [Flow] of [Boolean] values representing the service status and map it into a [Flow] of [ServiceOutageBanner] - */ - @JvmStatic - fun fromFlow(statusFlow: Flow): Flow = statusFlow.map { ServiceOutageBanner(it) } + fun queryAndEmit() { + _flow.tryEmit(TextSecurePreferences.getServiceOutage(context)) + } + } + + companion object { + private val TAG = Log.tag(ServiceOutageBanner::class) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/banner/banners/UnauthorizedBanner.kt b/app/src/main/java/org/thoughtcrime/securesms/banner/banners/UnauthorizedBanner.kt index 2919fe27f2..35b5c7e9c5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/banner/banners/UnauthorizedBanner.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/banner/banners/UnauthorizedBanner.kt @@ -9,6 +9,9 @@ import android.content.Context import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.map +import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.banner.Banner import org.thoughtcrime.securesms.banner.ui.compose.Action @@ -18,6 +21,9 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.registration.ui.RegistrationActivity import org.thoughtcrime.securesms.util.TextSecurePreferences +/** + * A banner displayed when the client is unauthorized (deregistered). + */ class UnauthorizedBanner(val context: Context) : Banner() { override val enabled = TextSecurePreferences.isUnauthorizedReceived(context) || !SignalStore.account.isRegistered @@ -37,11 +43,24 @@ class UnauthorizedBanner(val context: Context) : Banner() { ) } - companion object { + /** + * A class that can be held by a listener but still produce new [UnauthorizedBanner] in its flow. + * Designed for being called upon by a listener that is listening to changes in [TextSecurePreferences] + */ + class Producer(private val context: Context) { + private val _flow = MutableSharedFlow(replay = 1) + val flow: Flow = _flow.map { UnauthorizedBanner(context) } - @JvmStatic - fun createFlow(context: Context): Flow = createAndEmit { - UnauthorizedBanner(context) + init { + queryAndEmit() + } + + fun queryAndEmit() { + _flow.tryEmit(TextSecurePreferences.isUnauthorizedReceived(context)) } } + + companion object { + private val TAG = Log.tag(UnauthorizedBanner::class) + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsFragment.kt index 8fab7f547a..cf1565340d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsFragment.kt @@ -35,6 +35,8 @@ import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.util.Environment import org.thoughtcrime.securesms.util.RemoteConfig +import org.thoughtcrime.securesms.util.SharedPreferencesLifecycleObserver +import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.Util import org.thoughtcrime.securesms.util.ViewUtil import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory @@ -73,11 +75,21 @@ class AppSettingsFragment : DSLSettingsFragment( } private fun updateBanners() { + val unauthorizedProducer = UnauthorizedBanner.Producer(requireContext()) + lifecycle.addObserver( + SharedPreferencesLifecycleObserver( + requireContext(), + mapOf( + TextSecurePreferences.UNAUTHORIZED_RECEIVED to { unauthorizedProducer.queryAndEmit() } + ) + ) + ) val bannerFlows = listOf( OutdatedBuildBanner.createFlow(requireContext(), OutdatedBuildBanner.ExpiryStatus.EXPIRED_ONLY), - UnauthorizedBanner.createFlow(requireContext()) + unauthorizedProducer.flow ) - val bannerManager = BannerManager(bannerFlows, + val bannerManager = BannerManager( + bannerFlows, onNewBannerShownListener = { if (bannerView.resolved()) { bannerView.get().addOnLayoutChangeListener { _, _, top, _, bottom, _, _, _, _ -> diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt index 6c22ed092c..d6b65f65ab 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt @@ -106,6 +106,8 @@ import org.thoughtcrime.securesms.badges.gifts.OpenableGift import org.thoughtcrime.securesms.badges.gifts.OpenableGiftItemDecoration import org.thoughtcrime.securesms.badges.gifts.viewgift.received.ViewReceivedGiftBottomSheet import org.thoughtcrime.securesms.badges.gifts.viewgift.sent.ViewSentGiftBottomSheet +import org.thoughtcrime.securesms.banner.banners.ServiceOutageBanner +import org.thoughtcrime.securesms.banner.banners.UnauthorizedBanner import org.thoughtcrime.securesms.components.AnimatingToggle import org.thoughtcrime.securesms.components.ComposeText import org.thoughtcrime.securesms.components.ConversationSearchBottomBar @@ -227,6 +229,7 @@ import org.thoughtcrime.securesms.groups.ui.migration.GroupsV1MigrationInfoBotto import org.thoughtcrime.securesms.groups.ui.migration.GroupsV1MigrationSuggestionsDialog import org.thoughtcrime.securesms.groups.v2.GroupBlockJoinRequestResult import org.thoughtcrime.securesms.invites.InviteActions +import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob import org.thoughtcrime.securesms.keyboard.KeyboardPage import org.thoughtcrime.securesms.keyboard.KeyboardPagerFragment import org.thoughtcrime.securesms.keyboard.KeyboardPagerViewModel @@ -306,7 +309,7 @@ import org.thoughtcrime.securesms.util.MessageConstraintsUtil.isValidEditMessage import org.thoughtcrime.securesms.util.PlayStoreUtil import org.thoughtcrime.securesms.util.RemoteConfig import org.thoughtcrime.securesms.util.SaveAttachmentUtil -import org.thoughtcrime.securesms.util.ServiceOutageObserver +import org.thoughtcrime.securesms.util.SharedPreferencesLifecycleObserver import org.thoughtcrime.securesms.util.SignalLocalMetrics import org.thoughtcrime.securesms.util.StorageUtil import org.thoughtcrime.securesms.util.TextSecurePreferences @@ -1019,13 +1022,22 @@ class ConversationFragment : val conversationBannerListener = ConversationBannerListener() binding.conversationBanner.listener = conversationBannerListener - val serviceOutageObserver = ServiceOutageObserver(requireContext()) - - lifecycle.addObserver(serviceOutageObserver) + val unauthorizedProducer = UnauthorizedBanner.Producer(requireContext()) + val serviceOutageProducer = ServiceOutageBanner.Producer(requireContext()) + lifecycle.addObserver( + SharedPreferencesLifecycleObserver( + requireContext(), + mapOf( + TextSecurePreferences.UNAUTHORIZED_RECEIVED to { unauthorizedProducer.queryAndEmit() }, + TextSecurePreferences.SERVICE_OUTAGE to { serviceOutageProducer.queryAndEmit() } + ) + ) + ) val bannerFlows = viewModel.getBannerFlows( context = requireContext(), - serviceOutageStatusFlow = serviceOutageObserver.flow, + unauthorizedFlow = unauthorizedProducer.flow, + serviceOutageStatusFlow = serviceOutageProducer.flow, groupJoinClickListener = conversationBannerListener::reviewJoinRequestsAction, onAddMembers = { conversationGroupViewModel.groupRecordSnapshot?.let { groupRecord -> @@ -1038,6 +1050,10 @@ class ConversationFragment : binding.conversationBanner.collectAndShowBanners(bannerFlows) + if (TextSecurePreferences.getServiceOutage(context)) { + AppDependencies.jobManager.add(ServiceOutageDetectionJob()) + } + viewModel .identityRecordsObservable .distinctUntilChanged() diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index 21ddc4dc9b..befe1a174b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -310,7 +310,7 @@ class ConversationViewModel( } @OptIn(ExperimentalCoroutinesApi::class) - fun getBannerFlows(context: Context, serviceOutageStatusFlow: Flow, groupJoinClickListener: () -> Unit, onAddMembers: () -> Unit, onNoThanks: () -> Unit, bubbleClickListener: (Boolean) -> Unit): List> { + fun getBannerFlows(context: Context, unauthorizedFlow: Flow, serviceOutageStatusFlow: Flow, groupJoinClickListener: () -> Unit, onAddMembers: () -> Unit, onNoThanks: () -> Unit, bubbleClickListener: (Boolean) -> Unit): List> { val pendingGroupJoinFlow: Flow = merge( flow { emit(PendingGroupJoinRequestsBanner(false, 0, {}, {})) @@ -327,8 +327,8 @@ class ConversationViewModel( return listOf( OutdatedBuildBanner.createFlow(context, OutdatedBuildBanner.ExpiryStatus.EXPIRED_ONLY), - UnauthorizedBanner.createFlow(context), - ServiceOutageBanner.fromFlow(serviceOutageStatusFlow), + unauthorizedFlow, + serviceOutageStatusFlow, pendingGroupJoinFlow, groupV1SuggestionsFlow, BubbleOptOutBanner.createFlow(inBubble = repository.isInBubble, bubbleClickListener) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java index f4e9bb6730..4ce7392de3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java @@ -99,8 +99,8 @@ import org.thoughtcrime.securesms.banner.BannerManager; import org.thoughtcrime.securesms.banner.banners.CdsPermanentErrorBanner; import org.thoughtcrime.securesms.banner.banners.CdsTemporaryErrorBanner; import org.thoughtcrime.securesms.banner.banners.DozeBanner; -import org.thoughtcrime.securesms.banner.banners.OutdatedBuildBanner; import org.thoughtcrime.securesms.banner.banners.MediaRestoreProgressBanner; +import org.thoughtcrime.securesms.banner.banners.OutdatedBuildBanner; import org.thoughtcrime.securesms.banner.banners.ServiceOutageBanner; import org.thoughtcrime.securesms.banner.banners.UnauthorizedBanner; import org.thoughtcrime.securesms.banner.banners.UsernameOutOfSyncBanner; @@ -165,8 +165,8 @@ import org.thoughtcrime.securesms.util.AppForegroundObserver; import org.thoughtcrime.securesms.util.AppStartup; import org.thoughtcrime.securesms.util.CachedInflater; import org.thoughtcrime.securesms.util.ConversationUtil; -import org.thoughtcrime.securesms.util.ServiceOutageObserver; import org.thoughtcrime.securesms.util.ServiceUtil; +import org.thoughtcrime.securesms.util.SharedPreferencesLifecycleObserver; import org.thoughtcrime.securesms.util.SignalLocalMetrics; import org.thoughtcrime.securesms.util.SignalProxyUtil; import org.thoughtcrime.securesms.util.SnapToTopDataObserver; @@ -184,14 +184,17 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import kotlin.Unit; +import kotlin.jvm.functions.Function0; import kotlinx.coroutines.flow.Flow; import static android.app.Activity.RESULT_OK; @@ -846,13 +849,26 @@ public class ConversationListFragment extends MainFragment implements ActionMode } private void initializeBanners() { - final ServiceOutageObserver serviceOutageObserver = new ServiceOutageObserver(requireContext()); + Map> listenerMap = new HashMap<>(); + final UnauthorizedBanner.Producer unauthorizedBannerProducer = new UnauthorizedBanner.Producer(requireContext()); + final ServiceOutageBanner.Producer serviceOutageBannerProducer = new ServiceOutageBanner.Producer(requireContext()); - getLifecycle().addObserver(serviceOutageObserver); + listenerMap.put(TextSecurePreferences.UNAUTHORIZED_RECEIVED, () -> { + unauthorizedBannerProducer.queryAndEmit(); + return Unit.INSTANCE; + }); + listenerMap.put(TextSecurePreferences.SERVICE_OUTAGE, () -> { + serviceOutageBannerProducer.queryAndEmit(); + return Unit.INSTANCE; + }); + + final SharedPreferencesLifecycleObserver sharedPrefsObserver = new SharedPreferencesLifecycleObserver(requireContext(), listenerMap); + + getLifecycle().addObserver(sharedPrefsObserver); final List> bannerRepositories = List.of(OutdatedBuildBanner.createFlow(requireContext(), OutdatedBuildBanner.ExpiryStatus.EXPIRED_ONLY), - UnauthorizedBanner.createFlow(requireContext()), - ServiceOutageBanner.fromFlow(serviceOutageObserver.getFlow()), + unauthorizedBannerProducer.getFlow(), + serviceOutageBannerProducer.getFlow(), OutdatedBuildBanner.createFlow(requireContext(), OutdatedBuildBanner.ExpiryStatus.OUTDATED_ONLY), DozeBanner.createFlow(requireContext()), CdsTemporaryErrorBanner.createFlow(getChildFragmentManager()), diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ServiceOutageDetectionJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/ServiceOutageDetectionJob.java index 53d0699144..4c50ecbd65 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ServiceOutageDetectionJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ServiceOutageDetectionJob.java @@ -19,9 +19,9 @@ public class ServiceOutageDetectionJob extends BaseJob { private static final String TAG = Log.tag(ServiceOutageDetectionJob.class); - public static final String IP_SUCCESS = "127.0.0.1"; - public static final String IP_FAILURE = "127.0.0.2"; - public static final long CHECK_TIME = 1000 * 60; + private static final String IP_SUCCESS = "127.0.0.1"; + private static final String IP_FAILURE = "127.0.0.2"; + private static final long CHECK_TIME = 1000 * 60; public ServiceOutageDetectionJob() { this(new Job.Parameters.Builder() diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingFragment.kt index f8fb2039b8..c6093ff5c8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingFragment.kt @@ -55,6 +55,8 @@ import org.thoughtcrime.securesms.stories.settings.StorySettingsActivity import org.thoughtcrime.securesms.stories.tabs.ConversationListTab import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsViewModel import org.thoughtcrime.securesms.stories.viewer.StoryViewerActivity +import org.thoughtcrime.securesms.util.SharedPreferencesLifecycleObserver +import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.ViewUtil import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter import org.thoughtcrime.securesms.util.fragments.requireListener @@ -139,9 +141,18 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l } private fun initializeBanners() { + val unauthorizedProducer = UnauthorizedBanner.Producer(requireContext()) + lifecycle.addObserver( + SharedPreferencesLifecycleObserver( + requireContext(), + mapOf( + TextSecurePreferences.UNAUTHORIZED_RECEIVED to { unauthorizedProducer.queryAndEmit() } + ) + ) + ) val bannerFlows = listOf( OutdatedBuildBanner.createFlow(requireContext(), OutdatedBuildBanner.ExpiryStatus.EXPIRED_ONLY), - UnauthorizedBanner.createFlow(requireContext()) + unauthorizedProducer.flow ) val bannerManager = BannerManager( bannerFlows, diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ServiceOutageObserver.kt b/app/src/main/java/org/thoughtcrime/securesms/util/ServiceOutageObserver.kt deleted file mode 100644 index dfe93b5937..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ServiceOutageObserver.kt +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2024 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.thoughtcrime.securesms.util - -import android.content.Context -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.LifecycleOwner -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.withContext -import org.signal.core.util.logging.Log -import org.thoughtcrime.securesms.BuildConfig -import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob -import java.net.InetAddress -import java.net.UnknownHostException - -/** - * A lifecycle aware observer that can be instantiated to monitor the service for outages. - * - * @see [ServiceOutageDetectionJob] - */ -class ServiceOutageObserver(private val context: Context) : DefaultLifecycleObserver { - companion object { - val TAG = Log.tag(ServiceOutageObserver::class) - } - - private var observing = false - - override fun onResume(owner: LifecycleOwner) { - observing = true - } - - override fun onStop(owner: LifecycleOwner) { - observing = false - } - - val flow: Flow = flow { - emit(TextSecurePreferences.getServiceOutage(context)) - TextSecurePreferences.setLastOutageCheckTime(context, System.currentTimeMillis()) - - while (true) { - if (observing && getNextCheckTime(context) <= System.currentTimeMillis()) { - when (queryAvailability()) { - Result.SUCCESS -> { - TextSecurePreferences.setServiceOutage(context, false) - emit(false) - } - - Result.FAILURE -> { - Log.w(TAG, "Service is down.") - TextSecurePreferences.setServiceOutage(context, true) - emit(true) - } - - Result.RETRY_LATER -> { - Log.w(TAG, "Service status check returned an unrecognized IP address. Could be a weird network state. Prompting retry.") - } - } - } - - val nextCheckTime = getNextCheckTime(context) - val now = System.currentTimeMillis() - val delay = nextCheckTime - now - if (delay > 0) { - delay(delay) - } - } - } - - private fun getNextCheckTime(context: Context): Long = TextSecurePreferences.getLastOutageCheckTime(context) + ServiceOutageDetectionJob.CHECK_TIME - - private suspend fun queryAvailability(): Result = withContext(Dispatchers.IO) { - try { - val now = System.currentTimeMillis() - TextSecurePreferences.setLastOutageCheckTime(context, now) - - val address = InetAddress.getByName(BuildConfig.SIGNAL_SERVICE_STATUS_URL) - - if (ServiceOutageDetectionJob.IP_SUCCESS == address.hostAddress) { - Result.SUCCESS - } else if (ServiceOutageDetectionJob.IP_FAILURE == address.hostAddress) { - Result.FAILURE - } else { - Result.RETRY_LATER - } - } catch (e: UnknownHostException) { - Log.i(TAG, "Received UnknownHostException!", e) - Result.RETRY_LATER - } - } - - private enum class Result { - SUCCESS, FAILURE, RETRY_LATER - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/SharedPreferencesLifecycleObserver.kt b/app/src/main/java/org/thoughtcrime/securesms/util/SharedPreferencesLifecycleObserver.kt new file mode 100644 index 0000000000..8dbd7e058e --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/util/SharedPreferencesLifecycleObserver.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.util + +import android.content.Context +import android.content.SharedPreferences +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner + +/** + * A lifecycle-aware observer that will let the changes to the [TextSecurePreferences] be observed. + * + * @param keysToListeners a map of [TextSecurePreferences] string keys to listeners that should be invoked when the values change. + */ +class SharedPreferencesLifecycleObserver(private val context: Context, keysToListeners: Map Unit>) : DefaultLifecycleObserver { + + private val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key -> + keysToListeners[key]?.invoke() + } + + override fun onResume(owner: LifecycleOwner) { + TextSecurePreferences.registerListener(context, listener) + } + + override fun onPause(owner: LifecycleOwner) { + TextSecurePreferences.unregisterListener(context, listener) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java index e4a444523e..7482d75caa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -110,7 +110,7 @@ public class TextSecurePreferences { public static final String ALWAYS_RELAY_CALLS_PREF = "pref_turn_only"; public static final String READ_RECEIPTS_PREF = "pref_read_receipts"; public static final String INCOGNITO_KEYBORAD_PREF = "pref_incognito_keyboard"; - private static final String UNAUTHORIZED_RECEIVED = "pref_unauthorized_received"; + public static final String UNAUTHORIZED_RECEIVED = "pref_unauthorized_received"; private static final String SUCCESSFUL_DIRECTORY_PREF = "pref_successful_directory"; private static final String DATABASE_ENCRYPTED_SECRET = "pref_database_encrypted_secret"; @@ -146,7 +146,7 @@ public class TextSecurePreferences { public static final String SIGNAL_PIN_CHANGE = "pref_kbs_change"; - private static final String SERVICE_OUTAGE = "pref_service_outage"; + public static final String SERVICE_OUTAGE = "pref_service_outage"; private static final String LAST_OUTAGE_CHECK_TIME = "pref_last_outage_check_time"; private static final String LAST_FULL_CONTACT_SYNC_TIME = "pref_last_full_contact_sync_time"; @@ -293,6 +293,14 @@ public class TextSecurePreferences { } } + public static void registerListener(@NonNull Context context, SharedPreferences.OnSharedPreferenceChangeListener listener) { + getSharedPreferences(context).registerOnSharedPreferenceChangeListener(listener); + } + + public static void unregisterListener(@NonNull Context context, SharedPreferences.OnSharedPreferenceChangeListener listener) { + getSharedPreferences(context).unregisterOnSharedPreferenceChangeListener(listener); + } + public static boolean isScreenLockEnabled(@NonNull Context context) { return getBooleanPreference(context, SCREEN_LOCK, false); }