diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index d18c56462b..7402463dda 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -261,10 +261,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { components.backgroundServices.accountManagerAvailableQueue.runIfReadyOrQueue { lifecycleScope.launch { // Make sure accountManager is initialized. - components.backgroundServices.accountManager.initAsync().await() + components.backgroundServices.accountManager.start() // If we're authenticated, kick-off a sync and a device state refresh. components.backgroundServices.accountManager.authenticatedAccount()?.let { - components.backgroundServices.accountManager.syncNowAsync( + components.backgroundServices.accountManager.syncNow( SyncReason.Startup, debounce = true ) diff --git a/app/src/main/java/org/mozilla/fenix/components/AccountAbnormalities.kt b/app/src/main/java/org/mozilla/fenix/components/AccountAbnormalities.kt index 9f1a137e8c..92fb160bba 100644 --- a/app/src/main/java/org/mozilla/fenix/components/AccountAbnormalities.kt +++ b/app/src/main/java/org/mozilla/fenix/components/AccountAbnormalities.kt @@ -5,13 +5,11 @@ package org.mozilla.fenix.components import android.content.Context +import android.content.SharedPreferences import android.os.StrictMode import androidx.annotation.GuardedBy import androidx.annotation.VisibleForTesting -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async import mozilla.components.concept.sync.AccountObserver import mozilla.components.concept.sync.AuthType import mozilla.components.concept.sync.OAuthAccount @@ -77,9 +75,18 @@ class AccountAbnormalities( private val logger = Logger("AccountAbnormalities") - private val prefs = StrictMode.allowThreadDiskReads().resetPoliciesAfter { - context.getSharedPreferences(PREF_FXA_ABNORMALITIES, Context.MODE_PRIVATE) - } + private val prefs: SharedPreferences + private val hadAccountPrior: Boolean + + init { + val prefPair = StrictMode.allowThreadDiskReads().resetPoliciesAfter { + val p = context.getSharedPreferences(PREF_FXA_ABNORMALITIES, Context.MODE_PRIVATE) + val a = p.getBoolean(KEY_HAS_ACCOUNT, false) + Pair(p, a) + } + prefs = prefPair.first + hadAccountPrior = prefPair.second +} /** * Once [accountManager] is initialized, queries it to detect abnormal account states. @@ -89,37 +96,28 @@ class AccountAbnormalities( * @param initResult A deferred result of initializing [accountManager]. * @return A [Unit] deferred, resolved once [initResult] is resolved and state is processed for abnormalities. */ - fun accountManagerInitializedAsync( - accountManager: FxaAccountManager, - initResult: Deferred - ): Deferred { + fun accountManagerStarted( + accountManager: FxaAccountManager + ) { + check(!accountManagerConfigured) { "accountManagerStarted called twice" } accountManagerConfigured = true - return CoroutineScope(coroutineContext).async { - // Wait for the account manager to finish initializing. If it's queried before the - // "init" deferred returns, we'll get inaccurate results. - initResult.await() - - // Account manager finished initialization, we can now query it for the account state - // and see if it doesn't match our expectations. - // Behaviour considered abnormal: - // - we had an account before, and it's no longer present during startup - - // We use a flag in prefs to keep track of the fact that we have an authenticated - // account. This works because our account state is persisted in the application's - // directory, same as SharedPreferences. If user clears application data, both the - // fxa state and our flag will be removed. - val hadAccountBefore = prefs.getBoolean(KEY_HAS_ACCOUNT, false) - val hasAccountNow = accountManager.authenticatedAccount() != null - if (hadAccountBefore && !hasAccountNow) { - prefs.edit().putBoolean(KEY_HAS_ACCOUNT, false).apply() - - logger.warn("Missing expected account on startup") - - crashReporter.submitCaughtException( - AbnormalFxaEvent.MissingExpectedAccountAfterStartup() - ) - } + // Behaviour considered abnormal: + // - we had an account before, and it's no longer present during startup + + // We use a flag in prefs to keep track of the fact that we have an authenticated + // account. This works because our account state is persisted in the application's + // directory, same as SharedPreferences. If user clears application data, both the + // fxa state and our flag will be removed. + val hasAccountNow = accountManager.authenticatedAccount() != null + if (hadAccountPrior && !hasAccountNow) { + prefs.edit().putBoolean(KEY_HAS_ACCOUNT, false).apply() + + logger.warn("Missing expected account on startup") + + crashReporter.submitCaughtException( + AbnormalFxaEvent.MissingExpectedAccountAfterStartup() + ) } } @@ -152,8 +150,7 @@ class AccountAbnormalities( } override fun onAuthenticated(account: OAuthAccount, authType: AuthType) { - check(accountManagerConfigured) { "onAuthenticated before account manager was configured" } - + // Not checking state of accountManagerConfigured because we'll race against account manager's start. onAuthenticatedCalled = true // We don't check if KEY_HAS_ACCOUNT was already true: we will see onAuthenticated on every 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 44f4131fec..c2e92057e7 100644 --- a/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt +++ b/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt @@ -8,19 +8,22 @@ import android.content.Context import android.os.Build import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting.PRIVATE +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch import mozilla.components.browser.storage.sync.PlacesBookmarksStorage import mozilla.components.browser.storage.sync.PlacesHistoryStorage import mozilla.components.browser.storage.sync.RemoteTabsStorage import mozilla.components.concept.sync.AccountObserver import mozilla.components.concept.sync.AuthType import mozilla.components.concept.sync.DeviceCapability +import mozilla.components.concept.sync.DeviceConfig import mozilla.components.concept.sync.DeviceType import mozilla.components.concept.sync.OAuthAccount import mozilla.components.feature.accounts.push.FxaPushSupportFeature import mozilla.components.feature.accounts.push.SendTabFeature import mozilla.components.feature.syncedtabs.storage.SyncedTabsStorage import mozilla.components.lib.crash.CrashReporter -import mozilla.components.service.fxa.DeviceConfig +import mozilla.components.service.fxa.PeriodicSyncConfig import mozilla.components.service.fxa.ServerConfig import mozilla.components.service.fxa.SyncConfig import mozilla.components.service.fxa.SyncEngine @@ -86,7 +89,7 @@ class BackgroundServices( @VisibleForTesting val supportedEngines = setOf(SyncEngine.History, SyncEngine.Bookmarks, SyncEngine.Passwords, SyncEngine.Tabs) - private val syncConfig = SyncConfig(supportedEngines, syncPeriodInMinutes = 240L) // four hours + private val syncConfig = SyncConfig(supportedEngines, PeriodicSyncConfig(periodMinutes = 240)) // four hours init { /* Make the "history", "bookmark", "passwords", and "tabs" stores accessible to workers @@ -156,10 +159,10 @@ class BackgroundServices( SyncedTabsIntegration(context, accountManager).launch() - accountAbnormalities.accountManagerInitializedAsync( - accountManager, - accountManager.initAsync() - ) + MainScope().launch { + accountManager.start() + accountAbnormalities.accountManagerStarted(accountManager) + } }.also { accountManagerAvailableQueue.ready() } @@ -180,31 +183,33 @@ internal class TelemetryAccountObserver( override fun onAuthenticated(account: OAuthAccount, authType: AuthType) { when (authType) { // User signed-in into an existing FxA account. - AuthType.Signin -> - metricController.track(Event.SyncAuthSignIn) + AuthType.Signin -> Event.SyncAuthSignIn // User created a new FxA account. - AuthType.Signup -> - metricController.track(Event.SyncAuthSignUp) + AuthType.Signup -> Event.SyncAuthSignUp // User paired to an existing account via QR code scanning. - AuthType.Pairing -> - metricController.track(Event.SyncAuthPaired) + AuthType.Pairing -> Event.SyncAuthPaired + + // User signed-in into an FxA account shared from another locally installed app using the reuse flow. + AuthType.MigratedReuse -> Event.SyncAuthFromSharedReuse - // User signed-in into an FxA account shared from another locally installed app - // (e.g. Fennec). - AuthType.Shared -> - metricController.track(Event.SyncAuthFromShared) + // User signed-in into an FxA account shared from another locally installed app using the copy flow. + AuthType.MigratedCopy -> Event.SyncAuthFromSharedCopy // Account Manager recovered a broken FxA auth state, without direct user involvement. - AuthType.Recovered -> - metricController.track(Event.SyncAuthRecovered) + AuthType.Recovered -> Event.SyncAuthRecovered // User signed-in into an FxA account via unknown means. // Exact mechanism identified by the 'action' param. - is AuthType.OtherExternal -> - metricController.track(Event.SyncAuthOtherExternal) + is AuthType.OtherExternal -> Event.SyncAuthOtherExternal + + // Account restored from a hydrated state on disk (e.g. during startup). + AuthType.Existing -> null + }?.let { + metricController.track(it) } + // Used by Leanplum as a context variable. settings.fxaSignedIn = true } diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt index 1a3be9a71f..c01c4998b4 100644 --- a/app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt +++ b/app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt @@ -67,7 +67,8 @@ sealed class Event { object SyncAuthPaired : Event() object SyncAuthRecovered : Event() object SyncAuthOtherExternal : Event() - object SyncAuthFromShared : Event() + object SyncAuthFromSharedReuse : Event() + object SyncAuthFromSharedCopy : Event() object SyncAccountOpened : Event() object SyncAccountClosed : Event() object SyncAccountSyncNow : Event() diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt index ff71896e16..25a289a55f 100644 --- a/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt +++ b/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt @@ -269,7 +269,7 @@ private val Event.wrapper: EventWrapper<*>? is Event.SyncAuthOtherExternal -> EventWrapper( { SyncAuth.otherExternal.record(it) } ) - is Event.SyncAuthFromShared -> EventWrapper( + is Event.SyncAuthFromSharedReuse, Event.SyncAuthFromSharedCopy -> EventWrapper( { SyncAuth.autoLogin.record(it) } ) is Event.SyncAuthRecovered -> EventWrapper( diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/LeanplumMetricsService.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/LeanplumMetricsService.kt index acd4dae51c..d3dc2b3925 100644 --- a/app/src/main/java/org/mozilla/fenix/components/metrics/LeanplumMetricsService.kt +++ b/app/src/main/java/org/mozilla/fenix/components/metrics/LeanplumMetricsService.kt @@ -42,7 +42,7 @@ private val Event.name: String? is Event.CollectionTabRestored -> "E_Collection_Tab_Opened" is Event.SyncAuthSignUp -> "E_FxA_New_Signup" is Event.SyncAuthSignIn, Event.SyncAuthPaired, Event.SyncAuthOtherExternal -> "E_Sign_In_FxA" - is Event.SyncAuthFromShared -> "E_Sign_In_FxA_Fennec_to_Fenix" + is Event.SyncAuthFromSharedCopy, Event.SyncAuthFromSharedReuse -> "E_Sign_In_FxA_Fennec_to_Fenix" is Event.SyncAuthSignOut -> "E_Sign_Out_FxA" is Event.ClearedPrivateData -> "E_Cleared_Private_Data" is Event.DismissedOnboarding -> "E_Dismissed_Onboarding" diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolder.kt index 338b514b96..7bbad2c75f 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolder.kt @@ -14,7 +14,7 @@ import kotlinx.android.synthetic.main.onboarding_automatic_signin.view.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch -import mozilla.components.service.fxa.manager.SignInWithShareableAccountResult +import mozilla.components.service.fxa.manager.MigrationResult import mozilla.components.service.fxa.sharing.ShareableAccount import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelativeWithIntrinsicBounds import org.mozilla.fenix.R @@ -56,8 +56,12 @@ class OnboardingAutomaticSignInViewHolder( button.isEnabled = false val accountManager = context.components.backgroundServices.accountManager - when (accountManager.signInWithShareableAccountAsync(shareableAccount).await()) { - SignInWithShareableAccountResult.Failure -> { + when (accountManager.migrateFromAccount(shareableAccount)) { + MigrationResult.WillRetry, + MigrationResult.Success -> { + // We consider both of these as a 'success'. + } + MigrationResult.Failure -> { // Failed to sign-in (e.g. bad credentials). Allow to try again. button.text = context.getString(R.string.onboarding_firefox_account_auto_signin_confirm) button.isEnabled = true @@ -69,9 +73,6 @@ class OnboardingAutomaticSignInViewHolder( context.getString(R.string.onboarding_firefox_account_automatic_signin_failed) ).show() } - SignInWithShareableAccountResult.WillRetry, SignInWithShareableAccountResult.Success -> { - // We consider both of these as a 'success'. - } } } diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkController.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkController.kt index 5d203028f5..842d1878c3 100644 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkController.kt +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkController.kt @@ -143,7 +143,7 @@ class DefaultBookmarkController( scope.launch { store.dispatch(BookmarkFragmentAction.StartSync) invokePendingDeletion() - activity.components.backgroundServices.accountManager.syncNowAsync(SyncReason.User).await() + activity.components.backgroundServices.accountManager.syncNow(SyncReason.User) // The current bookmark node we are viewing may be made invalid after syncing so we // check if the current node is valid and if it isn't we find the nearest valid ancestor // and open it diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt index b34f22bb4a..f4714615a4 100644 --- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt @@ -344,7 +344,7 @@ class HistoryFragment : LibraryPageFragment(), UserInteractionHandl private suspend fun syncHistory() { val accountManager = requireComponents.backgroundServices.accountManager - accountManager.syncNowAsync(SyncReason.User).await() + accountManager.syncNow(SyncReason.User) viewModel.invalidate() } } diff --git a/app/src/main/java/org/mozilla/fenix/push/PushFxaIntegration.kt b/app/src/main/java/org/mozilla/fenix/push/PushFxaIntegration.kt index 061d06ac62..9412435f00 100644 --- a/app/src/main/java/org/mozilla/fenix/push/PushFxaIntegration.kt +++ b/app/src/main/java/org/mozilla/fenix/push/PushFxaIntegration.kt @@ -122,7 +122,7 @@ internal class OneTimeMessageDeliveryObserver( authType: AuthType ) { lazyAccount.value.withConstellation { - processRawEventAsync(String(message)) + MainScope().launch { processRawEvent(String(message)) } } MainScope().launch { diff --git a/app/src/main/java/org/mozilla/fenix/settings/account/AccountSettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/account/AccountSettingsFragment.kt index a9f56e8b6d..8ccc5ff87d 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/account/AccountSettingsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/account/AccountSettingsFragment.kt @@ -169,25 +169,32 @@ class AccountSettingsFragment : PreferenceFragmentCompat() { updateSyncEngineStates() setDisabledWhileSyncing(accountManager.isSyncActive()) - requirePreference(R.string.pref_key_sync_history).apply { - setOnPreferenceChangeListener { _, newValue -> - SyncEnginesStorage(context).setStatus(SyncEngine.History, newValue as Boolean) - @Suppress("DeferredResultUnused") - context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange) - true + fun updateSyncEngineState(context: Context, engine: SyncEngine, newState: Boolean) { + SyncEnginesStorage(context).setStatus(engine, newState) + viewLifecycleOwner.lifecycleScope.launch { + context.components.backgroundServices.accountManager.syncNow(SyncReason.EngineChange) } } - requirePreference(R.string.pref_key_sync_bookmarks).apply { - setOnPreferenceChangeListener { _, newValue -> - SyncEnginesStorage(context).setStatus(SyncEngine.Bookmarks, newValue as Boolean) - @Suppress("DeferredResultUnused") - context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange) - true + fun SyncEngine.prefId(): Int = when (this) { + SyncEngine.History -> R.string.pref_key_sync_history + SyncEngine.Bookmarks -> R.string.pref_key_sync_bookmarks + SyncEngine.Passwords -> R.string.pref_key_sync_logins + SyncEngine.Tabs -> R.string.pref_key_sync_tabs + else -> throw IllegalStateException("Accessing internal sync engines") + } + + listOf(SyncEngine.History, SyncEngine.Bookmarks, SyncEngine.Tabs).forEach { + requirePreference(it.prefId()).apply { + setOnPreferenceChangeListener { _, newValue -> + updateSyncEngineState(context, it, newValue as Boolean) + true + } } } - requirePreference(R.string.pref_key_sync_logins).apply { + // 'Passwords' listener is special, since we also display a pin protection warning. + requirePreference(SyncEngine.Passwords.prefId()).apply { setOnPreferenceChangeListener { _, newValue -> val manager = activity?.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager @@ -195,9 +202,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat() { newValue == false || !context.settings().shouldShowSecurityPinWarningSync ) { - SyncEnginesStorage(context).setStatus(SyncEngine.Passwords, newValue as Boolean) - @Suppress("DeferredResultUnused") - context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange) + updateSyncEngineState(context, SyncEngine.Passwords, newValue as Boolean) } else { showPinDialogWarning(newValue as Boolean) } @@ -205,15 +210,6 @@ class AccountSettingsFragment : PreferenceFragmentCompat() { } } - requirePreference(R.string.pref_key_sync_tabs).apply { - setOnPreferenceChangeListener { _, newValue -> - SyncEnginesStorage(context).setStatus(SyncEngine.Tabs, newValue as Boolean) - @Suppress("DeferredResultUnused") - context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange) - true - } - } - deviceConstellation?.registerDeviceObserver( deviceConstellationObserver, owner = this, @@ -237,8 +233,9 @@ class AccountSettingsFragment : PreferenceFragmentCompat() { setNegativeButton(getString(R.string.logins_warning_dialog_later)) { _: DialogInterface, _ -> SyncEnginesStorage(context).setStatus(SyncEngine.Passwords, newValue) - @Suppress("DeferredResultUnused") - context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange) + viewLifecycleOwner.lifecycleScope.launch { + context.components.backgroundServices.accountManager.syncNow(SyncReason.EngineChange) + } } setPositiveButton(getString(R.string.logins_warning_dialog_set_up_now)) { it: DialogInterface, _ -> @@ -278,13 +275,12 @@ class AccountSettingsFragment : PreferenceFragmentCompat() { viewLifecycleOwner.lifecycleScope.launch { requireComponents.analytics.metrics.track(Event.SyncAccountSyncNow) // Trigger a sync. - requireComponents.backgroundServices.accountManager.syncNowAsync(SyncReason.User) - .await() + requireComponents.backgroundServices.accountManager.syncNow(SyncReason.User) // Poll for device events & update devices. accountManager.authenticatedAccount() ?.deviceConstellation()?.run { - refreshDevicesAsync().await() - pollForCommandsAsync().await() + refreshDevices() + pollForCommands() } } } @@ -298,8 +294,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat() { context?.let { accountManager.authenticatedAccount() ?.deviceConstellation() - ?.setDeviceNameAsync(newValue, it) - ?.await() + ?.setDeviceName(newValue, it) } } return true diff --git a/app/src/main/java/org/mozilla/fenix/settings/account/SignOutFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/account/SignOutFragment.kt index f2c3ef8611..c972737def 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/account/SignOutFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/account/SignOutFragment.kt @@ -64,7 +64,7 @@ class SignOutFragment : BottomSheetDialogFragment() { viewLifecycleOwner.lifecycleScope.launch { requireComponents .backgroundServices.accountAbnormalities.userRequestedLogout() - accountManager.logoutAsync().await() + accountManager.logout() }.invokeOnCompletion { if (!findNavController().popBackStack(R.id.settingsFragment, false)) { dismiss() diff --git a/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt index 99fe35db9e..262d19b1dd 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt @@ -70,6 +70,7 @@ class TurnOnSyncFragment : Fragment(), AccountObserver { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + requireComponents.backgroundServices.accountManager.register(this, owner = this) requireComponents.analytics.metrics.track(Event.SyncAuthOpened) // App can be installed on devices with no camera modules. Like Android TV boxes. @@ -139,23 +140,13 @@ class TurnOnSyncFragment : Fragment(), AccountObserver { // Since the snackbar can be presented in BrowserFragment or in SettingsFragment we must // base our display method on the padSnackbar argument - if (args.padSnackbar) { - FenixSnackbar.make( - view = requireView(), - duration = snackbarLength, - isDisplayedWithBrowserToolbar = true - ) - .setText(snackbarText) - .show() - } else { - FenixSnackbar.make( - view = requireView(), - duration = snackbarLength, - isDisplayedWithBrowserToolbar = false - ) - .setText(snackbarText) - .show() - } + FenixSnackbar.make( + view = requireView(), + duration = snackbarLength, + isDisplayedWithBrowserToolbar = args.padSnackbar + ) + .setText(snackbarText) + .show() } private fun navigateToPairWithEmail() { diff --git a/app/src/main/java/org/mozilla/fenix/share/ShareViewModel.kt b/app/src/main/java/org/mozilla/fenix/share/ShareViewModel.kt index 65d25caede..4fde40fcef 100644 --- a/app/src/main/java/org/mozilla/fenix/share/ShareViewModel.kt +++ b/app/src/main/java/org/mozilla/fenix/share/ShareViewModel.kt @@ -54,8 +54,7 @@ class ShareViewModel(application: Application) : AndroidViewModel(application) { viewModelScope.launch(ioDispatcher) { fxaAccountManager.authenticatedAccount() ?.deviceConstellation() - ?.refreshDevicesAsync() - ?.await() + ?.refreshDevices() val devicesShareOptions = buildDeviceList(fxaAccountManager, network) devicesListLiveData.postValue(devicesShareOptions) diff --git a/app/src/test/java/org/mozilla/fenix/components/AccountAbnormalitiesTest.kt b/app/src/test/java/org/mozilla/fenix/components/AccountAbnormalitiesTest.kt index 21c0b404c4..22a8df8edd 100644 --- a/app/src/test/java/org/mozilla/fenix/components/AccountAbnormalitiesTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/AccountAbnormalitiesTest.kt @@ -9,7 +9,6 @@ import io.mockk.every import io.mockk.mockk import io.mockk.verify import org.mozilla.fenix.helpers.FenixRobolectricTestRunner -import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.runBlocking import mozilla.components.lib.crash.CrashReporter import mozilla.components.service.fxa.manager.FxaAccountManager @@ -35,12 +34,8 @@ class AccountAbnormalitiesTest { assertEquals("userRequestedLogout before account manager was configured", e.message) } - try { - accountAbnormalities.onAuthenticated(mockk(), mockk()) - fail() - } catch (e: IllegalStateException) { - assertEquals("onAuthenticated before account manager was configured", e.message) - } + // This doesn't throw, see method for details. + accountAbnormalities.onAuthenticated(mockk(), mockk()) try { accountAbnormalities.onLoggedOut() @@ -58,10 +53,7 @@ class AccountAbnormalitiesTest { val accountManager: FxaAccountManager = mockk(relaxed = true) val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext) - accountAbnormalities.accountManagerInitializedAsync( - accountManager, - CompletableDeferred(Unit).also { it.complete(Unit) } - ).await() + accountAbnormalities.accountManagerStarted(accountManager) // Logout action must be preceded by auth. accountAbnormalities.userRequestedLogout() @@ -74,10 +66,7 @@ class AccountAbnormalitiesTest { val accountManager: FxaAccountManager = mockk(relaxed = true) val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext) - accountAbnormalities.accountManagerInitializedAsync( - accountManager, - CompletableDeferred(Unit).also { it.complete(Unit) } - ).await() + accountAbnormalities.accountManagerStarted(accountManager) accountAbnormalities.onAuthenticated(mockk(), mockk()) // So far, so good. A regular logout request while being authenticated. @@ -95,10 +84,7 @@ class AccountAbnormalitiesTest { val accountManager: FxaAccountManager = mockk(relaxed = true) val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext) - accountAbnormalities.accountManagerInitializedAsync( - accountManager, - CompletableDeferred(Unit).also { it.complete(Unit) } - ).await() + accountAbnormalities.accountManagerStarted(accountManager) // User didn't request this logout. accountAbnormalities.onLoggedOut() @@ -111,10 +97,7 @@ class AccountAbnormalitiesTest { val accountManager: FxaAccountManager = mockk(relaxed = true) val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext) - accountAbnormalities.accountManagerInitializedAsync( - accountManager, - CompletableDeferred(Unit).also { it.complete(Unit) } - ).await() + accountAbnormalities.accountManagerStarted(accountManager) accountAbnormalities.onAuthenticated(mockk(), mockk()) verify { crashReporter wasNot Called } @@ -124,10 +107,7 @@ class AccountAbnormalitiesTest { val accountAbnormalities2 = AccountAbnormalities(testContext, crashReporter, this.coroutineContext) // mock accountManager doesn't have an account, but we expect it to have one since we // were authenticated before our "restart". - accountAbnormalities2.accountManagerInitializedAsync( - accountManager, - CompletableDeferred(Unit).also { it.complete(Unit) } - ).await() + accountAbnormalities2.accountManagerStarted(accountManager) assertCaughtException(crashReporter) } @@ -138,10 +118,7 @@ class AccountAbnormalitiesTest { val accountManager: FxaAccountManager = mockk(relaxed = true) val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext) - accountAbnormalities.accountManagerInitializedAsync( - accountManager, - CompletableDeferred(Unit).also { it.complete(Unit) } - ).await() + accountAbnormalities.accountManagerStarted(accountManager) // We saw an auth event, then user requested a logout. accountAbnormalities.onAuthenticated(mockk(), mockk()) diff --git a/app/src/test/java/org/mozilla/fenix/components/BackgroundServicesTest.kt b/app/src/test/java/org/mozilla/fenix/components/BackgroundServicesTest.kt index e0e93dad0d..9104b8a98c 100644 --- a/app/src/test/java/org/mozilla/fenix/components/BackgroundServicesTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/BackgroundServicesTest.kt @@ -78,10 +78,13 @@ class BackgroundServicesTest { fun `telemetry account observer tracks shared event`() { val account = mockk() - registry.notifyObservers { onAuthenticated(account, AuthType.Shared) } - verify { metrics.track(Event.SyncAuthFromShared) } + registry.notifyObservers { onAuthenticated(account, AuthType.MigratedReuse) } + verify { metrics.track(Event.SyncAuthFromSharedReuse) } verify { settings.fxaSignedIn = true } confirmVerified(metrics, settings) + + registry.notifyObservers { onAuthenticated(account, AuthType.MigratedCopy) } + verify { metrics.track(Event.SyncAuthFromSharedCopy) } } @Test diff --git a/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolderTest.kt index c81dad81dc..80978e3f0b 100644 --- a/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolderTest.kt +++ b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolderTest.kt @@ -7,16 +7,16 @@ package org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding import android.view.LayoutInflater import android.view.View import io.mockk.every +import io.mockk.coEvery import io.mockk.mockk import io.mockk.mockkObject -import io.mockk.unmockkObject import io.mockk.verify +import io.mockk.unmockkObject import kotlinx.android.synthetic.main.onboarding_automatic_signin.view.* -import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runBlockingTest -import mozilla.components.service.fxa.manager.SignInWithShareableAccountResult +import mozilla.components.service.fxa.manager.MigrationResult import mozilla.components.service.fxa.sharing.ShareableAccount import mozilla.components.support.test.robolectric.testContext import org.junit.After @@ -69,13 +69,30 @@ class OnboardingAutomaticSignInViewHolderTest { } @Test - fun `sign in on click`() = runBlocking { + fun `sign in on click - MigrationResult Success`() = runBlocking { + val account = mockk { + every { email } returns "email@example.com" + } + coEvery { + backgroundServices.accountManager.migrateFromAccount(account) + } returns MigrationResult.Success + + val holder = OnboardingAutomaticSignInViewHolder(view, scope = this) + holder.bind(account) + holder.onClick(view.fxa_sign_in_button) + + assertEquals("Signing in…", view.fxa_sign_in_button.text) + assertFalse(view.fxa_sign_in_button.isEnabled) + } + + @Test + fun `sign in on click - MigrationResult WillRetry treated the same as Success`() = runBlocking { val account = mockk { every { email } returns "email@example.com" } - every { - backgroundServices.accountManager.signInWithShareableAccountAsync(account) - } returns CompletableDeferred(SignInWithShareableAccountResult.Success) + coEvery { + backgroundServices.accountManager.migrateFromAccount(account) + } returns MigrationResult.WillRetry val holder = OnboardingAutomaticSignInViewHolder(view, scope = this) holder.bind(account) @@ -90,9 +107,9 @@ class OnboardingAutomaticSignInViewHolderTest { val account = mockk { every { email } returns "email@example.com" } - every { - backgroundServices.accountManager.signInWithShareableAccountAsync(account) - } returns CompletableDeferred(SignInWithShareableAccountResult.Failure) + coEvery { + backgroundServices.accountManager.migrateFromAccount(account) + } returns MigrationResult.Failure val holder = OnboardingAutomaticSignInViewHolder(view, scope = this) holder.bind(account)