Loading packages/SystemUI/res/values/config.xml +6 −0 Original line number Diff line number Diff line Loading @@ -611,6 +611,12 @@ 2 - Override the setting to never bypass keyguard --> <integer name="config_face_unlock_bypass_override">0</integer> <!-- Which face help messages to surface when fingerprint is also enrolled. Message ids correspond with the acquired ids in BiometricFaceConstants --> <integer-array name="config_face_help_msgs_when_fingerprint_enrolled"> <!-- for example: <item>26</item> for FACE_ACQUIRED_MOUTH_COVERING_DETECTED --> </integer-array> <!-- Whether the communal service should be enabled --> <bool name="config_communalServiceEnabled">false</bool> Loading packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +0 −1 Original line number Diff line number Diff line Loading @@ -275,7 +275,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab HashMap<Integer, SimData> mSimDatas = new HashMap<>(); HashMap<Integer, ServiceState> mServiceStates = new HashMap<Integer, ServiceState>(); private int mRingMode; private int mPhoneState; private boolean mKeyguardIsVisible; private boolean mCredentialAttempted; Loading packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +46 −7 Original line number Diff line number Diff line Loading @@ -49,12 +49,14 @@ import android.hardware.biometrics.BiometricSourceType; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import android.os.BatteryManager; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.text.TextUtils; import android.text.format.Formatter; import android.util.Log; Loading Loading @@ -92,6 +94,8 @@ import com.android.systemui.util.wakelock.WakeLock; import java.io.PrintWriter; import java.text.NumberFormat; import java.util.HashSet; import java.util.Set; import javax.inject.Inject; Loading @@ -112,12 +116,12 @@ public class KeyguardIndicationController { private static final String TAG = "KeyguardIndication"; private static final boolean DEBUG_CHARGING_SPEED = false; private static final boolean DEBUG = Build.IS_DEBUGGABLE; private static final int MSG_HIDE_TRANSIENT = 1; private static final int MSG_SHOW_ACTION_TO_UNLOCK = 2; private static final int MSG_HIDE_BIOMETRIC_MESSAGE = 3; private static final long TRANSIENT_BIOMETRIC_ERROR_TIMEOUT = 1300; private static final float BOUNCE_ANIMATION_FINAL_Y = 0f; private final Context mContext; private final BroadcastDispatcher mBroadcastDispatcher; Loading Loading @@ -166,6 +170,7 @@ public class KeyguardIndicationController { private boolean mBatteryPresent = true; private long mChargingTimeRemaining; private String mMessageToShowOnScreenOn; private final Set<Integer> mCoExFaceHelpMsgIdsToShow; private boolean mInited; private KeyguardUpdateMonitorCallback mUpdateMonitorCallback; Loading Loading @@ -234,6 +239,22 @@ public class KeyguardIndicationController { mScreenLifecycle = screenLifecycle; mScreenLifecycle.addObserver(mScreenObserver); mCoExFaceHelpMsgIdsToShow = new HashSet<>(); final String msgsToShowOverride = Settings.Global.getString(mContext.getContentResolver(), "coex_face_help_msgs"); // TODO: remove after UX testing b/231733975 if (msgsToShowOverride != null) { final String[] msgIds = msgsToShowOverride.split("\\|"); for (String msgId : msgIds) { mCoExFaceHelpMsgIdsToShow.add(Integer.parseInt(msgId)); } } else { int[] msgIds = context.getResources().getIntArray( com.android.systemui.R.array.config_face_help_msgs_when_fingerprint_enrolled); for (int msgId : msgIds) { mCoExFaceHelpMsgIdsToShow.add(msgId); } } mHandler = new Handler(mainLooper) { @Override public void handleMessage(Message msg) { Loading Loading @@ -924,6 +945,7 @@ public class KeyguardIndicationController { mTopIndicationView == null ? null : mTopIndicationView.getText())); pw.println(" computePowerIndication(): " + computePowerIndication()); pw.println(" trustGrantedIndication: " + getTrustGrantedIndication()); pw.println(" mCoExFaceHelpMsgIdsToShow=" + mCoExFaceHelpMsgIdsToShow); mRotateTextViewController.dump(pw, args); } Loading Loading @@ -982,9 +1004,20 @@ public class KeyguardIndicationController { .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)) { return; } boolean showActionToUnlock = msgId == KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; if (mStatusBarKeyguardViewManager.isBouncerShowing()) { if (biometricSourceType == BiometricSourceType.FACE && !showActionToUnlock && mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( KeyguardUpdateMonitor.getCurrentUser()) && !mCoExFaceHelpMsgIdsToShow.contains(msgId)) { if (DEBUG) { Log.d(TAG, "skip showing msgId=" + msgId + " helpString=" + helpString + ", due to co-ex logic"); } return; } else if (mStatusBarKeyguardViewManager.isBouncerShowing()) { mStatusBarKeyguardViewManager.showBouncerMessage(helpString, mInitialTextColorState); } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) { Loading @@ -1001,14 +1034,20 @@ public class KeyguardIndicationController { if (shouldSuppressBiometricError(msgId, biometricSourceType, mKeyguardUpdateMonitor)) { return; } if (msgId == FaceManager.FACE_ERROR_TIMEOUT) { if (mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( KeyguardUpdateMonitor.getCurrentUser())) { // no message if fingerprint is also enrolled if (DEBUG) { Log.d(TAG, "skip showing FACE_ERROR_TIMEOUT due to co-ex logic"); } return; } // The face timeout message is not very actionable, let's ask the user to // manually retry. if (!mStatusBarKeyguardViewManager.isBouncerShowing() && mKeyguardUpdateMonitor.isUdfpsEnrolled() && mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) { showFaceFailedTryFingerprintMsg(msgId, errString); } else if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) { if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) { mStatusBarKeyguardViewManager.showBouncerMessage( mContext.getResources().getString(R.string.keyguard_try_fingerprint), mInitialTextColorState Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +92 −0 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; Loading @@ -59,6 +60,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.UserInfo; import android.graphics.Color; import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.BiometricSourceType; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; Loading Loading @@ -106,6 +108,9 @@ import org.mockito.MockitoAnnotations; import java.text.NumberFormat; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @SmallTest @RunWith(AndroidTestingRunner.class) Loading Loading @@ -577,6 +582,80 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { verify(mStatusBarKeyguardViewManager).showBouncerMessage(eq(message), any()); } @Test public void faceErrorTimeout_whenFingerprintEnrolled_doesNotShowMessage() { createController(); when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( 0)).thenReturn(true); String message = "A message"; mController.setVisible(true); mController.getKeyguardCallback().onBiometricError( FaceManager.FACE_ERROR_TIMEOUT, message, BiometricSourceType.FACE); verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE); } @Test public void doNotSendFaceHelpMessages_fingerprintEnrolled() { createController(); // GIVEN fingerprint enrolled when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( 0)).thenReturn(true); // WHEN help messages received final String helpString = "helpString"; final int[] msgIds = new int[]{ BiometricFaceConstants.FACE_ACQUIRED_FACE_OBSCURED, BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED, BiometricFaceConstants.FACE_ACQUIRED_TOO_RIGHT, BiometricFaceConstants.FACE_ACQUIRED_TOO_LEFT, BiometricFaceConstants.FACE_ACQUIRED_TOO_HIGH, BiometricFaceConstants.FACE_ACQUIRED_TOO_LOW, BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT, BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK }; for (int msgId : msgIds) { mKeyguardUpdateMonitorCallback.onBiometricHelp( msgId, helpString + msgId, BiometricSourceType.FACE); } // THEN no messages shown verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE); } @Test public void sendAllFaceHelpMessages_fingerprintNotEnrolled() { createController(); // GIVEN fingerprint NOT enrolled when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( 0)).thenReturn(false); // WHEN help messages received final Set<CharSequence> helpStrings = new HashSet<>(); final String helpString = "helpString"; final int[] msgIds = new int[]{ BiometricFaceConstants.FACE_ACQUIRED_FACE_OBSCURED, BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED, BiometricFaceConstants.FACE_ACQUIRED_TOO_RIGHT, BiometricFaceConstants.FACE_ACQUIRED_TOO_LEFT, BiometricFaceConstants.FACE_ACQUIRED_TOO_HIGH, BiometricFaceConstants.FACE_ACQUIRED_TOO_LOW, BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT, BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK }; for (int msgId : msgIds) { final String numberedHelpString = helpString + msgId; mKeyguardUpdateMonitorCallback.onBiometricHelp( msgId, numberedHelpString, BiometricSourceType.FACE); helpStrings.add(numberedHelpString); } // THEN message shown for each call verifyIndicationMessages(INDICATION_TYPE_BIOMETRIC_MESSAGE, helpStrings); } @Test public void updateMonitor_listenerUpdatesIndication() { createController(); Loading Loading @@ -850,6 +929,19 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mBroadcastReceiver.onReceive(mContext, new Intent()); } private void verifyIndicationMessages(int type, Set<CharSequence> messages) { verify(mRotateTextViewController, times(messages.size())).updateIndication(eq(type), mKeyguardIndicationCaptor.capture(), anyBoolean()); List<KeyguardIndication> kis = mKeyguardIndicationCaptor.getAllValues(); for (KeyguardIndication ki : kis) { final CharSequence msg = ki.getMessage(); assertTrue(messages.contains(msg)); // check message is shown messages.remove(msg); } assertThat(messages.size()).isEqualTo(0); // check that all messages accounted for (removed) } private void verifyIndicationMessage(int type, String message) { verify(mRotateTextViewController).updateIndication(eq(type), mKeyguardIndicationCaptor.capture(), anyBoolean()); Loading Loading
packages/SystemUI/res/values/config.xml +6 −0 Original line number Diff line number Diff line Loading @@ -611,6 +611,12 @@ 2 - Override the setting to never bypass keyguard --> <integer name="config_face_unlock_bypass_override">0</integer> <!-- Which face help messages to surface when fingerprint is also enrolled. Message ids correspond with the acquired ids in BiometricFaceConstants --> <integer-array name="config_face_help_msgs_when_fingerprint_enrolled"> <!-- for example: <item>26</item> for FACE_ACQUIRED_MOUTH_COVERING_DETECTED --> </integer-array> <!-- Whether the communal service should be enabled --> <bool name="config_communalServiceEnabled">false</bool> Loading
packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +0 −1 Original line number Diff line number Diff line Loading @@ -275,7 +275,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab HashMap<Integer, SimData> mSimDatas = new HashMap<>(); HashMap<Integer, ServiceState> mServiceStates = new HashMap<Integer, ServiceState>(); private int mRingMode; private int mPhoneState; private boolean mKeyguardIsVisible; private boolean mCredentialAttempted; Loading
packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +46 −7 Original line number Diff line number Diff line Loading @@ -49,12 +49,14 @@ import android.hardware.biometrics.BiometricSourceType; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import android.os.BatteryManager; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.text.TextUtils; import android.text.format.Formatter; import android.util.Log; Loading Loading @@ -92,6 +94,8 @@ import com.android.systemui.util.wakelock.WakeLock; import java.io.PrintWriter; import java.text.NumberFormat; import java.util.HashSet; import java.util.Set; import javax.inject.Inject; Loading @@ -112,12 +116,12 @@ public class KeyguardIndicationController { private static final String TAG = "KeyguardIndication"; private static final boolean DEBUG_CHARGING_SPEED = false; private static final boolean DEBUG = Build.IS_DEBUGGABLE; private static final int MSG_HIDE_TRANSIENT = 1; private static final int MSG_SHOW_ACTION_TO_UNLOCK = 2; private static final int MSG_HIDE_BIOMETRIC_MESSAGE = 3; private static final long TRANSIENT_BIOMETRIC_ERROR_TIMEOUT = 1300; private static final float BOUNCE_ANIMATION_FINAL_Y = 0f; private final Context mContext; private final BroadcastDispatcher mBroadcastDispatcher; Loading Loading @@ -166,6 +170,7 @@ public class KeyguardIndicationController { private boolean mBatteryPresent = true; private long mChargingTimeRemaining; private String mMessageToShowOnScreenOn; private final Set<Integer> mCoExFaceHelpMsgIdsToShow; private boolean mInited; private KeyguardUpdateMonitorCallback mUpdateMonitorCallback; Loading Loading @@ -234,6 +239,22 @@ public class KeyguardIndicationController { mScreenLifecycle = screenLifecycle; mScreenLifecycle.addObserver(mScreenObserver); mCoExFaceHelpMsgIdsToShow = new HashSet<>(); final String msgsToShowOverride = Settings.Global.getString(mContext.getContentResolver(), "coex_face_help_msgs"); // TODO: remove after UX testing b/231733975 if (msgsToShowOverride != null) { final String[] msgIds = msgsToShowOverride.split("\\|"); for (String msgId : msgIds) { mCoExFaceHelpMsgIdsToShow.add(Integer.parseInt(msgId)); } } else { int[] msgIds = context.getResources().getIntArray( com.android.systemui.R.array.config_face_help_msgs_when_fingerprint_enrolled); for (int msgId : msgIds) { mCoExFaceHelpMsgIdsToShow.add(msgId); } } mHandler = new Handler(mainLooper) { @Override public void handleMessage(Message msg) { Loading Loading @@ -924,6 +945,7 @@ public class KeyguardIndicationController { mTopIndicationView == null ? null : mTopIndicationView.getText())); pw.println(" computePowerIndication(): " + computePowerIndication()); pw.println(" trustGrantedIndication: " + getTrustGrantedIndication()); pw.println(" mCoExFaceHelpMsgIdsToShow=" + mCoExFaceHelpMsgIdsToShow); mRotateTextViewController.dump(pw, args); } Loading Loading @@ -982,9 +1004,20 @@ public class KeyguardIndicationController { .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)) { return; } boolean showActionToUnlock = msgId == KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; if (mStatusBarKeyguardViewManager.isBouncerShowing()) { if (biometricSourceType == BiometricSourceType.FACE && !showActionToUnlock && mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( KeyguardUpdateMonitor.getCurrentUser()) && !mCoExFaceHelpMsgIdsToShow.contains(msgId)) { if (DEBUG) { Log.d(TAG, "skip showing msgId=" + msgId + " helpString=" + helpString + ", due to co-ex logic"); } return; } else if (mStatusBarKeyguardViewManager.isBouncerShowing()) { mStatusBarKeyguardViewManager.showBouncerMessage(helpString, mInitialTextColorState); } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) { Loading @@ -1001,14 +1034,20 @@ public class KeyguardIndicationController { if (shouldSuppressBiometricError(msgId, biometricSourceType, mKeyguardUpdateMonitor)) { return; } if (msgId == FaceManager.FACE_ERROR_TIMEOUT) { if (mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( KeyguardUpdateMonitor.getCurrentUser())) { // no message if fingerprint is also enrolled if (DEBUG) { Log.d(TAG, "skip showing FACE_ERROR_TIMEOUT due to co-ex logic"); } return; } // The face timeout message is not very actionable, let's ask the user to // manually retry. if (!mStatusBarKeyguardViewManager.isBouncerShowing() && mKeyguardUpdateMonitor.isUdfpsEnrolled() && mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) { showFaceFailedTryFingerprintMsg(msgId, errString); } else if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) { if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) { mStatusBarKeyguardViewManager.showBouncerMessage( mContext.getResources().getString(R.string.keyguard_try_fingerprint), mInitialTextColorState Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +92 −0 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; Loading @@ -59,6 +60,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.UserInfo; import android.graphics.Color; import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.BiometricSourceType; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; Loading Loading @@ -106,6 +108,9 @@ import org.mockito.MockitoAnnotations; import java.text.NumberFormat; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @SmallTest @RunWith(AndroidTestingRunner.class) Loading Loading @@ -577,6 +582,80 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { verify(mStatusBarKeyguardViewManager).showBouncerMessage(eq(message), any()); } @Test public void faceErrorTimeout_whenFingerprintEnrolled_doesNotShowMessage() { createController(); when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( 0)).thenReturn(true); String message = "A message"; mController.setVisible(true); mController.getKeyguardCallback().onBiometricError( FaceManager.FACE_ERROR_TIMEOUT, message, BiometricSourceType.FACE); verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE); } @Test public void doNotSendFaceHelpMessages_fingerprintEnrolled() { createController(); // GIVEN fingerprint enrolled when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( 0)).thenReturn(true); // WHEN help messages received final String helpString = "helpString"; final int[] msgIds = new int[]{ BiometricFaceConstants.FACE_ACQUIRED_FACE_OBSCURED, BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED, BiometricFaceConstants.FACE_ACQUIRED_TOO_RIGHT, BiometricFaceConstants.FACE_ACQUIRED_TOO_LEFT, BiometricFaceConstants.FACE_ACQUIRED_TOO_HIGH, BiometricFaceConstants.FACE_ACQUIRED_TOO_LOW, BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT, BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK }; for (int msgId : msgIds) { mKeyguardUpdateMonitorCallback.onBiometricHelp( msgId, helpString + msgId, BiometricSourceType.FACE); } // THEN no messages shown verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE); } @Test public void sendAllFaceHelpMessages_fingerprintNotEnrolled() { createController(); // GIVEN fingerprint NOT enrolled when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( 0)).thenReturn(false); // WHEN help messages received final Set<CharSequence> helpStrings = new HashSet<>(); final String helpString = "helpString"; final int[] msgIds = new int[]{ BiometricFaceConstants.FACE_ACQUIRED_FACE_OBSCURED, BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED, BiometricFaceConstants.FACE_ACQUIRED_TOO_RIGHT, BiometricFaceConstants.FACE_ACQUIRED_TOO_LEFT, BiometricFaceConstants.FACE_ACQUIRED_TOO_HIGH, BiometricFaceConstants.FACE_ACQUIRED_TOO_LOW, BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT, BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK }; for (int msgId : msgIds) { final String numberedHelpString = helpString + msgId; mKeyguardUpdateMonitorCallback.onBiometricHelp( msgId, numberedHelpString, BiometricSourceType.FACE); helpStrings.add(numberedHelpString); } // THEN message shown for each call verifyIndicationMessages(INDICATION_TYPE_BIOMETRIC_MESSAGE, helpStrings); } @Test public void updateMonitor_listenerUpdatesIndication() { createController(); Loading Loading @@ -850,6 +929,19 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mBroadcastReceiver.onReceive(mContext, new Intent()); } private void verifyIndicationMessages(int type, Set<CharSequence> messages) { verify(mRotateTextViewController, times(messages.size())).updateIndication(eq(type), mKeyguardIndicationCaptor.capture(), anyBoolean()); List<KeyguardIndication> kis = mKeyguardIndicationCaptor.getAllValues(); for (KeyguardIndication ki : kis) { final CharSequence msg = ki.getMessage(); assertTrue(messages.contains(msg)); // check message is shown messages.remove(msg); } assertThat(messages.size()).isEqualTo(0); // check that all messages accounted for (removed) } private void verifyIndicationMessage(int type, String message) { verify(mRotateTextViewController).updateIndication(eq(type), mKeyguardIndicationCaptor.capture(), anyBoolean()); Loading