Add support for Predictive Back in CredentialPasswordViewBinder

Test: atest AuthContainerViewTest
Bug: 254450850
Change-Id: I3d71d3f595eba16b2e1cab01d915dd7842717a6e
This commit is contained in:
omarmt
2022-11-09 16:28:53 +00:00
committed by Omar Miatello
parent d7fcc948d6
commit 1417609ab3
2 changed files with 62 additions and 20 deletions

View File

@@ -6,6 +6,8 @@ import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.ImeAwareEditText import android.widget.ImeAwareEditText
import android.widget.TextView import android.widget.TextView
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.R import com.android.systemui.R
@@ -13,7 +15,7 @@ import com.android.systemui.biometrics.ui.CredentialPasswordView
import com.android.systemui.biometrics.ui.CredentialView import com.android.systemui.biometrics.ui.CredentialView
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.lifecycle.repeatWhenAttached
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/** Sub-binder for the [CredentialPasswordView]. */ /** Sub-binder for the [CredentialPasswordView]. */
@@ -29,6 +31,8 @@ object CredentialPasswordViewBinder {
val passwordField: ImeAwareEditText = view.requireViewById(R.id.lockPassword) val passwordField: ImeAwareEditText = view.requireViewById(R.id.lockPassword)
val onBackInvokedCallback = OnBackInvokedCallback { host.onCredentialAborted() }
view.repeatWhenAttached { view.repeatWhenAttached {
passwordField.requestFocus() passwordField.requestFocus()
passwordField.scheduleShowSoftInput() passwordField.scheduleShowSoftInput()
@@ -43,9 +47,7 @@ object CredentialPasswordViewBinder {
launch { viewModel.checkCredential(text, header) } launch { viewModel.checkCredential(text, header) }
} }
) )
passwordField.setOnKeyListener( passwordField.setOnKeyListener(OnBackButtonListener(onBackInvokedCallback))
OnBackButtonListener { host.onCredentialAborted() }
)
} }
} }
@@ -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 { override fun onKey(v: View, keyCode: Int, event: KeyEvent): Boolean {
if (keyCode != KeyEvent.KEYCODE_BACK) { if (keyCode != KeyEvent.KEYCODE_BACK) {
return false return false
} }
if (event.action == KeyEvent.ACTION_UP) { if (event.action == KeyEvent.ACTION_UP) {
onBack() onBackInvokedCallback.onBackInvoked()
} }
return true return true
} }

View File

@@ -124,6 +124,21 @@ class AuthContainerViewTest : SysuiTestCase() {
assertThat(root.isAttachedToWindow).isFalse() 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 @Test
fun testIgnoresAnimatedInWhenDismissed() { fun testIgnoresAnimatedInWhenDismissed() {
val container = initializeFingerprintContainer(addToView = false) val container = initializeFingerprintContainer(addToView = false)
@@ -369,20 +384,7 @@ class AuthContainerViewTest : SysuiTestCase() {
@Test @Test
fun testCredentialUI_disablesClickingOnBackground() { fun testCredentialUI_disablesClickingOnBackground() {
whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20) val container = initializeCredentialPasswordContainer()
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()
assertThat(container.hasBiometricPrompt()).isFalse() assertThat(container.hasBiometricPrompt()).isFalse()
assertThat( assertThat(
container.findViewById<View>(R.id.background)?.isImportantForAccessibility container.findViewById<View>(R.id.background)?.isImportantForAccessibility
@@ -442,6 +444,27 @@ class AuthContainerViewTest : SysuiTestCase() {
verify(callback).onTryAgainPressed(authContainer?.requestId ?: 0L) 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( private fun initializeFingerprintContainer(
authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK, authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
addToView: Boolean = true addToView: Boolean = true