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