Loading packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt +25 −6 Original line number Diff line number Diff line Loading @@ -6,6 +6,8 @@ import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager import android.widget.ImeAwareEditText import android.widget.TextView import android.window.OnBackInvokedCallback import android.window.OnBackInvokedDispatcher import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.R Loading @@ -13,7 +15,7 @@ import com.android.systemui.biometrics.ui.CredentialPasswordView import com.android.systemui.biometrics.ui.CredentialView import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel import com.android.systemui.lifecycle.repeatWhenAttached import kotlinx.coroutines.flow.collect import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.launch /** Sub-binder for the [CredentialPasswordView]. */ Loading @@ -29,6 +31,8 @@ object CredentialPasswordViewBinder { val passwordField: ImeAwareEditText = view.requireViewById(R.id.lockPassword) val onBackInvokedCallback = OnBackInvokedCallback { host.onCredentialAborted() } view.repeatWhenAttached { passwordField.requestFocus() passwordField.scheduleShowSoftInput() Loading @@ -43,9 +47,7 @@ object CredentialPasswordViewBinder { launch { viewModel.checkCredential(text, header) } } ) passwordField.setOnKeyListener( OnBackButtonListener { host.onCredentialAborted() } ) passwordField.setOnKeyListener(OnBackButtonListener(onBackInvokedCallback)) } } Loading @@ -66,18 +68,35 @@ object CredentialPasswordViewBinder { } } } val onBackInvokedDispatcher = view.findOnBackInvokedDispatcher() if (onBackInvokedDispatcher != null) { launch { onBackInvokedDispatcher.registerOnBackInvokedCallback( OnBackInvokedDispatcher.PRIORITY_DEFAULT, onBackInvokedCallback ) awaitCancellation() } .invokeOnCompletion { onBackInvokedDispatcher.unregisterOnBackInvokedCallback( onBackInvokedCallback ) } } } } } } private class OnBackButtonListener(private val onBack: () -> Unit) : View.OnKeyListener { private class OnBackButtonListener(private val onBackInvokedCallback: OnBackInvokedCallback) : View.OnKeyListener { override fun onKey(v: View, keyCode: Int, event: KeyEvent): Boolean { if (keyCode != KeyEvent.KEYCODE_BACK) { return false } if (event.action == KeyEvent.ACTION_UP) { onBack() onBackInvokedCallback.onBackInvoked() } return true } Loading packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +37 −14 Original line number Diff line number Diff line Loading @@ -124,6 +124,21 @@ class AuthContainerViewTest : SysuiTestCase() { assertThat(root.isAttachedToWindow).isFalse() } @Test fun testCredentialPasswordDismissesOnBack() { val container = initializeCredentialPasswordContainer(addToView = true) assertThat(container.parent).isNotNull() val root = container.rootView // Simulate back invocation container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK)) container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK)) waitForIdleSync() assertThat(container.parent).isNull() assertThat(root.isAttachedToWindow).isFalse() } @Test fun testIgnoresAnimatedInWhenDismissed() { val container = initializeFingerprintContainer(addToView = false) Loading Loading @@ -369,20 +384,7 @@ class AuthContainerViewTest : SysuiTestCase() { @Test fun testCredentialUI_disablesClickingOnBackground() { whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20) whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20))).thenReturn( DevicePolicyManager.PASSWORD_QUALITY_NUMERIC ) // In the credential view, clicking on the background (to cancel authentication) is not // valid. Thus, the listener should be null, and it should not be in the accessibility // hierarchy. val container = initializeFingerprintContainer( authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL ) waitForIdleSync() assertThat(container.hasCredentialPasswordView()).isTrue() val container = initializeCredentialPasswordContainer() assertThat(container.hasBiometricPrompt()).isFalse() assertThat( container.findViewById<View>(R.id.background)?.isImportantForAccessibility Loading Loading @@ -442,6 +444,27 @@ class AuthContainerViewTest : SysuiTestCase() { verify(callback).onTryAgainPressed(authContainer?.requestId ?: 0L) } private fun initializeCredentialPasswordContainer( addToView: Boolean = true, ): TestAuthContainerView { whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20) whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20))).thenReturn( DevicePolicyManager.PASSWORD_QUALITY_NUMERIC ) // In the credential view, clicking on the background (to cancel authentication) is not // valid. Thus, the listener should be null, and it should not be in the accessibility // hierarchy. val container = initializeFingerprintContainer( authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL, addToView = addToView, ) waitForIdleSync() assertThat(container.hasCredentialPasswordView()).isTrue() return container } private fun initializeFingerprintContainer( authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK, addToView: Boolean = true Loading Loading
packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt +25 −6 Original line number Diff line number Diff line Loading @@ -6,6 +6,8 @@ import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager import android.widget.ImeAwareEditText import android.widget.TextView import android.window.OnBackInvokedCallback import android.window.OnBackInvokedDispatcher import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.R Loading @@ -13,7 +15,7 @@ import com.android.systemui.biometrics.ui.CredentialPasswordView import com.android.systemui.biometrics.ui.CredentialView import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel import com.android.systemui.lifecycle.repeatWhenAttached import kotlinx.coroutines.flow.collect import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.launch /** Sub-binder for the [CredentialPasswordView]. */ Loading @@ -29,6 +31,8 @@ object CredentialPasswordViewBinder { val passwordField: ImeAwareEditText = view.requireViewById(R.id.lockPassword) val onBackInvokedCallback = OnBackInvokedCallback { host.onCredentialAborted() } view.repeatWhenAttached { passwordField.requestFocus() passwordField.scheduleShowSoftInput() Loading @@ -43,9 +47,7 @@ object CredentialPasswordViewBinder { launch { viewModel.checkCredential(text, header) } } ) passwordField.setOnKeyListener( OnBackButtonListener { host.onCredentialAborted() } ) passwordField.setOnKeyListener(OnBackButtonListener(onBackInvokedCallback)) } } Loading @@ -66,18 +68,35 @@ object CredentialPasswordViewBinder { } } } val onBackInvokedDispatcher = view.findOnBackInvokedDispatcher() if (onBackInvokedDispatcher != null) { launch { onBackInvokedDispatcher.registerOnBackInvokedCallback( OnBackInvokedDispatcher.PRIORITY_DEFAULT, onBackInvokedCallback ) awaitCancellation() } .invokeOnCompletion { onBackInvokedDispatcher.unregisterOnBackInvokedCallback( onBackInvokedCallback ) } } } } } } private class OnBackButtonListener(private val onBack: () -> Unit) : View.OnKeyListener { private class OnBackButtonListener(private val onBackInvokedCallback: OnBackInvokedCallback) : View.OnKeyListener { override fun onKey(v: View, keyCode: Int, event: KeyEvent): Boolean { if (keyCode != KeyEvent.KEYCODE_BACK) { return false } if (event.action == KeyEvent.ACTION_UP) { onBack() onBackInvokedCallback.onBackInvoked() } return true } Loading
packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +37 −14 Original line number Diff line number Diff line Loading @@ -124,6 +124,21 @@ class AuthContainerViewTest : SysuiTestCase() { assertThat(root.isAttachedToWindow).isFalse() } @Test fun testCredentialPasswordDismissesOnBack() { val container = initializeCredentialPasswordContainer(addToView = true) assertThat(container.parent).isNotNull() val root = container.rootView // Simulate back invocation container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK)) container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK)) waitForIdleSync() assertThat(container.parent).isNull() assertThat(root.isAttachedToWindow).isFalse() } @Test fun testIgnoresAnimatedInWhenDismissed() { val container = initializeFingerprintContainer(addToView = false) Loading Loading @@ -369,20 +384,7 @@ class AuthContainerViewTest : SysuiTestCase() { @Test fun testCredentialUI_disablesClickingOnBackground() { whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20) whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20))).thenReturn( DevicePolicyManager.PASSWORD_QUALITY_NUMERIC ) // In the credential view, clicking on the background (to cancel authentication) is not // valid. Thus, the listener should be null, and it should not be in the accessibility // hierarchy. val container = initializeFingerprintContainer( authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL ) waitForIdleSync() assertThat(container.hasCredentialPasswordView()).isTrue() val container = initializeCredentialPasswordContainer() assertThat(container.hasBiometricPrompt()).isFalse() assertThat( container.findViewById<View>(R.id.background)?.isImportantForAccessibility Loading Loading @@ -442,6 +444,27 @@ class AuthContainerViewTest : SysuiTestCase() { verify(callback).onTryAgainPressed(authContainer?.requestId ?: 0L) } private fun initializeCredentialPasswordContainer( addToView: Boolean = true, ): TestAuthContainerView { whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20) whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20))).thenReturn( DevicePolicyManager.PASSWORD_QUALITY_NUMERIC ) // In the credential view, clicking on the background (to cancel authentication) is not // valid. Thus, the listener should be null, and it should not be in the accessibility // hierarchy. val container = initializeFingerprintContainer( authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL, addToView = addToView, ) waitForIdleSync() assertThat(container.hasCredentialPasswordView()).isTrue() return container } private fun initializeFingerprintContainer( authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK, addToView: Boolean = true Loading