diff --git a/app/build.gradle b/app/build.gradle index 69f20465f..f4a0fed6b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -185,6 +185,7 @@ dependencies { implementation Deps.mozilla_feature_tabs implementation Deps.mozilla_feature_downloads implementation Deps.mozilla_feature_prompts + implementation Deps.mozilla_feature_qr implementation Deps.mozilla_ui_autocomplete implementation Deps.mozilla_ui_colors @@ -192,6 +193,7 @@ dependencies { implementation Deps.mozilla_service_firefox_accounts implementation Deps.mozilla_service_glean + implementation Deps.mozilla_support_base implementation Deps.mozilla_support_utils implementation Deps.mozilla_support_ktx implementation Deps.mozilla_support_rustlog diff --git a/app/src/main/java/org/mozilla/reference/browser/AppPermissionCodes.kt b/app/src/main/java/org/mozilla/reference/browser/AppPermissionCodes.kt new file mode 100644 index 000000000..c0bf3f9d4 --- /dev/null +++ b/app/src/main/java/org/mozilla/reference/browser/AppPermissionCodes.kt @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.reference.browser + +object AppPermissionCodes { + const val REQUEST_CODE_DOWNLOAD_PERMISSIONS = 1 + const val REQUEST_CODE_PROMPT_PERMISSIONS = 2 + const val REQUEST_CODE_APP_PERMISSIONS = 3 + const val REQUEST_CODE_CAMERA_PERMISSIONS = 4 +} diff --git a/app/src/main/java/org/mozilla/reference/browser/browser/BrowserFragment.kt b/app/src/main/java/org/mozilla/reference/browser/browser/BrowserFragment.kt index c85752b46..0674556c4 100644 --- a/app/src/main/java/org/mozilla/reference/browser/browser/BrowserFragment.kt +++ b/app/src/main/java/org/mozilla/reference/browser/browser/BrowserFragment.kt @@ -24,6 +24,9 @@ import mozilla.components.support.base.feature.BackHandler import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import mozilla.components.support.ktx.android.view.enterToImmersiveMode import mozilla.components.support.ktx.android.view.exitImmersiveModeIfNeeded +import org.mozilla.reference.browser.AppPermissionCodes.REQUEST_CODE_APP_PERMISSIONS +import org.mozilla.reference.browser.AppPermissionCodes.REQUEST_CODE_DOWNLOAD_PERMISSIONS +import org.mozilla.reference.browser.AppPermissionCodes.REQUEST_CODE_PROMPT_PERMISSIONS import org.mozilla.reference.browser.R import org.mozilla.reference.browser.UserInteractionHandler import org.mozilla.reference.browser.ext.requireComponents @@ -253,9 +256,6 @@ class BrowserFragment : Fragment(), BackHandler, UserInteractionHandler { companion object { private const val SESSION_ID = "session_id" - private const val REQUEST_CODE_DOWNLOAD_PERMISSIONS = 1 - private const val REQUEST_CODE_PROMPT_PERMISSIONS = 2 - private const val REQUEST_CODE_APP_PERMISSIONS = 3 fun create(sessionId: String? = null): BrowserFragment = BrowserFragment().apply { arguments = Bundle().apply { diff --git a/app/src/main/java/org/mozilla/reference/browser/components/BackgroundServices.kt b/app/src/main/java/org/mozilla/reference/browser/components/BackgroundServices.kt index 3decdea43..1fe64674f 100644 --- a/app/src/main/java/org/mozilla/reference/browser/components/BackgroundServices.kt +++ b/app/src/main/java/org/mozilla/reference/browser/components/BackgroundServices.kt @@ -25,7 +25,7 @@ class BackgroundServices( companion object { const val CLIENT_ID = "3c49430b43dfba77" const val REDIRECT_URL = "https://accounts.firefox.com/oauth/success/$CLIENT_ID" - const val SUCCESS_PATH = "connect_another_device?showSuccessMessage=true" + const val SUCCESS_PATH = "signin_confirmed" } // This is slightly messy - here we need to know the union of all "scopes" diff --git a/app/src/main/java/org/mozilla/reference/browser/settings/PairSettingsFragment.kt b/app/src/main/java/org/mozilla/reference/browser/settings/PairSettingsFragment.kt new file mode 100644 index 000000000..ed4e0ec91 --- /dev/null +++ b/app/src/main/java/org/mozilla/reference/browser/settings/PairSettingsFragment.kt @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.reference.browser.settings + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import mozilla.components.feature.qr.QrFeature +import mozilla.components.support.base.feature.BackHandler +import mozilla.components.support.base.feature.ViewBoundFeatureWrapper +import org.mozilla.reference.browser.AppPermissionCodes.REQUEST_CODE_CAMERA_PERMISSIONS +import org.mozilla.reference.browser.R +import org.mozilla.reference.browser.ext.requireComponents + +class PairSettingsFragment : Fragment(), BackHandler { + private val qrFeature = ViewBoundFeatureWrapper() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_pairing, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + qrFeature.set( + feature = QrFeature( + requireContext(), + fragmentManager = fragmentManager!!, + onNeedToRequestPermissions = { permissions -> + requestPermissions(permissions, REQUEST_CODE_CAMERA_PERMISSIONS) + }, + onScanResult = { pairingUrl -> + requireComponents.services.accountsAuthFeature.beginPairingAuthentication(pairingUrl) + activity?.finish() + } + ), + owner = this, + view = view + ) + + qrFeature.withFeature { + it.scan(R.id.pair_layout) + } + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + when (requestCode) { + REQUEST_CODE_CAMERA_PERMISSIONS -> qrFeature.withFeature { + it.onPermissionsResult(permissions, grantResults) + } + } + } + + override fun onBackPressed(): Boolean { + qrFeature.onBackPressed() + fragmentManager?.popBackStack() + return true + } +} diff --git a/app/src/main/java/org/mozilla/reference/browser/settings/SettingsActivity.kt b/app/src/main/java/org/mozilla/reference/browser/settings/SettingsActivity.kt index dce8150fd..9469b1205 100644 --- a/app/src/main/java/org/mozilla/reference/browser/settings/SettingsActivity.kt +++ b/app/src/main/java/org/mozilla/reference/browser/settings/SettingsActivity.kt @@ -8,6 +8,7 @@ import android.R.id.content import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.view.MenuItem +import mozilla.components.support.base.feature.BackHandler class SettingsActivity : AppCompatActivity(), SettingsFragment.ActionBarUpdater { @@ -33,4 +34,14 @@ class SettingsActivity : AppCompatActivity(), SettingsFragment.ActionBarUpdater override fun updateTitle(titleResId: Int) { setTitle(titleResId) } + + override fun onBackPressed() { + supportFragmentManager.fragments.forEach { + if (it is BackHandler && it.onBackPressed()) { + return + } else { + super.onBackPressed() + } + } + } } diff --git a/app/src/main/java/org/mozilla/reference/browser/settings/SettingsFragment.kt b/app/src/main/java/org/mozilla/reference/browser/settings/SettingsFragment.kt index f2e1afbd5..e1a88a95f 100644 --- a/app/src/main/java/org/mozilla/reference/browser/settings/SettingsFragment.kt +++ b/app/src/main/java/org/mozilla/reference/browser/settings/SettingsFragment.kt @@ -17,6 +17,7 @@ import org.mozilla.reference.browser.R import org.mozilla.reference.browser.R.string.pref_key_firefox_account import org.mozilla.reference.browser.ext.getPreferenceKey import org.mozilla.reference.browser.R.string.pref_key_sign_in +import org.mozilla.reference.browser.R.string.pref_key_pair_sign_in import org.mozilla.reference.browser.R.string.pref_key_make_default_browser import org.mozilla.reference.browser.R.string.pref_key_remote_debugging import org.mozilla.reference.browser.R.string.pref_key_about_page @@ -50,6 +51,7 @@ class SettingsFragment : PreferenceFragmentCompat() { @Suppress("LongMethod") // Yep, this should be refactored. private fun setupPreferences() { val signInKey = context?.getPreferenceKey(pref_key_sign_in) + val signInPairKey = context?.getPreferenceKey(pref_key_pair_sign_in) val firefoxAccountKey = context?.getPreferenceKey(pref_key_firefox_account) val makeDefaultBrowserKey = context?.getPreferenceKey(pref_key_make_default_browser) val remoteDebuggingKey = context?.getPreferenceKey(pref_key_remote_debugging) @@ -57,6 +59,7 @@ class SettingsFragment : PreferenceFragmentCompat() { val privacyKey = context?.getPreferenceKey(pref_key_privacy) val preferenceSignIn = findPreference(signInKey) + val preferencePairSignIn = findPreference(signInPairKey) val preferenceFirefoxAccount = findPreference(firefoxAccountKey) val preferenceMakeDefaultBrowser = findPreference(makeDefaultBrowserKey) val preferenceRemoteDebugging = findPreference(remoteDebuggingKey) @@ -66,6 +69,7 @@ class SettingsFragment : PreferenceFragmentCompat() { val accountManager = requireComponents.backgroundServices.accountManager if (accountManager.authenticatedAccount() != null) { preferenceSignIn.isVisible = false + preferencePairSignIn.isVisible = false preferenceFirefoxAccount.summary = accountManager.accountProfile()?.email.orEmpty() preferenceFirefoxAccount.onPreferenceClickListener = getClickListenerForFirefoxAccount() } else { @@ -73,6 +77,8 @@ class SettingsFragment : PreferenceFragmentCompat() { preferenceFirefoxAccount.isVisible = false preferenceFirefoxAccount.onPreferenceClickListener = null preferenceSignIn.onPreferenceClickListener = getClickListenerForSignIn() + preferencePairSignIn.isVisible = true + preferencePairSignIn.onPreferenceClickListener = getClickListenerForPairingSignIn() } preferenceMakeDefaultBrowser.onPreferenceClickListener = getClickListenerForMakeDefaultBrowser() @@ -103,6 +109,19 @@ class SettingsFragment : PreferenceFragmentCompat() { } } + private fun getClickListenerForPairingSignIn(): OnPreferenceClickListener { + return OnPreferenceClickListener { + fragmentManager?.beginTransaction() + ?.replace(android.R.id.content, PairSettingsFragment()) + ?.addToBackStack(null) + ?.commit() + getActionBarUpdater().apply { + updateTitle(R.string.pair_preferences) + } + true + } + } + private fun getClickListenerForFirefoxAccount(): OnPreferenceClickListener { return OnPreferenceClickListener { fragmentManager?.beginTransaction() diff --git a/app/src/main/res/layout/fragment_pairing.xml b/app/src/main/res/layout/fragment_pairing.xml new file mode 100644 index 000000000..be47bf0c1 --- /dev/null +++ b/app/src/main/res/layout/fragment_pairing.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index dd2f26e3e..81e637dec 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -4,6 +4,7 @@ - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> pref_key_sign_in + pref_key_pair_sign_in pref_key_sign_out pref_key_sync_now pref_key_firefox_account diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6646057a7..b3604987a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -18,6 +18,9 @@ Sync your history + + Pair with Firefox Desktop + Send usage data @@ -42,6 +45,9 @@ Sign in + + Sign in with a QR code + Firefox Account @@ -99,6 +105,10 @@ Privacy Settings + + Pairing + Open accounts.firefox.com/pair in Firefox Desktop for your pairing code + Sorry. We crashed diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 7ac568f1f..b3d836112 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -10,6 +10,11 @@ android:title="@string/sign_in" android:summary="@string/preferences_sign_in_summary"/> + + diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 1b29b2be2..f4346fe16 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -69,6 +69,7 @@ object Deps { const val mozilla_feature_downloads = "org.mozilla.components:feature-downloads:${Versions.mozilla_android_components}" const val mozilla_feature_storage = "org.mozilla.components:feature-storage:${Versions.mozilla_android_components}" const val mozilla_feature_prompts = "org.mozilla.components:feature-prompts:${Versions.mozilla_android_components}" + const val mozilla_feature_qr = "org.mozilla.components:feature-qr:${Versions.mozilla_android_components}" const val mozilla_ui_autocomplete = "org.mozilla.components:ui-autocomplete:${Versions.mozilla_android_components}" const val mozilla_ui_colors = "org.mozilla.components:ui-colors:${Versions.mozilla_android_components}" @@ -76,6 +77,7 @@ object Deps { const val mozilla_service_firefox_accounts = "org.mozilla.components:service-firefox-accounts:${Versions.mozilla_android_components}" const val mozilla_service_glean = "org.mozilla.components:service-glean:${Versions.mozilla_android_components}" + const val mozilla_support_base = "org.mozilla.components:support-base:${Versions.mozilla_android_components}" const val mozilla_support_utils = "org.mozilla.components:support-utils:${Versions.mozilla_android_components}" const val mozilla_support_ktx= "org.mozilla.components:support-ktx:${Versions.mozilla_android_components}" const val mozilla_support_rustlog = "org.mozilla.components:support-rustlog:${Versions.mozilla_android_components}"