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
This commit is contained in:
Kevin Chyn
2020-03-27 10:15:50 -07:00
parent 4f9ab82c08
commit 0a45b662b0
11 changed files with 123 additions and 1 deletions

View File

@@ -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;
}

View File

@@ -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) {}
}
/**

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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 */);

View File

@@ -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();
}

View File

@@ -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.