Revision control

Copy as Markdown

/* 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.focus.biometrics
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.Fragment
import mozilla.components.lib.auth.AuthenticationDelegate
import mozilla.components.lib.auth.BiometricPromptAuth
import mozilla.components.lib.auth.canUseBiometricFeature
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import org.mozilla.focus.R
import org.mozilla.focus.ext.hideToolbar
import org.mozilla.focus.ext.requireComponents
import org.mozilla.focus.searchwidget.ExternalIntentNavigation
import org.mozilla.focus.state.AppAction
import org.mozilla.focus.ui.theme.FocusTheme
/**
* Fragment used to display biometric authentication when the app is locked.
*/
class BiometricAuthenticationFragment : Fragment(), AuthenticationDelegate {
@VisibleForTesting
internal val biometricPromptAuth = ViewBoundFeatureWrapper<BiometricPromptAuth>()
@VisibleForTesting
internal val biometricErrorText = mutableStateOf("")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
return ComposeView(requireContext()).apply {
setBiometricPrompt(this)
setContent {
FocusTheme {
val biometricErrorText by biometricErrorText
BiometricPromptContent(biometricErrorText) {
showBiometricPrompt(
biometricPromptAuth.get(),
getString(R.string.biometric_prompt_title),
getString(R.string.biometric_prompt_subtitle),
)
}
}
}
isTransitionGroup = true
}
}
override fun onResume() {
super.onResume()
hideToolbar()
}
override fun onAuthError(errorText: String) {
biometricErrorText.value = errorText
}
override fun onAuthFailure() {
// onAuthFailure
}
override fun onAuthSuccess() {
onAuthenticated()
}
@VisibleForTesting
internal fun showBiometricPrompt(
biometricPromptAuth: BiometricPromptAuth?,
title: String,
subtitle: String,
) {
if (context?.canUseBiometricFeature() == true) {
biometricPromptAuth?.requestAuthentication(
title = title,
subtitle = subtitle,
)
}
}
private fun setBiometricPrompt(view: View) {
biometricPromptAuth.set(
feature = BiometricPromptAuth(
context = requireContext(),
fragment = this,
authenticationDelegate = this,
),
owner = this,
view = view,
)
}
@VisibleForTesting
internal fun onAuthenticated() {
ExternalIntentNavigation.handleAppNavigation(
bundle = arguments,
context = requireContext(),
)
val tabId = requireComponents.store.state.selectedTabId
requireComponents.appStore.dispatch(AppAction.Unlock(tabId))
dismiss()
}
@VisibleForTesting
internal fun dismiss() {
requireActivity().supportFragmentManager.beginTransaction().remove(this).commitAllowingStateLoss()
}
companion object {
const val FRAGMENT_TAG = "biometric-authentication-fragment"
/**
* Creates a [BiometricAuthenticationFragment] with redirection to a destination from @param [bundle].
*/
fun createWithDestinationData(bundle: Bundle? = null): BiometricAuthenticationFragment {
val fragment = BiometricAuthenticationFragment()
fragment.arguments = bundle
return fragment
}
}
}