diff --git a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt index c19d5bc71..469c5397d 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt @@ -25,6 +25,7 @@ import mozilla.components.concept.sync.AccountObserver import mozilla.components.concept.sync.AuthType import mozilla.components.concept.sync.OAuthAccount import mozilla.components.concept.sync.Profile +import mozilla.components.support.ktx.android.content.hasCamera import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.Config import org.mozilla.fenix.HomeActivity @@ -188,7 +189,16 @@ class SettingsFragment : PreferenceFragmentCompat() { val directions: NavDirections? = when (preference.key) { resources.getString(R.string.pref_key_sign_in) -> { - SettingsFragmentDirections.actionSettingsFragmentToTurnOnSyncFragment() + // App can be installed on devices with no camera modules. Like Android TV boxes. + // Let's skip presenting the option to sign in by scanning a qr code in this case + // and default to login with email and password. + if (requireContext().hasCamera()) { + SettingsFragmentDirections.actionSettingsFragmentToTurnOnSyncFragment() + } else { + requireComponents.services.accountsAuthFeature.beginAuthentication(requireContext()) + requireComponents.analytics.metrics.track(Event.SyncAuthUseEmail) + null + } } resources.getString(R.string.pref_key_search_settings) -> { SettingsFragmentDirections.actionSettingsFragmentToSearchEngineFragment() diff --git a/app/src/main/java/org/mozilla/fenix/settings/logins/SyncLoginsPreferenceView.kt b/app/src/main/java/org/mozilla/fenix/settings/logins/SyncLoginsPreferenceView.kt index 49e313b57..81d742d39 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/logins/SyncLoginsPreferenceView.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/logins/SyncLoginsPreferenceView.kt @@ -4,16 +4,21 @@ package org.mozilla.fenix.settings.logins +import android.content.Context import androidx.lifecycle.LifecycleOwner import androidx.navigation.NavController import androidx.preference.Preference import mozilla.components.concept.sync.AccountObserver import mozilla.components.concept.sync.AuthType import mozilla.components.concept.sync.OAuthAccount +import mozilla.components.feature.accounts.FirefoxAccountsAuthFeature import mozilla.components.service.fxa.SyncEngine import mozilla.components.service.fxa.manager.FxaAccountManager import mozilla.components.service.fxa.manager.SyncEnginesStorage +import mozilla.components.support.ktx.android.content.hasCamera import org.mozilla.fenix.R +import org.mozilla.fenix.components.metrics.Event +import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.settings.logins.fragment.SavedLoginsAuthFragmentDirections /** @@ -23,7 +28,9 @@ class SyncLoginsPreferenceView( private val syncLoginsPreference: Preference, lifecycleOwner: LifecycleOwner, accountManager: FxaAccountManager, - private val navController: NavController + private val navController: NavController, + private val accountsAuthFeature: FirefoxAccountsAuthFeature, + private val metrics: MetricController ) { init { @@ -68,7 +75,15 @@ class SyncLoginsPreferenceView( syncLoginsPreference.apply { summary = context.getString(R.string.preferences_passwords_sync_logins_sign_in) setOnPreferenceClickListener { - navigateToTurnOnSyncFragment() + // App can be installed on devices with no camera modules. Like Android TV boxes. + // Let's skip presenting the option to sign in by scanning a qr code in this case + // and default to login with email and password. + if (context.hasCamera()) { + navigateToTurnOnSyncFragment() + } else { + navigateToPairWithEmail(context) + } + true } } @@ -102,4 +117,9 @@ class SyncLoginsPreferenceView( val directions = SavedLoginsAuthFragmentDirections.actionSavedLoginsAuthFragmentToTurnOnSyncFragment() navController.navigate(directions) } + + private fun navigateToPairWithEmail(context: Context) { + accountsAuthFeature.beginAuthentication(context) + metrics.track(Event.SyncAuthUseEmail) + } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/SavedLoginsAuthFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/SavedLoginsAuthFragment.kt index 4246beccd..a7493f99b 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/SavedLoginsAuthFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/SavedLoginsAuthFragment.kt @@ -145,7 +145,9 @@ class SavedLoginsAuthFragment : PreferenceFragmentCompat() { requirePreference(R.string.pref_key_password_sync_logins), lifecycleOwner = viewLifecycleOwner, accountManager = requireComponents.backgroundServices.accountManager, - navController = findNavController() + navController = findNavController(), + accountsAuthFeature = requireComponents.services.accountsAuthFeature, + metrics = requireComponents.analytics.metrics ) togglePrefsEnabledWhileAuthenticating(enabled = true) diff --git a/app/src/test/java/org/mozilla/fenix/settings/logins/SyncLoginsPreferenceViewTest.kt b/app/src/test/java/org/mozilla/fenix/settings/logins/SyncLoginsPreferenceViewTest.kt index a6b68ecb4..fb2de9d7b 100644 --- a/app/src/test/java/org/mozilla/fenix/settings/logins/SyncLoginsPreferenceViewTest.kt +++ b/app/src/test/java/org/mozilla/fenix/settings/logins/SyncLoginsPreferenceViewTest.kt @@ -12,18 +12,24 @@ import io.mockk.impl.annotations.MockK import io.mockk.just import io.mockk.mockk import io.mockk.mockkConstructor +import io.mockk.mockkStatic import io.mockk.slot import io.mockk.unmockkConstructor +import io.mockk.unmockkStatic import io.mockk.verify import mozilla.components.concept.sync.AccountObserver +import mozilla.components.feature.accounts.FirefoxAccountsAuthFeature import mozilla.components.service.fxa.SyncEngine import mozilla.components.service.fxa.manager.FxaAccountManager import mozilla.components.service.fxa.manager.SyncEnginesStorage +import mozilla.components.support.ktx.android.content.hasCamera import org.junit.After import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.mozilla.fenix.R +import org.mozilla.fenix.components.metrics.Event +import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.settings.logins.fragment.SavedLoginsAuthFragmentDirections class SyncLoginsPreferenceViewTest { @@ -32,6 +38,8 @@ class SyncLoginsPreferenceViewTest { @MockK private lateinit var lifecycleOwner: LifecycleOwner @MockK private lateinit var accountManager: FxaAccountManager @MockK(relaxed = true) private lateinit var navController: NavController + @MockK(relaxed = true) private lateinit var accountsAuthFeature: FirefoxAccountsAuthFeature + @MockK(relaxed = true) private lateinit var metrics: MetricController private lateinit var accountObserver: CapturingSlot private lateinit var clickListener: CapturingSlot @@ -87,9 +95,11 @@ class SyncLoginsPreferenceViewTest { } @Test - fun `needs login if account does not exist`() { + fun `needs login if account does not exist and device has camera`() { every { accountManager.authenticatedAccount() } returns null every { accountManager.accountNeedsReauth() } returns false + mockkStatic("mozilla.components.support.ktx.android.content.ContextKt") + every { any().hasCamera() } returns true createView() verify { syncLoginsPreference.summary = "Sign in to Sync" } @@ -100,6 +110,26 @@ class SyncLoginsPreferenceViewTest { SavedLoginsAuthFragmentDirections.actionSavedLoginsAuthFragmentToTurnOnSyncFragment() ) } + + unmockkStatic("mozilla.components.support.ktx.android.content.ContextKt") + } + + @Test + fun `needs login if account does not exist and device does not have camera`() { + every { accountManager.authenticatedAccount() } returns null + every { accountManager.accountNeedsReauth() } returns false + createView() + mockkStatic("mozilla.components.support.ktx.android.content.ContextKt") + every { any().hasCamera() } returns false + + verify { syncLoginsPreference.summary = "Sign in to Sync" } + assertTrue(clickListener.captured.onPreferenceClick(syncLoginsPreference)) + verify { + accountsAuthFeature.beginAuthentication(any()) + metrics.track(Event.SyncAuthUseEmail) + } + + unmockkStatic("mozilla.components.support.ktx.android.content.ContextKt") } @Test @@ -141,6 +171,8 @@ class SyncLoginsPreferenceViewTest { syncLoginsPreference, lifecycleOwner, accountManager, - navController + navController, + accountsAuthFeature, + metrics ) }