From 0a45b662b035661dce8e5e267bbb705326cd14c8 Mon Sep 17 00:00:00 2001 From: Kevin Chyn Date: Fri, 27 Mar 2020 10:15:50 -0700 Subject: [PATCH] Send "early canceled" to privileged clients The navigation bar dismissal animation caused by "activity finish" should be invoked in some cases immediately when the user cancels authentication. Add a "early user cancel" message for ConfirmDeviceCredentialActivity to subscribe to. This message is sent immediately when the user invokes a back gesture or cancels authentication. Bug: 148273355 Test: Set up work profile with separate password and biometric Unlock work profile Lock screen Open work profile app Cancel authentication at various states in various ways Notice no navigation bar jank Test: atest com.android.systemui.biometrics Change-Id: I89c5b5e2782339cae15f936268e6e7b8ad4e5359 --- .../biometrics/BiometricConstants.java | 14 +++++++ .../hardware/biometrics/BiometricPrompt.java | 37 +++++++++++++++++++ .../biometrics/IBiometricServiceReceiver.aidl | 2 + .../IBiometricServiceReceiverInternal.aidl | 2 + .../biometrics/AuthContainerView.java | 9 +++++ .../systemui/biometrics/AuthController.java | 13 +++++++ .../AuthCredentialPasswordView.java | 2 + .../biometrics/AuthDialogCallback.java | 7 ++++ .../biometrics/AuthContainerViewTest.java | 3 ++ .../server/biometrics/AuthService.java | 3 +- .../server/biometrics/BiometricService.java | 32 ++++++++++++++++ 11 files changed, 123 insertions(+), 1 deletion(-) diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java index add67aa436c64..8e3f809e4f880 100644 --- a/core/java/android/hardware/biometrics/BiometricConstants.java +++ b/core/java/android/hardware/biometrics/BiometricConstants.java @@ -204,4 +204,18 @@ public interface BiometricConstants { * @hide */ int BIOMETRIC_ACQUIRED_VENDOR_BASE = 1000; + + // + // Internal messages. + // + + /** + * See {@link BiometricPrompt.Builder#setReceiveSystemEvents(boolean)}. This message is sent + * immediately when the user cancels authentication for example by tapping the back button or + * tapping the scrim. This is before {@link #BIOMETRIC_ERROR_USER_CANCELED}, which is sent when + * dismissal animation completes. + * @hide + */ + int BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL = 1; + } diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index a3aa258fec351..5af7cef3e2b48 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -63,6 +63,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan /** * @hide */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) public static final String KEY_USE_DEFAULT_TITLE = "use_default_title"; /** * @hide @@ -75,14 +76,17 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan /** * @hide */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) public static final String KEY_DEVICE_CREDENTIAL_TITLE = "device_credential_title"; /** * @hide */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) public static final String KEY_DEVICE_CREDENTIAL_SUBTITLE = "device_credential_subtitle"; /** * @hide */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) public static final String KEY_DEVICE_CREDENTIAL_DESCRIPTION = "device_credential_description"; /** * @hide @@ -106,7 +110,15 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * If this is set, check the Device Policy Manager for allowed biometrics. * @hide */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) public static final String EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS = "check_dpm"; + /** + * Request to receive system events, such as back gesture/button. See + * {@link AuthenticationCallback#onSystemEvent(int)} + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public static final String KEY_RECEIVE_SYSTEM_EVENTS = "receive_system_events"; /** * Error/help message will show for this amount of time. @@ -383,6 +395,18 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan return this; } + /** + * If set, receive internal events via {@link AuthenticationCallback#onSystemEvent(int)} + * @param set + * @return This builder. + * @hide + */ + @NonNull + public Builder setReceiveSystemEvents(boolean set) { + mBundle.putBoolean(KEY_RECEIVE_SYSTEM_EVENTS, set); + return this; + } + /** * Creates a {@link BiometricPrompt}. * @@ -493,6 +517,13 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan }); } } + + @Override + public void onSystemEvent(int event) throws RemoteException { + mExecutor.execute(() -> { + mAuthenticationCallback.onSystemEvent(event); + }); + } }; private BiometricPrompt(Context context, Bundle bundle, @@ -732,6 +763,12 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan */ @Override public void onAuthenticationAcquired(int acquireInfo) {} + + /** + * Receiver for internal system events. See {@link Builder#setReceiveSystemEvents(boolean)} + * @hide + */ + public void onSystemEvent(int event) {} } /** diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl index 1d43aa640b402..b0cddfd3b47f9 100644 --- a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl +++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl @@ -30,4 +30,6 @@ oneway interface IBiometricServiceReceiver { void onAcquired(int acquiredInfo, String message); // Notifies that the SystemUI dialog has been dismissed. void onDialogDismissed(int reason); + // Notifies the client that an internal event, e.g. back button has occurred. + void onSystemEvent(int event); } diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl index e7219caf6cd8f..e57abd548057f 100644 --- a/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl +++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl @@ -42,4 +42,6 @@ oneway interface IBiometricServiceReceiverInternal { void onTryAgainPressed(); // Notifies that the user has pressed the "use password" button on SystemUI void onDeviceCredentialPressed(); + // Notifies the client that an internal event, e.g. back button has occurred. + void onSystemEvent(int event); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 0018d33bdacbc..b736b4df8abf3 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.content.Context; import android.graphics.PixelFormat; import android.hardware.biometrics.BiometricAuthenticator; +import android.hardware.biometrics.BiometricConstants; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -207,6 +208,7 @@ public class AuthContainerView extends LinearLayout animateAway(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED); break; case AuthBiometricView.Callback.ACTION_USER_CANCELED: + sendEarlyUserCanceled(); animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED); break; case AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE: @@ -286,11 +288,13 @@ public class AuthContainerView extends LinearLayout addView(mFrameLayout); + // TODO: De-dupe the logic with AuthCredentialPasswordView setOnKeyListener((v, keyCode, event) -> { if (keyCode != KeyEvent.KEYCODE_BACK) { return false; } if (event.getAction() == KeyEvent.ACTION_UP) { + sendEarlyUserCanceled(); animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED); } return true; @@ -300,6 +304,11 @@ public class AuthContainerView extends LinearLayout requestFocus(); } + void sendEarlyUserCanceled() { + mConfig.mCallback.onSystemEvent( + BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL); + } + @Override public boolean isAllowDeviceCredentials() { return Utils.isDeviceCredentialAllowed(mConfig.mBiometricPromptBundle); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index c30477c77bbba..0c6794c2ab858 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -164,6 +164,19 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, } } + @Override + public void onSystemEvent(int event) { + if (mReceiver == null) { + Log.e(TAG, "onSystemEvent(" + event + "): Receiver is null"); + return; + } + try { + mReceiver.onSystemEvent(event); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException when sending system event", e); + } + } + @Override public void onDismissed(@DismissedReason int reason, @Nullable byte[] credentialAttestation) { switch (reason) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java index b986f6c9e6802..d8a11d36a3359 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java @@ -51,11 +51,13 @@ public class AuthCredentialPasswordView extends AuthCredentialView super.onFinishInflate(); mPasswordField = findViewById(R.id.lockPassword); mPasswordField.setOnEditorActionListener(this); + // TODO: De-dupe the logic with AuthContainerView mPasswordField.setOnKeyListener((v, keyCode, event) -> { if (keyCode != KeyEvent.KEYCODE_BACK) { return false; } if (event.getAction() == KeyEvent.ACTION_UP) { + mContainerView.sendEarlyUserCanceled(); mContainerView.animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED); } return true; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java index a47621d121226..d3bd4fbd921c4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java @@ -58,4 +58,11 @@ public interface AuthDialogCallback { * Invoked when the "use password" button is clicked */ void onDeviceCredentialPressed(); + + /** + * See {@link android.hardware.biometrics.BiometricPrompt.Builder + * #setReceiveSystemEvents(boolean)} + * @param event + */ + void onSystemEvent(int event); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java index 1db8e4c3d73ec..74d0610ee0d66 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java @@ -34,6 +34,7 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; +import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricPrompt; import android.os.Bundle; import android.os.IBinder; @@ -89,6 +90,8 @@ public class AuthContainerViewTest extends SysuiTestCase { mAuthContainer.mBiometricCallback.onAction( AuthBiometricView.Callback.ACTION_USER_CANCELED); + verify(mCallback).onSystemEvent(eq( + BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL)); verify(mCallback).onDismissed( eq(AuthDialogCallback.DISMISSED_USER_CANCELED), eq(null) /* credentialAttestation */); diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index ff8e3a973641e..a0876c063fb32 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -158,7 +158,8 @@ public class AuthService extends SystemService { || bundle.getCharSequence( BiometricPrompt.KEY_DEVICE_CREDENTIAL_SUBTITLE) != null || bundle.getCharSequence( - BiometricPrompt.KEY_DEVICE_CREDENTIAL_DESCRIPTION) != null) { + BiometricPrompt.KEY_DEVICE_CREDENTIAL_DESCRIPTION) != null + || bundle.getBoolean(BiometricPrompt.KEY_RECEIVE_SYSTEM_EVENTS, false)) { checkInternalPermission(); } diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 233416d663d9a..d49b590b8a4d5 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -112,6 +112,7 @@ public class BiometricService extends SystemService { private static final int MSG_CANCEL_AUTHENTICATION = 10; private static final int MSG_ON_AUTHENTICATION_TIMED_OUT = 11; private static final int MSG_ON_DEVICE_CREDENTIAL_PRESSED = 12; + private static final int MSG_ON_SYSTEM_EVENT = 13; /** * Authentication either just called and we have not transitioned to the CALLED state, or @@ -360,6 +361,11 @@ public class BiometricService extends SystemService { break; } + case MSG_ON_SYSTEM_EVENT: { + handleOnSystemEvent((int) msg.obj); + break; + } + default: Slog.e(TAG, "Unknown message: " + msg); break; @@ -632,6 +638,11 @@ public class BiometricService extends SystemService { public void onDeviceCredentialPressed() { mHandler.sendEmptyMessage(MSG_ON_DEVICE_CREDENTIAL_PRESSED); } + + @Override + public void onSystemEvent(int event) { + mHandler.obtainMessage(MSG_ON_SYSTEM_EVENT, event).sendToTarget(); + } }; @@ -1579,6 +1590,27 @@ public class BiometricService extends SystemService { mCurrentAuthSession.mState = STATE_SHOWING_DEVICE_CREDENTIAL; } + private void handleOnSystemEvent(int event) { + final boolean shouldReceive = mCurrentAuthSession.mBundle + .getBoolean(BiometricPrompt.KEY_RECEIVE_SYSTEM_EVENTS, false); + Slog.d(TAG, "onSystemEvent: " + event + ", shouldReceive: " + shouldReceive); + + if (mCurrentAuthSession == null) { + Slog.e(TAG, "Auth session null"); + return; + } + + if (!shouldReceive) { + return; + } + + try { + mCurrentAuthSession.mClientReceiver.onSystemEvent(event); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException", e); + } + } + /** * Invoked when each service has notified that its client is ready to be started. When * all biometrics are ready, this invokes the SystemUI dialog through StatusBar.