diff --git a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt index 9a363167a..7ab541601 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -4,11 +4,14 @@ package org.mozilla.fenix.browser +import android.app.KeyguardManager import android.content.Context +import android.content.DialogInterface import android.content.Intent import android.content.res.Configuration import android.os.Build import android.os.Bundle +import android.provider.Settings import android.view.Gravity import android.view.LayoutInflater import android.view.View @@ -16,7 +19,9 @@ import android.view.ViewGroup import android.view.accessibility.AccessibilityManager import androidx.annotation.CallSuper import androidx.annotation.VisibleForTesting +import androidx.appcompat.app.AlertDialog import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.core.content.getSystemService import androidx.core.net.toUri import androidx.core.view.isVisible import androidx.fragment.app.Fragment @@ -65,6 +70,7 @@ import mozilla.components.feature.intent.ext.EXTRA_SESSION_ID import mozilla.components.feature.media.fullscreen.MediaSessionFullscreenFeature import mozilla.components.feature.privatemode.feature.SecureWindowFeature import mozilla.components.feature.prompts.PromptFeature +import mozilla.components.feature.prompts.PromptFeature.Companion.PIN_REQUEST import mozilla.components.feature.prompts.share.ShareDelegate import mozilla.components.feature.readerview.ReaderViewFeature import mozilla.components.feature.search.SearchFeature @@ -132,6 +138,8 @@ import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph import mozilla.components.support.ktx.android.view.enterToImmersiveMode import org.mozilla.fenix.GleanMetrics.PerfStartup import org.mozilla.fenix.ext.measureNoInline +import org.mozilla.fenix.ext.secure +import org.mozilla.fenix.settings.biometric.BiometricPromptFeature import mozilla.components.feature.session.behavior.ToolbarPosition as MozacToolbarPosition /** @@ -180,6 +188,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit ViewBoundFeatureWrapper() private val searchFeature = ViewBoundFeatureWrapper() private val webAuthnFeature = ViewBoundFeatureWrapper() + private val biometricPromptFeature = ViewBoundFeatureWrapper() private var pipFeature: PictureInPictureFeature? = null var customTabSessionId: String? = null @@ -533,6 +542,21 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit view = view ) + biometricPromptFeature.set( + feature = BiometricPromptFeature( + context = context, + fragment = this, + onAuthFailure = { + promptsFeature.get()?.onBiometricResult(isAuthenticated = false) + }, + onAuthSuccess = { + promptsFeature.get()?.onBiometricResult(isAuthenticated = true) + } + ), + owner = this, + view = view + ) + promptsFeature.set( feature = PromptFeature( activity = activity, @@ -580,6 +604,9 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit val directions = NavGraphDirections.actionGlobalCreditCardsSettingFragment() findNavController().navigateBlockingForAsyncNavGraph(directions) + }, + onSelectCreditCard = { + showBiometricPrompt(context) } ), owner = this, @@ -731,6 +758,66 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit initializeEngineView(toolbarHeight) } + /** + * Shows a biometric prompt and fallback to prompting for the password. + */ + private fun showBiometricPrompt(context: Context) { + if (BiometricPromptFeature.canUseFeature(context)) { + biometricPromptFeature.get() + ?.requestAuthentication(getString(R.string.credit_cards_biometric_prompt_unlock_message)) + return + } + + // Fallback to prompting for password with the KeyguardManager + val manager = context.getSystemService() + if (manager?.isKeyguardSecure == true) { + showPinVerification(manager) + } else { + // Warn that the device has not been secured + if (context.settings().shouldShowSecurityPinWarning) { + showPinDialogWarning(context) + } else { + promptsFeature.get()?.onBiometricResult(isAuthenticated = true) + } + } + } + + /** + * Shows a pin request prompt. This is only used when BiometricPrompt is unavailable. + */ + @Suppress("Deprecation") + private fun showPinVerification(manager: KeyguardManager) { + val intent = manager.createConfirmDeviceCredentialIntent( + getString(R.string.credit_cards_biometric_prompt_message_pin), + getString(R.string.credit_cards_biometric_prompt_unlock_message) + ) + requireActivity().startActivityForResult(intent, PIN_REQUEST) + } + + /** + * Shows a dialog warning about setting up a device lock PIN. + */ + private fun showPinDialogWarning(context: Context) { + AlertDialog.Builder(context).apply { + setTitle(getString(R.string.credit_cards_warning_dialog_title)) + setMessage(getString(R.string.credit_cards_warning_dialog_message)) + + setNegativeButton(getString(R.string.credit_cards_warning_dialog_later)) { _: DialogInterface, _ -> + promptsFeature.get()?.onBiometricResult(isAuthenticated = false) + } + + setPositiveButton(getString(R.string.credit_cards_warning_dialog_set_up_now)) { it: DialogInterface, _ -> + it.dismiss() + promptsFeature.get()?.onBiometricResult(isAuthenticated = false) + startActivity(Intent(Settings.ACTION_SECURITY_SETTINGS)) + } + + create() + }.show().secure(activity) + + context.settings().incrementSecureWarningCount() + } + @VisibleForTesting internal fun expandToolbarOnNavigation(store: BrowserStore) { consumeFlow(store) { flow -> diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index bb47b15be..bf7f754ce 100644 --- a/buildSrc/src/main/java/AndroidComponents.kt +++ b/buildSrc/src/main/java/AndroidComponents.kt @@ -3,5 +3,5 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ object AndroidComponents { - const val VERSION = "90.0.20210528002854" + const val VERSION = "90.0.20210528204811" }