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 c213567c60..fe3e428220 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 36070deede..e1da153fb1 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 961542809b..8764ca674f 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 0728daac04..8ff531cc4c 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 9cadd3b66b..a1bc70f662 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 3bfd85f1c3..c87ff7b6aa 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",