diff options
| author | 2024-09-24 10:53:32 -0700 | |
|---|---|---|
| committer | 2024-10-02 15:22:43 -0700 | |
| commit | 6ae23ed901cb49ccace53bcd0293ffd5d99c86d9 (patch) | |
| tree | 00d5e21eac501a283c1da7c7bd4c4d8d0459920c | |
| parent | 6c9e8045e11a065fffaba7a3671dbf2c9b025e7c (diff) | |
Support biometric prompt for visible background users
Allow biometric prompt to appear on secondary displays when being
launched for visible background users.
Bug: 365998136
Test: atest com.android.systemui.biometrics.AuthControllerTest
Flag: NONE bugfix
Change-Id: I3a8052c13e810a4301e1e90968664d22a6d38372
4 files changed, 189 insertions, 30 deletions
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index b11961cc2b21..e3fdd267623e 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -139,6 +139,13 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan public static final int DISMISSED_REASON_CONTENT_VIEW_MORE_OPTIONS = 8; /** + * Dialog dismissal due to the system being unable to retrieve a WindowManager instance required + * to show the dialog. + * @hide + */ + public static final int DISMISSED_REASON_ERROR_NO_WM = 9; + + /** * @hide */ @IntDef({DISMISSED_REASON_BIOMETRIC_CONFIRMED, @@ -148,7 +155,8 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan DISMISSED_REASON_ERROR, DISMISSED_REASON_SERVER_REQUESTED, DISMISSED_REASON_CREDENTIAL_CONFIRMED, - DISMISSED_REASON_CONTENT_VIEW_MORE_OPTIONS}) + DISMISSED_REASON_CONTENT_VIEW_MORE_OPTIONS, + DISMISSED_REASON_ERROR_NO_WM}) @Retention(RetentionPolicy.SOURCE) public @interface DismissedReason {} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java index 65825b2444af..2dcbdc80f695 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -17,6 +17,7 @@ package com.android.systemui.biometrics; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; +import static android.view.Display.INVALID_DISPLAY; import static com.google.common.truth.Truth.assertThat; @@ -68,10 +69,12 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback; import android.os.Handler; import android.os.RemoteException; +import android.os.UserHandle; import android.os.UserManager; import android.testing.TestableContext; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; +import android.view.Display; import android.view.DisplayInfo; import android.view.Surface; import android.view.WindowManager; @@ -210,6 +213,7 @@ public class AuthControllerTest extends SysuiTestCase { .thenReturn(true); when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) .thenReturn(true); + when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(false); when(mDialog1.getOpPackageName()).thenReturn("Dialog1"); when(mDialog2.getOpPackageName()).thenReturn("Dialog2"); @@ -462,7 +466,7 @@ public class AuthControllerTest extends SysuiTestCase { @Test public void testShowInvoked_whenSystemRequested() { showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */); - verify(mDialog1).show(any()); + verify(mDialog1).show(mWindowManager); } @Test @@ -679,7 +683,7 @@ public class AuthControllerTest extends SysuiTestCase { // 2) Client cancels authentication showDialog(new int[0] /* sensorIds */, true /* credentialAllowed */); - verify(mDialog1).show(any()); + verify(mDialog1).show(mWindowManager); final byte[] credentialAttestation = generateRandomHAT(); @@ -695,7 +699,7 @@ public class AuthControllerTest extends SysuiTestCase { @Test public void testShowNewDialog_beforeOldDialogDismissed_SkipsAnimations() { showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */); - verify(mDialog1).show(any()); + verify(mDialog1).show(mWindowManager); showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */); @@ -703,7 +707,7 @@ public class AuthControllerTest extends SysuiTestCase { verify(mDialog1).dismissWithoutCallback(eq(false) /* animate */); // Second dialog should be shown without animation - verify(mDialog2).show(any()); + verify(mDialog2).show(mWindowManager); } @Test @@ -990,13 +994,97 @@ public class AuthControllerTest extends SysuiTestCase { verify(mDialog1, never()).show(any()); } + @Test + public void testShowDialog_visibleBackgroundUser() { + int backgroundUserId = 1001; + int backgroundDisplayId = 1001; + when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(true); + WindowManager wm = mockBackgroundUser(backgroundUserId, backgroundDisplayId, + true /* isVisible */, true /* hasUserManager */, true /* hasDisplay */); + + showDialog(new int[]{1} /* sensorIds */, backgroundUserId /* userId */, + false /* credentialAllowed */); + + verify(mDialog1).show(wm); + } + + @Test + public void testShowDialog_invisibleBackgroundUser_defaultWM() { + int backgroundUserId = 1001; + when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(true); + mockBackgroundUser(backgroundUserId, INVALID_DISPLAY, + false /* isVisible */, true /* hasUserManager */, true /* hasDisplay */); + + showDialog(new int[]{1} /* sensorIds */, backgroundUserId /* userId */, + false /* credentialAllowed */); + + verify(mDialog1).show(mWindowManager); + } + + @Test + public void testShowDialog_visibleBackgroundUser_noUserManager_dismissError() + throws RemoteException { + int backgroundUserId = 1001; + int backgroundDisplayId = 1001; + when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(true); + mockBackgroundUser(backgroundUserId, backgroundDisplayId, + true /* isVisible */, false /* hasUserManager */, true /* hasDisplay */); + + showDialog(new int[]{1} /* sensorIds */, backgroundUserId /* userId */, + false /* credentialAllowed */); + + verify(mDialog1, never()).show(any()); + verify(mReceiver).onDialogDismissed( + eq(BiometricPrompt.DISMISSED_REASON_ERROR_NO_WM), + eq(null) /* credentialAttestation */); + } + + @Test + public void testShowDialog_visibleBackgroundUser_invalidDisplayId_dismissError() + throws RemoteException { + int backgroundUserId = 1001; + when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(true); + mockBackgroundUser(backgroundUserId, INVALID_DISPLAY, + true /* isVisible */, true /* hasUserManager */, false /* hasDisplay */); + + showDialog(new int[]{1} /* sensorIds */, backgroundUserId /* userId */, + false /* credentialAllowed */); + + verify(mDialog1, never()).show(any()); + verify(mReceiver).onDialogDismissed( + eq(BiometricPrompt.DISMISSED_REASON_ERROR_NO_WM), + eq(null) /* credentialAttestation */); + } + + @Test + public void testShowDialog_visibleBackgroundUser_invalidDisplay_dismissError() + throws RemoteException { + int backgroundUserId = 1001; + int backgroundDisplayId = 1001; + when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(true); + mockBackgroundUser(backgroundUserId, backgroundDisplayId, + true /* isVisible */, true /* hasUserManager */, false /* hasDisplay */); + + showDialog(new int[]{1} /* sensorIds */, backgroundUserId /* userId */, + false /* credentialAllowed */); + + verify(mDialog1, never()).show(any()); + verify(mReceiver).onDialogDismissed( + eq(BiometricPrompt.DISMISSED_REASON_ERROR_NO_WM), + eq(null) /* credentialAttestation */); + } + private void showDialog(int[] sensorIds, boolean credentialAllowed) { + showDialog(sensorIds, 0 /* userId */, credentialAllowed); + } + + private void showDialog(int[] sensorIds, int userId, boolean credentialAllowed) { mAuthController.showAuthenticationDialog(createTestPromptInfo(), mReceiver /* receiver */, sensorIds, credentialAllowed, true /* requireConfirmation */, - 0 /* userId */, + userId /* userId */, 0 /* operationId */, "testPackage", REQUEST_ID); @@ -1059,6 +1147,40 @@ public class AuthControllerTest extends SysuiTestCase { assertTrue(mAuthController.isFaceAuthEnrolled(userId)); } + /** + * Create mocks related to visible background users. + * + * @param userId the user id of the background user to mock + * @param displayId display id of the background user + * @param isVisible whether the background user is a visible background user or not + * @param hasUserManager simulate whether the background user's context will return a mock + * UserManager instance or null + * @param hasDisplay simulate whether the background user's context will return a mock Display + * instance or null + * @return mock WindowManager instance associated with the background user's display context + */ + private WindowManager mockBackgroundUser(int userId, int displayId, boolean isVisible, + boolean hasUserManager, boolean hasDisplay) { + Context mockUserContext = mock(Context.class); + Context mockDisplayContext = mock(Context.class); + UserManager mockUserManager = mock(UserManager.class); + Display mockDisplay = mock(Display.class); + WindowManager mockDisplayWM = mock(WindowManager.class); + doReturn(mockUserContext).when(mContextSpy).createContextAsUser(eq(UserHandle.of(userId)), + anyInt()); + if (hasUserManager) { + when(mockUserContext.getSystemService(UserManager.class)).thenReturn(mockUserManager); + } + when(mockUserManager.isUserVisible()).thenReturn(isVisible); + when(mockUserManager.getMainDisplayIdAssignedToUser()).thenReturn(displayId); + if (hasDisplay) { + when(mDisplayManager.getDisplay(displayId)).thenReturn(mockDisplay); + } + doReturn(mockDisplayContext).when(mContextSpy).createDisplayContext(mockDisplay); + when(mockDisplayContext.getSystemService(WindowManager.class)).thenReturn(mockDisplayWM); + return mockDisplayWM; + } + private final class TestableAuthController extends AuthController { private int mBuildCount = 0; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index b39aae94ed4e..a5bd559dcbf2 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -19,6 +19,7 @@ package com.android.systemui.biometrics; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_REAR; +import static android.view.Display.INVALID_DISPLAY; import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy; @@ -54,6 +55,7 @@ import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; import android.os.Handler; import android.os.RemoteException; +import android.os.UserHandle; import android.os.UserManager; import android.util.Log; import android.util.RotationUtils; @@ -211,9 +213,13 @@ public class AuthController implements } }; - private void closeDialog(String reason) { + private void closeDialog(String reasonString) { + closeDialog(BiometricPrompt.DISMISSED_REASON_USER_CANCEL, reasonString); + } + + private void closeDialog(@DismissedReason int reason, String reasonString) { if (isShowing()) { - Log.i(TAG, "Close BP, reason :" + reason); + Log.i(TAG, "Close BP, reason :" + reasonString); mCurrentDialog.dismissWithoutCallback(true /* animate */); mCurrentDialog = null; @@ -223,8 +229,7 @@ public class AuthController implements try { if (mReceiver != null) { - mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL, - null /* credentialAttestation */); + mReceiver.onDialogDismissed(reason, null /* credentialAttestation */); mReceiver = null; } } catch (RemoteException e) { @@ -251,25 +256,7 @@ public class AuthController implements private void cancelIfOwnerIsNotInForeground() { mExecution.assertIsMainThread(); - if (mCurrentDialog != null) { - try { - mCurrentDialog.dismissWithoutCallback(true /* animate */); - mCurrentDialog = null; - - for (Callback cb : mCallbacks) { - cb.onBiometricPromptDismissed(); - } - - if (mReceiver != null) { - mReceiver.onDialogDismissed( - BiometricPrompt.DISMISSED_REASON_USER_CANCEL, - null /* credentialAttestation */); - mReceiver = null; - } - } catch (RemoteException e) { - Log.e(TAG, "Remote exception", e); - } - } + closeDialog("owner not in foreground"); } /** @@ -1271,8 +1258,42 @@ public class AuthController implements if (!promptInfo.isAllowBackgroundAuthentication() && isOwnerInBackground()) { cancelIfOwnerIsNotInForeground(); } else { - mCurrentDialog.show(mWindowManager); + WindowManager wm = getWindowManagerForUser(userId); + if (wm != null) { + mCurrentDialog.show(wm); + } else { + closeDialog(BiometricPrompt.DISMISSED_REASON_ERROR_NO_WM, + "unable to get WM instance for user"); + } + } + } + + @Nullable + private WindowManager getWindowManagerForUser(int userId) { + if (!mUserManager.isVisibleBackgroundUsersSupported()) { + return mWindowManager; + } + UserManager um = mContext.createContextAsUser(UserHandle.of(userId), + 0 /* flags */).getSystemService(UserManager.class); + if (um == null) { + Log.e(TAG, "unable to get UserManager for user=" + userId); + return null; + } + if (!um.isUserVisible()) { + // not visible user - use default window manager + return mWindowManager; + } + int displayId = um.getMainDisplayIdAssignedToUser(); + if (displayId == INVALID_DISPLAY) { + Log.e(TAG, "unable to get display assigned to user=" + userId); + return null; + } + Display display = mDisplayManager.getDisplay(displayId); + if (display == null) { + Log.e(TAG, "unable to get Display for user=" + userId); + return null; } + return mContext.createDisplayContext(display).getSystemService(WindowManager.class); } private void onDialogDismissed(@DismissedReason int reason) { diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java index abfbddc18e24..3afecf1d8bbf 100644 --- a/services/core/java/com/android/server/biometrics/AuthSession.java +++ b/services/core/java/com/android/server/biometrics/AuthSession.java @@ -879,6 +879,14 @@ public final class AuthSession implements IBinder.DeathRecipient { ); break; + case BiometricPrompt.DISMISSED_REASON_ERROR_NO_WM: + mClientReceiver.onError( + getEligibleModalities(), + BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, + 0 /* vendorCode */ + ); + break; + default: Slog.w(TAG, "Unhandled reason: " + reason); break; |