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.