diff --git a/packages/SystemUI/res/drawable/face_dialog_idle_static.xml b/packages/SystemUI/res/drawable/face_dialog_idle_static.xml new file mode 100644 index 0000000000000000000000000000000000000000..6ad8e83989d3bf303f3ae0cb190c7e47fa3d5779 --- /dev/null +++ b/packages/SystemUI/res/drawable/face_dialog_idle_static.xml @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java index f25b580b13a377edb03c37f8f83d67827a397901..f99587b6cdc2444a813de8aec6b3233864e95ec0 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java @@ -56,11 +56,16 @@ public abstract class BiometricDialogView extends LinearLayout { private static final String KEY_TRY_AGAIN_VISIBILITY = "key_try_again_visibility"; private static final String KEY_CONFIRM_VISIBILITY = "key_confirm_visibility"; + private static final String KEY_STATE = "key_state"; + private static final String KEY_ERROR_TEXT_VISIBILITY = "key_error_text_visibility"; + private static final String KEY_ERROR_TEXT_STRING = "key_error_text_string"; + private static final String KEY_ERROR_TEXT_IS_TEMPORARY = "key_error_text_is_temporary"; + private static final String KEY_ERROR_TEXT_COLOR = "key_error_text_color"; private static final int ANIMATION_DURATION_SHOW = 250; // ms private static final int ANIMATION_DURATION_AWAY = 350; // ms - protected static final int MSG_CLEAR_MESSAGE = 1; + protected static final int MSG_RESET_MESSAGE = 1; protected static final int STATE_IDLE = 0; protected static final int STATE_AUTHENTICATING = 1; @@ -94,7 +99,7 @@ public abstract class BiometricDialogView extends LinearLayout { private Bundle mBundle; private Bundle mRestoredState; - private int mState; + private int mState = STATE_IDLE; private boolean mAnimatingAway; private boolean mWasForceRemoved; private boolean mSkipIntro; @@ -106,7 +111,7 @@ public abstract class BiometricDialogView extends LinearLayout { protected abstract int getIconDescriptionResourceId(); protected abstract int getDelayAfterAuthenticatedDurationMs(); protected abstract boolean shouldGrayAreaDismissDialog(); - protected abstract void handleClearMessage(); + protected abstract void handleResetMessage(); protected abstract void updateIcon(int oldState, int newState); private final Runnable mShowAnimationRunnable = new Runnable() { @@ -132,8 +137,8 @@ public abstract class BiometricDialogView extends LinearLayout { @Override public void handleMessage(Message msg) { switch(msg.what) { - case MSG_CLEAR_MESSAGE: - handleClearMessage(); + case MSG_RESET_MESSAGE: + handleResetMessage(); break; default: Log.e(TAG, "Unhandled message: " + msg.what); @@ -231,14 +236,17 @@ public abstract class BiometricDialogView extends LinearLayout { public void onSaveState(Bundle bundle) { bundle.putInt(KEY_TRY_AGAIN_VISIBILITY, mTryAgainButton.getVisibility()); bundle.putInt(KEY_CONFIRM_VISIBILITY, mPositiveButton.getVisibility()); + bundle.putInt(KEY_STATE, mState); + bundle.putInt(KEY_ERROR_TEXT_VISIBILITY, mErrorText.getVisibility()); + bundle.putCharSequence(KEY_ERROR_TEXT_STRING, mErrorText.getText()); + bundle.putBoolean(KEY_ERROR_TEXT_IS_TEMPORARY, mHandler.hasMessages(MSG_RESET_MESSAGE)); + bundle.putInt(KEY_ERROR_TEXT_COLOR, mErrorText.getCurrentTextColor()); } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); - mErrorText.setText(getHintStringResourceId()); - final ImageView backgroundView = mLayout.findViewById(R.id.background); if (mUserManager.isManagedProfile(mUserId)) { @@ -253,14 +261,18 @@ public abstract class BiometricDialogView extends LinearLayout { } mNegativeButton.setVisibility(View.VISIBLE); - mErrorText.setVisibility(View.VISIBLE); if (RotationUtils.getRotation(mContext) != RotationUtils.ROTATION_NONE) { mDialog.getLayoutParams().width = (int) mDialogWidth; } - mState = STATE_IDLE; - updateState(STATE_AUTHENTICATING); + if (mRestoredState == null) { + updateState(STATE_AUTHENTICATING); + mErrorText.setText(getHintStringResourceId()); + mErrorText.setVisibility(View.VISIBLE); + } else { + updateState(mState); + } CharSequence titleText = mBundle.getCharSequence(BiometricPrompt.KEY_TITLE); @@ -329,7 +341,7 @@ public abstract class BiometricDialogView extends LinearLayout { mWindowManager.removeView(BiometricDialogView.this); mAnimatingAway = false; // Set the icons / text back to normal state - handleClearMessage(); + handleResetMessage(); showTryAgainButton(false /* show */); updateState(STATE_IDLE); } @@ -401,17 +413,17 @@ public abstract class BiometricDialogView extends LinearLayout { // Shows an error/help message protected void showTemporaryMessage(String message) { - mHandler.removeMessages(MSG_CLEAR_MESSAGE); + mHandler.removeMessages(MSG_RESET_MESSAGE); mErrorText.setText(message); mErrorText.setTextColor(mErrorColor); mErrorText.setContentDescription(message); - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_MESSAGE), + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RESET_MESSAGE), BiometricPrompt.HIDE_DIALOG_DELAY); } public void clearTemporaryMessage() { - mHandler.removeMessages(MSG_CLEAR_MESSAGE); - mHandler.obtainMessage(MSG_CLEAR_MESSAGE).sendToTarget(); + mHandler.removeMessages(MSG_RESET_MESSAGE); + mHandler.obtainMessage(MSG_RESET_MESSAGE).sendToTarget(); } /** @@ -442,7 +454,7 @@ public abstract class BiometricDialogView extends LinearLayout { public void updateState(int newState) { if (newState == STATE_PENDING_CONFIRMATION) { - mHandler.removeMessages(MSG_CLEAR_MESSAGE); + mHandler.removeMessages(MSG_RESET_MESSAGE); mErrorText.setVisibility(View.INVISIBLE); mPositiveButton.setVisibility(View.VISIBLE); mPositiveButton.setEnabled(true); @@ -470,6 +482,19 @@ public abstract class BiometricDialogView extends LinearLayout { mRestoredState = bundle; mTryAgainButton.setVisibility(bundle.getInt(KEY_TRY_AGAIN_VISIBILITY)); mPositiveButton.setVisibility(bundle.getInt(KEY_CONFIRM_VISIBILITY)); + mState = bundle.getInt(KEY_STATE); + mErrorText.setText(bundle.getCharSequence(KEY_ERROR_TEXT_STRING)); + mErrorText.setVisibility(bundle.getInt(KEY_ERROR_TEXT_VISIBILITY)); + mErrorText.setTextColor(bundle.getInt(KEY_ERROR_TEXT_COLOR)); + + if (bundle.getBoolean(KEY_ERROR_TEXT_IS_TEMPORARY)) { + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RESET_MESSAGE), + BiometricPrompt.HIDE_DIALOG_DELAY); + } + } + + protected int getState() { + return mState; } public WindowManager.LayoutParams getLayoutParams() { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java index 9679d26d4bfede7189ae213d5fe80334ef1dfac6..dbbb71c93c0327eadc8e87959bd68c22b1e15320 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java @@ -44,6 +44,7 @@ public class FaceDialogView extends BiometricDialogView { private static final String TAG = "FaceDialogView"; private static final String KEY_DIALOG_SIZE = "key_dialog_size"; + private static final String KEY_DIALOG_ANIMATED_IN = "key_dialog_animated_in"; private static final int HIDE_DIALOG_DELAY = 500; // ms private static final int IMPLICIT_Y_PADDING = 16; // dp @@ -78,6 +79,10 @@ public class FaceDialogView extends BiometricDialogView { animateIcon(iconRes, false); } + public void showStatic(int iconRes) { + mBiometricIcon.setImageDrawable(mContext.getDrawable(iconRes)); + } + public void startPulsing() { mLastPulseDirection = false; animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true); @@ -276,13 +281,19 @@ public class FaceDialogView extends BiometricDialogView { public void onSaveState(Bundle bundle) { super.onSaveState(bundle); bundle.putInt(KEY_DIALOG_SIZE, mSize); + bundle.putBoolean(KEY_DIALOG_ANIMATED_IN, mDialogAnimatedIn); } @Override - protected void handleClearMessage() { + protected void handleResetMessage() { mErrorText.setText(getHintStringResourceId()); mErrorText.setTextColor(mTextColor); + if (getState() == STATE_AUTHENTICATING) { + mErrorText.setVisibility(View.VISIBLE); + } else { + mErrorText.setVisibility(View.INVISIBLE); + } } @Override @@ -290,6 +301,7 @@ public class FaceDialogView extends BiometricDialogView { super.restoreState(bundle); // Keep in mind that this happens before onAttachedToWindow() mSize = bundle.getInt(KEY_DIALOG_SIZE); + mDialogAnimatedIn = bundle.getBoolean(KEY_DIALOG_ANIMATED_IN); } /** @@ -386,7 +398,8 @@ public class FaceDialogView extends BiometricDialogView { protected void updateIcon(int oldState, int newState) { mIconController.mState = newState; - if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) { + if (newState == STATE_AUTHENTICATING) { + mHandler.removeCallbacks(mErrorToIdleAnimationRunnable); if (mDialogAnimatedIn) { mIconController.startPulsing(); mErrorText.setVisibility(View.VISIBLE); @@ -397,25 +410,39 @@ public class FaceDialogView extends BiometricDialogView { mIconController.animateOnce(R.drawable.face_dialog_dark_to_checkmark); } else if (oldState == STATE_ERROR && newState == STATE_IDLE) { mIconController.animateOnce(R.drawable.face_dialog_error_to_idle); - } else if (oldState == STATE_ERROR && newState == STATE_AUTHENTICATING) { - mHandler.removeCallbacks(mErrorToIdleAnimationRunnable); - mIconController.startPulsing(); - } else if (oldState == STATE_ERROR && newState == STATE_PENDING_CONFIRMATION) { - mHandler.removeCallbacks(mErrorToIdleAnimationRunnable); - mIconController.animateOnce(R.drawable.face_dialog_wink_from_dark); } else if (oldState == STATE_ERROR && newState == STATE_AUTHENTICATED) { mHandler.removeCallbacks(mErrorToIdleAnimationRunnable); mIconController.animateOnce(R.drawable.face_dialog_dark_to_checkmark); - } else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) { - mIconController.animateOnce(R.drawable.face_dialog_dark_to_error); - mHandler.postDelayed(mErrorToIdleAnimationRunnable, BiometricPrompt.HIDE_DIALOG_DELAY); + } else if (newState == STATE_ERROR) { + // It's easier to only check newState and gate showing the animation on the + // mErrorToIdleAnimationRunnable as a proxy, than add a ton of extra state. For example, + // we may go from error -> error due to configuration change which is valid and we + // should show the animation, or we can go from error -> error by receiving repeated + // acquire messages in which case we do not want to repeatedly start the animation. + if (!mHandler.hasCallbacks(mErrorToIdleAnimationRunnable)) { + mIconController.animateOnce(R.drawable.face_dialog_dark_to_error); + mHandler.postDelayed(mErrorToIdleAnimationRunnable, + BiometricPrompt.HIDE_DIALOG_DELAY); + } } else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) { mIconController.animateOnce(R.drawable.face_dialog_dark_to_checkmark); - } else if (oldState == STATE_AUTHENTICATING && newState == STATE_PENDING_CONFIRMATION) { + } else if (newState == STATE_PENDING_CONFIRMATION) { + mHandler.removeCallbacks(mErrorToIdleAnimationRunnable); mIconController.animateOnce(R.drawable.face_dialog_wink_from_dark); + } else if (newState == STATE_IDLE) { + mIconController.showStatic(R.drawable.face_dialog_idle_static); } else { Log.w(TAG, "Unknown animation from " + oldState + " -> " + newState); } + + // Note that this must be after the newState == STATE_ERROR check above since this affects + // the logic. + if (oldState == STATE_ERROR && newState == STATE_ERROR) { + // Keep the error icon and text around for a while longer if we keep receiving + // STATE_ERROR + mHandler.removeCallbacks(mErrorToIdleAnimationRunnable); + mHandler.postDelayed(mErrorToIdleAnimationRunnable, BiometricPrompt.HIDE_DIALOG_DELAY); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java index 6072664406179c611d8a439f63475c0e34749c95..183933eb395c8983570caf23687d4183d9722c99 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java @@ -38,7 +38,7 @@ public class FingerprintDialogView extends BiometricDialogView { } @Override - protected void handleClearMessage() { + protected void handleResetMessage() { updateState(STATE_AUTHENTICATING); mErrorText.setText(getHintStringResourceId()); mErrorText.setTextColor(mTextColor); @@ -80,9 +80,7 @@ public class FingerprintDialogView extends BiometricDialogView { } protected boolean shouldAnimateForTransition(int oldState, int newState) { - if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) { - return false; - } else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) { + if (newState == STATE_ERROR) { return true; } else if (oldState == STATE_ERROR && newState == STATE_AUTHENTICATING) { return true; @@ -92,6 +90,8 @@ public class FingerprintDialogView extends BiometricDialogView { } else if (oldState == STATE_ERROR && newState == STATE_AUTHENTICATED) { // TODO(b/77328470): add animation when fingerprint is authenticated return false; + } else if (newState == STATE_AUTHENTICATING) { + return false; } return false; } @@ -109,9 +109,7 @@ public class FingerprintDialogView extends BiometricDialogView { protected Drawable getAnimationForTransition(int oldState, int newState) { int iconRes; - if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) { - iconRes = R.drawable.fingerprint_dialog_fp_to_error; - } else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) { + if (newState == STATE_ERROR) { iconRes = R.drawable.fingerprint_dialog_fp_to_error; } else if (oldState == STATE_ERROR && newState == STATE_AUTHENTICATING) { iconRes = R.drawable.fingerprint_dialog_error_to_fp; @@ -121,9 +119,11 @@ public class FingerprintDialogView extends BiometricDialogView { } else if (oldState == STATE_ERROR && newState == STATE_AUTHENTICATED) { // TODO(b/77328470): add animation when fingerprint is authenticated iconRes = R.drawable.fingerprint_dialog_fp_to_error; + } else if (newState == STATE_AUTHENTICATING) { + iconRes = R.drawable.fingerprint_dialog_fp_to_error; } else { return null; } return mContext.getDrawable(iconRes); } -} \ No newline at end of file +} diff --git a/services/core/java/com/android/server/biometrics/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/AuthenticationClient.java index 1e0f2051933cb9323d5e754eb4a0e423ce322801..b899d028869bb6dc27f38ff8c9e991dbbdbc57c0 100644 --- a/services/core/java/com/android/server/biometrics/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/AuthenticationClient.java @@ -63,6 +63,8 @@ public abstract class AuthenticationClient extends ClientMonitor { */ public abstract boolean shouldFrameworkHandleLockout(); + public abstract boolean wasUserDetected(); + public AuthenticationClient(Context context, Constants constants, BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token, BiometricServiceBase.ServiceListener listener, int targetUserId, int groupId, long opId, @@ -105,6 +107,10 @@ public abstract class AuthenticationClient extends ClientMonitor { if (!shouldFrameworkHandleLockout()) { switch (error) { case BiometricConstants.BIOMETRIC_ERROR_TIMEOUT: + if (!wasUserDetected() && !isBiometricPrompt()) { + // No vibration if user was not detected on keyguard + break; + } case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT: case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT: if (mStarted) { diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java index feb58a3ad5cea5cc809fcc3ff0bc7a9511b6d9e0..463a49931ae1cbe309811413affbc632d1fd82b8 100644 --- a/services/core/java/com/android/server/biometrics/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/face/FaceService.java @@ -64,8 +64,8 @@ import com.android.server.SystemServerInitThreadPool; import com.android.server.biometrics.AuthenticationClient; import com.android.server.biometrics.BiometricServiceBase; import com.android.server.biometrics.BiometricUtils; -import com.android.server.biometrics.EnumerateClient; import com.android.server.biometrics.Constants; +import com.android.server.biometrics.EnumerateClient; import com.android.server.biometrics.RemovalClient; import org.json.JSONArray; @@ -98,6 +98,8 @@ public class FaceService extends BiometricServiceBase { private static final int CHALLENGE_TIMEOUT_SEC = 600; // 10 minutes private final class FaceAuthClient extends AuthenticationClientImpl { + private int mLastAcquire; + public FaceAuthClient(Context context, DaemonWrapper daemon, long halDeviceId, IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId, @@ -116,6 +118,11 @@ public class FaceService extends BiometricServiceBase { return false; } + @Override + public boolean wasUserDetected() { + return mLastAcquire != FaceManager.FACE_ACQUIRED_NOT_DETECTED; + } + @Override public boolean onAuthenticated(BiometricAuthenticator.Identifier identifier, boolean authenticated, ArrayList token) { @@ -153,6 +160,8 @@ public class FaceService extends BiometricServiceBase { @Override public boolean onAcquired(int acquireInfo, int vendorCode) { + mLastAcquire = acquireInfo; + if (acquireInfo == FaceManager.FACE_ACQUIRED_RECALIBRATE) { final String name = getContext().getString(R.string.face_recalibrate_notification_name); diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java index d91670d20a545e0e861c23ce58ef9f4e0f1efc20..24fd1b7a6daf7bcbdc5b434af65848c4b79724a0 100644 --- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java @@ -67,8 +67,8 @@ import com.android.server.biometrics.AuthenticationClient; import com.android.server.biometrics.BiometricServiceBase; import com.android.server.biometrics.BiometricUtils; import com.android.server.biometrics.ClientMonitor; -import com.android.server.biometrics.EnumerateClient; import com.android.server.biometrics.Constants; +import com.android.server.biometrics.EnumerateClient; import com.android.server.biometrics.RemovalClient; import org.json.JSONArray; @@ -152,6 +152,12 @@ public class FingerprintService extends BiometricServiceBase { return true; } + @Override + public boolean wasUserDetected() { + // TODO: Return a proper value for devices that use ERROR_TIMEOUT + return false; + } + @Override public int handleFailedAttempt() { final int currentUser = ActivityManager.getCurrentUser();