diff --git a/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt b/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt index c213567c6..fe3e42822 100644 --- a/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt +++ b/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt @@ -124,6 +124,8 @@ class BackgroundServices( } } + private val accountAuthenticationObserver = AccountAuthenticationObserver(context.settings()) + private val telemetryAccountObserver = TelemetryAccountObserver( context.settings(), ) @@ -163,6 +165,9 @@ class BackgroundServices( ), crashReporter ).also { accountManager -> + // Register an authentication account observer to cache status + accountManager.register(accountAuthenticationObserver) + // Register a telemetry account observer to keep track of FxA auth metrics. accountManager.register(telemetryAccountObserver) @@ -196,6 +201,16 @@ class BackgroundServices( } } +private class AccountAuthenticationObserver(private val settings: Settings) : AccountObserver { + override fun onAuthenticated(account: OAuthAccount, authType: AuthType) { + settings.hasFxaAuthenticated = true + } + + override fun onLoggedOut() { + settings.hasFxaAuthenticated = false + } +} + private class AccountManagerReadyObserver( private val accountManagerAvailableQueue: RunWhenReadyQueue ) : AccountObserver { diff --git a/app/src/main/java/org/mozilla/fenix/components/Components.kt b/app/src/main/java/org/mozilla/fenix/components/Components.kt index 36070deed..e1da153fb 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Components.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Components.kt @@ -37,6 +37,7 @@ import org.mozilla.fenix.ext.sort import org.mozilla.fenix.home.PocketUpdatesMiddleware import org.mozilla.fenix.home.blocklist.BlocklistHandler import org.mozilla.fenix.home.blocklist.BlocklistMiddleware +import org.mozilla.fenix.home.recentsyncedtabs.RecentSyncedTabState import org.mozilla.fenix.perf.AppStartReasonProvider import org.mozilla.fenix.perf.StartupActivityLog import org.mozilla.fenix.perf.StartupStateProvider @@ -214,6 +215,11 @@ class Components(private val context: Context) { } else { emptyList() }, + recentSyncedTabState = if (settings.hasFxaAuthenticated) { + RecentSyncedTabState.Loading + } else { + RecentSyncedTabState.None + }, recentHistory = emptyList() ).run { filterState(blocklistHandler) }, middlewares = listOf( diff --git a/app/src/main/java/org/mozilla/fenix/home/recentsyncedtabs/RecentSyncedTabFeature.kt b/app/src/main/java/org/mozilla/fenix/home/recentsyncedtabs/RecentSyncedTabFeature.kt index 961542809..8764ca674 100644 --- a/app/src/main/java/org/mozilla/fenix/home/recentsyncedtabs/RecentSyncedTabFeature.kt +++ b/app/src/main/java/org/mozilla/fenix/home/recentsyncedtabs/RecentSyncedTabFeature.kt @@ -85,7 +85,10 @@ class RecentSyncedTabFeature( override fun stopLoading() = Unit override fun onError(error: SyncedTabsView.ErrorType) { - store.dispatch(AppAction.RecentSyncedTabStateChange(RecentSyncedTabState.None)) + val isSyncing = store.state.recentSyncedTabState == RecentSyncedTabState.Loading + if ((!isSyncing && error == SyncedTabsView.ErrorType.NO_TABS_AVAILABLE) || error.isFatal()) { + store.dispatch(AppAction.RecentSyncedTabStateChange(RecentSyncedTabState.None)) + } } override fun start() { @@ -107,6 +110,19 @@ class RecentSyncedTabFeature( RecentSyncedTabs.latestSyncedTabIsStale.add() } } + + // Fatal errors represent any that will force a NONE state for the recent synced tab, such that + // it won't be displayed. + // SYNC_UNAVAILABLE is sent even though displaySyncedTabs is never called when an account + // is not authenticated. NO_TABS_AVAILABLE is only fatal if encountered after a sync is + // completed, and is handled separately above. + private fun SyncedTabsView.ErrorType.isFatal(): Boolean = when (this) { + SyncedTabsView.ErrorType.MULTIPLE_DEVICES_UNAVAILABLE, + SyncedTabsView.ErrorType.SYNC_ENGINE_UNAVAILABLE, + SyncedTabsView.ErrorType.SYNC_NEEDS_REAUTHENTICATION -> true + SyncedTabsView.ErrorType.NO_TABS_AVAILABLE, + SyncedTabsView.ErrorType.SYNC_UNAVAILABLE -> false + } } /** diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt index 0728daac0..8ff531cc4 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -1012,6 +1012,16 @@ class Settings(private val appContext: Context) : PreferencesHolder { default = true ) + var hasFxaAuthenticated by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_fxa_has_authenticated), + default = false + ) + + var lastPlacesStorageMaintenance by longPreference( + appContext.getPreferenceKey(R.string.pref_key_last_maintenance), + default = 0 + ) + fun addSearchWidgetInstalled(count: Int) { val key = appContext.getPreferenceKey(R.string.pref_key_search_widget_installed) val newValue = preferences.getInt(key, 0) + count diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index 9cadd3b66..a1bc70f66 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -85,6 +85,7 @@ pref_key_sign_out pref_key_sync_sign_in project_id + pref_key_fxa_has_authenticated pref_key_search_widget_installed pref_key_saved_logins_sorting_strategy diff --git a/app/src/test/java/org/mozilla/fenix/home/recentsyncedtabs/RecentSyncedTabFeatureTest.kt b/app/src/test/java/org/mozilla/fenix/home/recentsyncedtabs/RecentSyncedTabFeatureTest.kt index 3bfd85f1c..c87ff7b6a 100644 --- a/app/src/test/java/org/mozilla/fenix/home/recentsyncedtabs/RecentSyncedTabFeatureTest.kt +++ b/app/src/test/java/org/mozilla/fenix/home/recentsyncedtabs/RecentSyncedTabFeatureTest.kt @@ -175,13 +175,6 @@ class RecentSyncedTabFeatureTest { } } - @Test - fun `WHEN error is received THEN action dispatched with empty synced state`() { - feature.onError(SyncedTabsView.ErrorType.NO_TABS_AVAILABLE) - - verify { store.dispatch(AppAction.RecentSyncedTabStateChange(RecentSyncedTabState.None)) } - } - @Test fun `WHEN synced tab displayed THEN labeled counter metric recorded with device type`() { val tab = SyncedDeviceTabs(deviceAccessed1, listOf(createActiveTab())) @@ -222,6 +215,26 @@ class RecentSyncedTabFeatureTest { assertFalse(RecentSyncedTabs.latestSyncedTabIsStale.testHasValue()) } + @Test + fun `GIVEN that feature is not loading WHEN no tabs error received THEN dispatches NONE state`() { + every { store.state } returns mockk { + every { recentSyncedTabState } returns RecentSyncedTabState.None + } + feature.onError(SyncedTabsView.ErrorType.NO_TABS_AVAILABLE) + + verify { store.dispatch(AppAction.RecentSyncedTabStateChange(RecentSyncedTabState.None)) } + } + + @Test + fun `GIVEN that feature is loading WHEN fatal error received THEN dispatches NONE state`() { + every { store.state } returns mockk { + every { recentSyncedTabState } returns RecentSyncedTabState.Loading + } + feature.onError(SyncedTabsView.ErrorType.MULTIPLE_DEVICES_UNAVAILABLE) + + verify { store.dispatch(AppAction.RecentSyncedTabStateChange(RecentSyncedTabState.None)) } + } + private fun createActiveTab( title: String = "title", url: String = "url",