Merge "Add support for Predictive Back in AuthCredentialPasswordView" into tm-qpr-dev am: b0614930ea

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20223490

Change-Id: Ifd5b9aac71ef86b065ae0a316d59b4dec570afed
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Omar Miatello
2022-11-18 14:44:39 +00:00
committed by Automerger Merge Worker
3 changed files with 70 additions and 23 deletions

View File

@@ -129,6 +129,7 @@ public class AuthContainerView extends LinearLayout
private final float mTranslationY; private final float mTranslationY;
@VisibleForTesting @ContainerState int mContainerState = STATE_UNKNOWN; @VisibleForTesting @ContainerState int mContainerState = STATE_UNKNOWN;
private final Set<Integer> mFailedModalities = new HashSet<Integer>(); private final Set<Integer> mFailedModalities = new HashSet<Integer>();
private OnBackInvokedDispatcher mOnBackInvokedDispatcher;
private final OnBackInvokedCallback mBackCallback = this::onBackInvoked; private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
private final @Background DelayableExecutor mBackgroundExecutor; private final @Background DelayableExecutor mBackgroundExecutor;
@@ -497,9 +498,9 @@ public class AuthContainerView extends LinearLayout
.start(); .start();
}); });
} }
OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher(); mOnBackInvokedDispatcher = findOnBackInvokedDispatcher();
if (dispatcher != null) { if (mOnBackInvokedDispatcher != null) {
dispatcher.registerOnBackInvokedCallback( mOnBackInvokedDispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT, mBackCallback); OnBackInvokedDispatcher.PRIORITY_DEFAULT, mBackCallback);
} }
} }
@@ -600,11 +601,11 @@ public class AuthContainerView extends LinearLayout
@Override @Override
public void onDetachedFromWindow() { public void onDetachedFromWindow() {
OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
if (dispatcher != null) {
findOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mBackCallback);
}
super.onDetachedFromWindow(); super.onDetachedFromWindow();
if (mOnBackInvokedDispatcher != null) {
mOnBackInvokedDispatcher.unregisterOnBackInvokedCallback(mBackCallback);
mOnBackInvokedDispatcher = null;
}
mWakefulnessLifecycle.removeObserver(this); mWakefulnessLifecycle.removeObserver(this);
} }

View File

@@ -35,6 +35,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 com.android.internal.widget.LockPatternChecker; import com.android.internal.widget.LockPatternChecker;
import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternUtils;
@@ -58,6 +60,8 @@ public class AuthCredentialPasswordView extends AuthCredentialView
private ViewGroup mAuthCredentialHeader; private ViewGroup mAuthCredentialHeader;
private ViewGroup mAuthCredentialInput; private ViewGroup mAuthCredentialInput;
private int mBottomInset = 0; private int mBottomInset = 0;
private OnBackInvokedDispatcher mOnBackInvokedDispatcher;
private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
public AuthCredentialPasswordView(Context context, public AuthCredentialPasswordView(Context context,
AttributeSet attrs) { AttributeSet attrs) {
@@ -79,8 +83,7 @@ public class AuthCredentialPasswordView extends AuthCredentialView
return false; return false;
} }
if (event.getAction() == KeyEvent.ACTION_UP) { if (event.getAction() == KeyEvent.ACTION_UP) {
mContainerView.sendEarlyUserCanceled(); onBackInvoked();
mContainerView.animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
} }
return true; return true;
}); });
@@ -88,6 +91,11 @@ public class AuthCredentialPasswordView extends AuthCredentialView
setOnApplyWindowInsetsListener(this); setOnApplyWindowInsetsListener(this);
} }
private void onBackInvoked() {
mContainerView.sendEarlyUserCanceled();
mContainerView.animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
}
@Override @Override
protected void onAttachedToWindow() { protected void onAttachedToWindow() {
super.onAttachedToWindow(); super.onAttachedToWindow();
@@ -100,6 +108,12 @@ public class AuthCredentialPasswordView extends AuthCredentialView
mPasswordField.requestFocus(); mPasswordField.requestFocus();
mPasswordField.scheduleShowSoftInput(); mPasswordField.scheduleShowSoftInput();
mOnBackInvokedDispatcher = findOnBackInvokedDispatcher();
if (mOnBackInvokedDispatcher != null) {
mOnBackInvokedDispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT, mBackCallback);
}
} }
@Override @Override
@@ -136,6 +150,15 @@ public class AuthCredentialPasswordView extends AuthCredentialView
} }
} }
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mOnBackInvokedDispatcher != null) {
mOnBackInvokedDispatcher.unregisterOnBackInvokedCallback(mBackCallback);
mOnBackInvokedDispatcher = null;
}
}
@Override @Override
protected void onCredentialVerified(@NonNull VerifyCredentialResponse response, protected void onCredentialVerified(@NonNull VerifyCredentialResponse response,
int timeoutMs) { int timeoutMs) {

View File

@@ -110,6 +110,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)
@@ -355,20 +370,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
@@ -428,6 +430,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