diff --git a/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..73ac333cfd89998843ac13a932838e6c55f0ffd3 --- /dev/null +++ b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.biometrics; + +/** + * Low-level callback interface between <Biometric>Manager and <Auth>Service. Allows core system + * services (e.g. SystemUI) to register a listener for updates about the current state of biometric + * authentication. + * @hide + */ +oneway interface AuthenticationStateListener { + /** + * Defines behavior in response to authentication starting + * @param requestReason reason from [BiometricRequestConstants.RequestReason] for requesting + * authentication starting + */ + void onAuthenticationStarted(int requestReason); + + /** + * Defines behavior in response to authentication stopping + */ + void onAuthenticationStopped(); +} diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index f82f79eba8c91cd5a63ada836cc9be390b7ea4f1..d7d1d1a7c6778027512d98d8996defdd9ed057bc 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -551,6 +551,44 @@ public class BiometricManager { } } + /** + * Registers listener for changes to biometric authentication state. + * Only sends callbacks for events that occur after the callback has been registered. + * @param listener Listener for changes to biometric authentication state + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void registerAuthenticationStateListener(AuthenticationStateListener listener) { + if (mService != null) { + try { + mService.registerAuthenticationStateListener(listener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "registerAuthenticationStateListener(): Service not connected"); + } + } + + /** + * Unregisters listener for changes to biometric authentication state. + * @param listener Listener for changes to biometric authentication state + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void unregisterAuthenticationStateListener(AuthenticationStateListener listener) { + if (mService != null) { + try { + mService.unregisterAuthenticationStateListener(listener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "unregisterAuthenticationStateListener(): Service not connected"); + } + } + + /** * Requests all {@link Authenticators.Types#BIOMETRIC_STRONG} sensors to have their * authenticatorId invalidated for the specified user. This happens when enrollments have been diff --git a/core/java/android/hardware/biometrics/BiometricOverlayConstants.java b/core/java/android/hardware/biometrics/BiometricRequestConstants.java similarity index 69% rename from core/java/android/hardware/biometrics/BiometricOverlayConstants.java rename to core/java/android/hardware/biometrics/BiometricRequestConstants.java index 065ae64a92ad227a58754253ed403e7159a63b1a..b036f309f7df4e114ff0666c74400dfe520adadc 100644 --- a/core/java/android/hardware/biometrics/BiometricOverlayConstants.java +++ b/core/java/android/hardware/biometrics/BiometricRequestConstants.java @@ -22,24 +22,27 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** - * Common constants for biometric overlays. + * Common constants for biometric requests. * @hide */ -public interface BiometricOverlayConstants { +public class BiometricRequestConstants { + + private BiometricRequestConstants() {} + /** Unknown usage. */ - int REASON_UNKNOWN = 0; + public static final int REASON_UNKNOWN = 0; /** User is about to enroll. */ - int REASON_ENROLL_FIND_SENSOR = 1; + public static final int REASON_ENROLL_FIND_SENSOR = 1; /** User is enrolling. */ - int REASON_ENROLL_ENROLLING = 2; + public static final int REASON_ENROLL_ENROLLING = 2; /** Usage from BiometricPrompt. */ - int REASON_AUTH_BP = 3; - /** Usage from Keyguard. */ - int REASON_AUTH_KEYGUARD = 4; + public static final int REASON_AUTH_BP = 3; + /** Usage from Device Entry. */ + public static final int REASON_AUTH_KEYGUARD = 4; /** Non-specific usage (from FingerprintManager). */ - int REASON_AUTH_OTHER = 5; + public static final int REASON_AUTH_OTHER = 5; /** Usage from Settings. */ - int REASON_AUTH_SETTINGS = 6; + public static final int REASON_AUTH_SETTINGS = 6; @IntDef({REASON_UNKNOWN, REASON_ENROLL_FIND_SENSOR, @@ -49,5 +52,5 @@ public interface BiometricOverlayConstants { REASON_AUTH_OTHER, REASON_AUTH_SETTINGS}) @Retention(RetentionPolicy.SOURCE) - @interface ShowReason {} + public @interface RequestReason {} } diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl index 5bdbe2b573b2f474a285bed42778f6ad0aa22d0c..8514f98fbf0dd03d986a2b36fe81294fb7ea486e 100644 --- a/core/java/android/hardware/biometrics/IAuthService.aidl +++ b/core/java/android/hardware/biometrics/IAuthService.aidl @@ -16,6 +16,7 @@ package android.hardware.biometrics; +import android.hardware.biometrics.AuthenticationStateListener; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.IBiometricServiceReceiver; import android.hardware.biometrics.IInvalidationCallback; @@ -66,6 +67,12 @@ interface IAuthService { // Register callback for when keyguard biometric eligibility changes. void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback); + // Register listener for changes to authentication state. + void registerAuthenticationStateListener(AuthenticationStateListener listener); + + // Unregister listener for changes to authentication state. + void unregisterAuthenticationStateListener(AuthenticationStateListener listener); + // Requests all BIOMETRIC_STRONG sensors to have their authenticatorId invalidated for the // specified user. This happens when enrollments have been added on devices with multiple // biometric sensors. diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 935157a48a4505a28d050569da1e5e59512cf3a3..fe7de83337842673623ad74b8867b2b8ff6b1d18 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -983,6 +983,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR /** * @hide */ diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index 0100660669e9361c8c670d2f28255432058fc9fc..f594c00b0e4771f4405b07dc23d4b7fd6d9b34a6 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -15,6 +15,7 @@ */ package android.hardware.fingerprint; +import android.hardware.biometrics.AuthenticationStateListener; import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.biometrics.IBiometricStateListener; @@ -203,6 +204,14 @@ interface IFingerprintService { @EnforcePermission("USE_BIOMETRIC_INTERNAL") void setSidefpsController(in ISidefpsController controller); + // Registers AuthenticationStateListener. + @EnforcePermission("USE_BIOMETRIC_INTERNAL") + void registerAuthenticationStateListener(AuthenticationStateListener listener); + + // Unregisters AuthenticationStateListener. + @EnforcePermission("USE_BIOMETRIC_INTERNAL") + void unregisterAuthenticationStateListener(AuthenticationStateListener listener); + // Registers BiometricStateListener. @EnforcePermission("USE_BIOMETRIC_INTERNAL") void registerBiometricStateListener(IBiometricStateListener listener); diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 695d888d94f5807e7f133f0801e26cbf4cf04641..f1701356c134ca85d73cad787d7de412df41609f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -18,7 +18,7 @@ package com.android.keyguard import android.content.res.Configuration -import android.hardware.biometrics.BiometricOverlayConstants +import android.hardware.biometrics.BiometricRequestConstants import android.media.AudioManager import android.telephony.TelephonyManager import android.testing.TestableLooper.RunWithLooper @@ -59,6 +59,7 @@ import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.DevicePostureController import com.android.systemui.statusbar.policy.DeviceProvisionedController @@ -235,6 +236,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { sceneInteractor = sceneInteractor, ) + mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) underTest = KeyguardSecurityContainerController( view, @@ -763,16 +765,18 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { @Test fun sideFpsControllerShow() { + mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) underTest.updateSideFpsVisibility(/* isVisible= */ true) verify(sideFpsController) .show( SideFpsUiRequestSource.PRIMARY_BOUNCER, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD + BiometricRequestConstants.REASON_AUTH_KEYGUARD ) } @Test fun sideFpsControllerHide() { + mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) underTest.updateSideFpsVisibility(/* isVisible= */ false) verify(sideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt index a1b801cd3d3f7f06620eb7207834ae1ed03c9256..f8321b7e7eb3d5d35d2e098d9aef065580dfe8a1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt @@ -22,9 +22,9 @@ import android.app.ActivityTaskManager import android.content.ComponentName import android.graphics.Insets import android.graphics.Rect -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS -import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS +import android.hardware.biometrics.BiometricRequestConstants.REASON_UNKNOWN import android.hardware.biometrics.SensorLocationInternal import android.hardware.biometrics.SensorProperties import android.hardware.display.DisplayManager @@ -65,6 +65,7 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R +import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock @@ -144,6 +145,7 @@ class SideFpsControllerTest : SysuiTestCase() { @Before fun setup() { + mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) displayRepository = FakeDisplayRepository() displayStateRepository = FakeDisplayStateRepository() keyguardBouncerRepository = FakeKeyguardBouncerRepository() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index 90d36e7fd81492167789caa664f432c00401190b..a726b7c2b075930343fd6143f28bd5afef302746 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -17,12 +17,12 @@ package com.android.systemui.biometrics import android.graphics.Rect -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS -import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING -import android.hardware.biometrics.BiometricOverlayConstants.ShowReason +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_OTHER +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS +import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING +import android.hardware.biometrics.BiometricRequestConstants.RequestReason import android.hardware.fingerprint.IUdfpsOverlayControllerCallback import android.testing.TestableLooper.RunWithLooper import android.view.LayoutInflater @@ -135,7 +135,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { } private fun withReason( - @ShowReason reason: Int, + @RequestReason reason: Int, isDebuggable: Boolean = false, enableDeviceEntryUdfpsRefactor: Boolean = false, block: () -> Unit, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 97ee526b7108f4e043aed3d8fca283607e0e25f5..dddcf18c1ede5708bb874c18e65a3f191350abd0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -43,7 +43,7 @@ import static org.mockito.Mockito.when; import android.graphics.Rect; import android.hardware.biometrics.BiometricFingerprintConstants; -import android.hardware.biometrics.BiometricOverlayConstants; +import android.hardware.biometrics.BiometricRequestConstants; import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.SensorProperties; import android.hardware.display.DisplayManager; @@ -359,7 +359,7 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void dozeTimeTick() throws RemoteException { mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); mUdfpsController.dozeTimeTick(); verify(mUdfpsView).dozeTimeTick(); @@ -455,7 +455,7 @@ public class UdfpsControllerTest extends SysuiTestCase { public void hideUdfpsOverlay_resetsAltAuthBouncerWhenShowing() throws RemoteException { // GIVEN overlay was showing and the udfps bouncer is showing mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); // WHEN the overlay is hidden @@ -469,7 +469,7 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void showUdfpsOverlay_callsListener() throws RemoteException { mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); verify(mFingerprintManager).onUdfpsUiEvent(FingerprintManager.UDFPS_UI_OVERLAY_SHOWN, @@ -479,7 +479,7 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void testSubscribesToOrientationChangesWhenShowingOverlay() throws Exception { mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); verify(mDisplayManager).registerDisplayListener(any(), eq(mHandler), anyLong()); @@ -520,7 +520,7 @@ public class UdfpsControllerTest extends SysuiTestCase { reset(mWindowManager); mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_ENROLL_ENROLLING, + BiometricRequestConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); verify(mWindowManager).addView(any(), any()); @@ -555,7 +555,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // Show the overlay. mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); verify(mWindowManager).addView(any(), any()); @@ -637,7 +637,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // Show the overlay. mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricOverlayConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); @@ -720,7 +720,7 @@ public class UdfpsControllerTest extends SysuiTestCase { initUdfpsController(testParams.sensorProps); mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); // WHEN ACTION_DOWN is received @@ -778,7 +778,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN that the overlay is showing and screen is on and fp is running mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); // WHEN fingerprint is requested because of AOD interrupt @@ -808,7 +808,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN AOD interrupt mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); @@ -886,7 +886,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN overlay is showing mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { when(mUdfpsView.isDisplayConfigured()).thenReturn(true); @@ -917,7 +917,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN AOD interrupt mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); @@ -958,7 +958,7 @@ public class UdfpsControllerTest extends SysuiTestCase { final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { // Configure UdfpsView to accept the ACTION_UP event @@ -1019,7 +1019,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN screen off mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOff(); mFgExecutor.runAllReady(); @@ -1041,7 +1041,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN showing overlay mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); @@ -1126,7 +1126,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN that the overlay is showing and a11y touch exploration NOT enabled when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(a11y); mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); if (a11y) { @@ -1148,7 +1148,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN that the overlay is showing and a11y touch exploration NOT enabled when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); @@ -1197,7 +1197,7 @@ public class UdfpsControllerTest extends SysuiTestCase { -1 /* pointerId */, touchData); mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); @@ -1289,7 +1289,7 @@ public class UdfpsControllerTest extends SysuiTestCase { public void onAodInterrupt_onAcquiredGood_fingerNoLongerDown() throws RemoteException { // GIVEN UDFPS overlay is showing mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); // GIVEN there's been an AoD interrupt @@ -1316,7 +1316,7 @@ public class UdfpsControllerTest extends SysuiTestCase { throws RemoteException { // GIVEN UDFPS overlay is showing mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); // GIVEN there's been an AoD interrupt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt index 90e0c19b7c65e8e9cced1b0617c29c878878e476..a3bf3f492e6e5ef3f4367e3e34198fc969005422 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt @@ -33,6 +33,7 @@ import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.TrustRepository import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor +import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.utils.os.FakeHandler @@ -105,8 +106,10 @@ class KeyguardBouncerViewModelTest : SysuiTestCase() { job.cancel() } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Test fun shouldUpdateSideFps_show() = runTest { + mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) var count = 0 val job = underTest.shouldUpdateSideFps.onEach { count++ }.launchIn(this) repository.setPrimaryShow(true) @@ -116,8 +119,10 @@ class KeyguardBouncerViewModelTest : SysuiTestCase() { job.cancel() } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Test fun shouldUpdateSideFps_hide() = runTest { + mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) repository.setPrimaryShow(true) var count = 0 val job = underTest.shouldUpdateSideFps.onEach { count++ }.launchIn(this) @@ -128,8 +133,10 @@ class KeyguardBouncerViewModelTest : SysuiTestCase() { job.cancel() } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Test fun sideFpsShowing() = runTest { + mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) var sideFpsIsShowing = false val job = underTest.sideFpsShowing.onEach { sideFpsIsShowing = it }.launchIn(this) repository.setSideFpsShowing(true) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt index 2b7221ec192cb29b206e16d51c06c8cc27cf594c..6b7d2635ffa4e4b96560a1bf46f159419e707ed4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt @@ -304,4 +304,34 @@ class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() { ) assertThat(authenticationStatus).isNull() } + + @Test + fun onBiometricRunningStateChanged_shouldUpdateIndicatorVisibility() = + testScope.runTest { + val shouldUpdateIndicatorVisibility by + collectLastValue(underTest.shouldUpdateIndicatorVisibility) + runCurrent() + + verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture()) + assertThat(shouldUpdateIndicatorVisibility).isFalse() + + invokeOnCallback { + it.onBiometricRunningStateChanged(false, BiometricSourceType.FINGERPRINT) + } + assertThat(shouldUpdateIndicatorVisibility).isTrue() + } + + @Test + fun onStrongAuthStateChanged_shouldUpdateIndicatorVisibility() = + testScope.runTest { + val shouldUpdateIndicatorVisibility by + collectLastValue(underTest.shouldUpdateIndicatorVisibility) + runCurrent() + + verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture()) + assertThat(shouldUpdateIndicatorVisibility).isFalse() + + invokeOnCallback { it.onStrongAuthStateChanged(0) } + assertThat(shouldUpdateIndicatorVisibility).isTrue() + } } diff --git a/packages/SystemUI/res/layout/sidefps_view.xml b/packages/SystemUI/res/layout/sidefps_view.xml index 4d952209da6fca254e0e3b609b732c90adadc4a0..fc4bf8a65643f41c78054382f389007efcde0719 100644 --- a/packages/SystemUI/res/layout/sidefps_view.xml +++ b/packages/SystemUI/res/layout/sidefps_view.xml @@ -14,7 +14,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<com.android.systemui.biometrics.SideFpsLottieViewWrapper +<com.android.systemui.biometrics.SideFpsIndicatorView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/sidefps_animation" diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 0a4378e07b45c7304067294987ee59afe55109ed..cce2018f733f759ccb6f127450ef6f23b9b39ec8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -38,7 +38,7 @@ import android.app.admin.DevicePolicyManager; import android.content.Intent; import android.content.res.ColorStateList; import android.content.res.Resources; -import android.hardware.biometrics.BiometricOverlayConstants; +import android.hardware.biometrics.BiometricRequestConstants; import android.media.AudioManager; import android.metrics.LogMaker; import android.os.SystemClock; @@ -74,6 +74,7 @@ import com.android.systemui.Gefingerpoken; import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate; import com.android.systemui.biometrics.SideFpsController; import com.android.systemui.biometrics.SideFpsUiRequestSource; +import com.android.systemui.biometrics.shared.SideFpsControllerRefactor; import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.classifier.FalsingA11yDelegate; @@ -486,7 +487,11 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mSceneContainerFlags = sceneContainerFlags; mGlobalSettings = globalSettings; mSessionTracker = sessionTracker; - mSideFpsController = sideFpsController; + if (SideFpsControllerRefactor.isEnabled()) { + mSideFpsController = Optional.empty(); + } else { + mSideFpsController = sideFpsController; + } mFalsingA11yDelegate = falsingA11yDelegate; mTelephonyManager = telephonyManager; mViewMediatorCallback = viewMediatorCallback; @@ -569,12 +574,14 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mView.clearFocus(); } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR /** * Shows and hides the side finger print sensor animation. * * @param isVisible sets whether we show or hide the side fps animation */ public void updateSideFpsVisibility(boolean isVisible) { + SideFpsControllerRefactor.assertInLegacyMode(); if (!mSideFpsController.isPresent()) { return; } @@ -582,7 +589,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard if (isVisible) { mSideFpsController.get().show( SideFpsUiRequestSource.PRIMARY_BOUNCER, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD + BiometricRequestConstants.REASON_AUTH_KEYGUARD ); } else { mSideFpsController.get().hide(SideFpsUiRequestSource.PRIMARY_BOUNCER); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 877afce7fe6577d5aacf15fcffa7499bc76359d1..5fba761b2f0942ab2d3fe8b1f6eec5fb909d2965 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -70,6 +70,7 @@ import com.android.systemui.CoreStartable; import com.android.systemui.biometrics.domain.interactor.LogContextInteractor; import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor; import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor; +import com.android.systemui.biometrics.shared.SideFpsControllerRefactor; import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams; import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel; import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel; @@ -88,6 +89,8 @@ import com.android.systemui.util.concurrency.Execution; import dagger.Lazy; +import kotlin.Unit; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; @@ -101,7 +104,6 @@ import java.util.Set; import javax.inject.Inject; import javax.inject.Provider; -import kotlin.Unit; import kotlinx.coroutines.CoroutineScope; /** @@ -317,7 +319,9 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, mSidefpsProps = !sidefpsProps.isEmpty() ? sidefpsProps : null; if (mSidefpsProps != null) { - mSideFpsController = mSidefpsControllerFactory.get(); + if (!SideFpsControllerRefactor.isEnabled()) { + mSideFpsController = mSidefpsControllerFactory.get(); + } } mFingerprintManager.registerBiometricStateListener(new BiometricStateListener() { @@ -1194,7 +1198,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, * Whether the passed userId has enrolled SFPS. */ public boolean isSfpsEnrolled(int userId) { - if (mSideFpsController == null) { + if (mSidefpsProps == null) { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt index 91cee9e51a93b08f7495e904a20dde8250d08bc9..ac99fc69b2b5c11113f0ac0073c102f9f35cd67d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt @@ -23,9 +23,9 @@ import android.graphics.PixelFormat import android.graphics.PorterDuff import android.graphics.PorterDuffColorFilter import android.graphics.Rect -import android.hardware.biometrics.BiometricOverlayConstants -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS +import android.hardware.biometrics.BiometricRequestConstants +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS import android.hardware.biometrics.SensorLocationInternal import android.hardware.display.DisplayManager import android.hardware.fingerprint.FingerprintManager @@ -58,6 +58,7 @@ import com.android.internal.annotations.VisibleForTesting import com.android.keyguard.KeyguardPINView import com.android.systemui.Dumpable import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor +import com.android.systemui.biometrics.shared.SideFpsControllerRefactor import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -91,7 +92,7 @@ constructor( @Main private val mainExecutor: DelayableExecutor, @Main private val handler: Handler, private val alternateBouncerInteractor: AlternateBouncerInteractor, - @Application private val scope: CoroutineScope, + @Application private val applicationScope: CoroutineScope, dumpManager: DumpManager, fpsUnlockTracker: FpsUnlockTracker ) : Dumpable { @@ -110,7 +111,7 @@ constructor( handler, sensorProps, { reason -> onOrientationChanged(reason) }, - BiometricOverlayConstants.REASON_UNKNOWN + BiometricRequestConstants.REASON_UNKNOWN ) @VisibleForTesting val orientationListener = orientationReasonListener.orientationListener @@ -169,25 +170,27 @@ constructor( } init { - fpsUnlockTracker.startTracking() - fingerprintManager?.setSidefpsController( - object : ISidefpsController.Stub() { - override fun show( - sensorId: Int, - @BiometricOverlayConstants.ShowReason reason: Int - ) = - if (reason.isReasonToAutoShow(activityTaskManager)) { - show(SideFpsUiRequestSource.AUTO_SHOW, reason) - } else { - hide(SideFpsUiRequestSource.AUTO_SHOW) - } - - override fun hide(sensorId: Int) = hide(SideFpsUiRequestSource.AUTO_SHOW) - } - ) - listenForAlternateBouncerVisibility() + if (!SideFpsControllerRefactor.isEnabled) { + fpsUnlockTracker.startTracking() + fingerprintManager?.setSidefpsController( + object : ISidefpsController.Stub() { + override fun show( + sensorId: Int, + @BiometricRequestConstants.RequestReason reason: Int + ) = + if (reason.isReasonToAutoShow(activityTaskManager)) { + show(SideFpsUiRequestSource.AUTO_SHOW, reason) + } else { + hide(SideFpsUiRequestSource.AUTO_SHOW) + } + + override fun hide(sensorId: Int) = hide(SideFpsUiRequestSource.AUTO_SHOW) + } + ) + listenForAlternateBouncerVisibility() - dumpManager.registerDumpable(this) + dumpManager.registerDumpable(this) + } } private fun listenForAlternateBouncerVisibility() { @@ -195,7 +198,7 @@ constructor( alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, "SideFpsController") } - scope.launch { + applicationScope.launch { alternateBouncerInteractor.isVisible.collect { isVisible: Boolean -> if (isVisible) { show(SideFpsUiRequestSource.ALTERNATE_BOUNCER, REASON_AUTH_KEYGUARD) @@ -209,8 +212,10 @@ constructor( /** Shows the side fps overlay if not already shown. */ fun show( request: SideFpsUiRequestSource, - @BiometricOverlayConstants.ShowReason reason: Int = BiometricOverlayConstants.REASON_UNKNOWN + @BiometricRequestConstants.RequestReason + reason: Int = BiometricRequestConstants.REASON_UNKNOWN ) { + SideFpsControllerRefactor.assertInLegacyMode() if (!displayStateInteractor.isInRearDisplayMode.value) { requests.add(request) mainExecutor.execute { @@ -229,6 +234,7 @@ constructor( /** Hides the fps overlay if shown. */ fun hide(request: SideFpsUiRequestSource) { + SideFpsControllerRefactor.assertInLegacyMode() requests.remove(request) mainExecutor.execute { if (requests.isEmpty()) { @@ -239,6 +245,7 @@ constructor( /** Hide the arrow indicator. */ fun hideIndicator() { + SideFpsControllerRefactor.assertInLegacyMode() val lottieAnimationView = overlayView?.findViewById(R.id.sidefps_animation) as LottieAnimationView? lottieAnimationView?.visibility = INVISIBLE @@ -246,6 +253,7 @@ constructor( /** Show the arrow indicator. */ fun showIndicator() { + SideFpsControllerRefactor.assertInLegacyMode() val lottieAnimationView = overlayView?.findViewById(R.id.sidefps_animation) as LottieAnimationView? lottieAnimationView?.visibility = VISIBLE @@ -279,13 +287,13 @@ constructor( pw.println("currentRotation=${displayInfo.rotation}") } - private fun onOrientationChanged(@BiometricOverlayConstants.ShowReason reason: Int) { + private fun onOrientationChanged(@BiometricRequestConstants.RequestReason reason: Int) { if (overlayView != null) { createOverlayForDisplay(reason) } } - private fun createOverlayForDisplay(@BiometricOverlayConstants.ShowReason reason: Int) { + private fun createOverlayForDisplay(@BiometricRequestConstants.RequestReason reason: Int) { val view = layoutInflater.inflate(R.layout.sidefps_view, null, false) overlayView = view val display = context.display!! @@ -395,7 +403,7 @@ private val FingerprintManager?.sideFpsSensorProperties: FingerprintSensorProper /** Returns [True] when the device has a side fingerprint sensor. */ fun FingerprintManager?.hasSideFpsSensor(): Boolean = this?.sideFpsSensorProperties != null -@BiometricOverlayConstants.ShowReason +@BiometricRequestConstants.RequestReason private fun Int.isReasonToAutoShow(activityTaskManager: ActivityTaskManager): Boolean = when (this) { REASON_AUTH_KEYGUARD -> false @@ -434,7 +442,7 @@ private fun Display.isNaturalOrientation(): Boolean = private fun LottieAnimationView.addOverlayDynamicColor( context: Context, - @BiometricOverlayConstants.ShowReason reason: Int + @BiometricRequestConstants.RequestReason reason: Int ) { fun update() { val isKeyguard = reason == REASON_AUTH_KEYGUARD @@ -501,7 +509,7 @@ class OrientationReasonListener( handler: Handler, sensorProps: FingerprintSensorPropertiesInternal, onOrientationChanged: (reason: Int) -> Unit, - @BiometricOverlayConstants.ShowReason var reason: Int + @BiometricRequestConstants.RequestReason var reason: Int ) { val orientationListener = BiometricDisplayListener( @@ -516,7 +524,7 @@ class OrientationReasonListener( /** * The source of a request to show the side fps visual indicator. This is distinct from - * [BiometricOverlayConstants] which corrresponds with the reason fingerprint authentication is + * [BiometricRequestConstants] which corresponds with the reason fingerprint authentication is * requested. */ enum class SideFpsUiRequestSource { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsIndicatorView.kt similarity index 96% rename from packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt rename to packages/SystemUI/src/com/android/systemui/biometrics/SideFpsIndicatorView.kt index e98f6db12d348ce97c175378c422705b32426c6a..d5e25ac84abacea3d7b5513f7cbf0387904b3273 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsIndicatorView.kt @@ -19,6 +19,6 @@ import android.content.Context import android.util.AttributeSet import com.android.systemui.util.wrapper.LottieViewWrapper -class SideFpsLottieViewWrapper +class SideFpsIndicatorView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : LottieViewWrapper(context, attrs) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index bb6ef41bdfd387a6b204874cfd83d598c6aab5b0..65668b56a9f3184ad55482fa4226e6c112d98f0f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -19,10 +19,10 @@ package com.android.systemui.biometrics; import static android.app.StatusBarManager.SESSION_BIOMETRIC_PROMPT; import static android.app.StatusBarManager.SESSION_KEYGUARD; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD; -import static android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP; -import static android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD; -import static android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING; -import static android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR; +import static android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP; +import static android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD; +import static android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING; +import static android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR; import static com.android.internal.util.LatencyTracker.ACTION_UDFPS_ILLUMINATE; import static com.android.internal.util.Preconditions.checkNotNull; @@ -106,6 +106,8 @@ import com.android.systemui.util.time.SystemClock; import dagger.Lazy; +import kotlin.Unit; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; @@ -115,8 +117,6 @@ import java.util.concurrent.Executor; import javax.inject.Inject; import javax.inject.Provider; -import kotlin.Unit; - import kotlinx.coroutines.ExperimentalCoroutinesApi; /** diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index 452cd6a7c4df498d5a90470fc4f6203a0ba62031..dae6d08f733198a8677f735b2a38250fea12242b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -21,13 +21,13 @@ import android.annotation.UiThread import android.content.Context import android.graphics.PixelFormat import android.graphics.Rect -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS -import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING -import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR -import android.hardware.biometrics.BiometricOverlayConstants.ShowReason +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_OTHER +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS +import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING +import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR +import android.hardware.biometrics.BiometricRequestConstants.RequestReason import android.hardware.fingerprint.IUdfpsOverlayControllerCallback import android.os.Build import android.os.RemoteException @@ -96,7 +96,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor( private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider, val requestId: Long, - @ShowReason val requestReason: Int, + @RequestReason val requestReason: Int, private val controllerCallback: IUdfpsOverlayControllerCallback, private val onTouch: (View, MotionEvent, Boolean) -> Boolean, private val activityLaunchAnimator: ActivityLaunchAnimator, @@ -461,7 +461,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor( } } -@ShowReason +@RequestReason private fun Int.isImportantForAccessibility() = this == REASON_ENROLL_FIND_SENSOR || this == REASON_ENROLL_ENROLLING || diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt index e3dbcb52325856c7d2b44973f4c5238090feb61a..88b9e1bdfd9716eefad35a8ae28718e319106df5 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt @@ -18,13 +18,13 @@ package com.android.systemui.biometrics import android.content.Context import android.graphics.Rect -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS -import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING -import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR -import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_OTHER +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS +import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING +import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR +import android.hardware.biometrics.BiometricRequestConstants.REASON_UNKNOWN import android.hardware.fingerprint.IUdfpsOverlayControllerCallback import android.util.Log import android.view.LayoutInflater diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt index 72fcfe777e792e0a95cdedd657e077b70ac2f383..8ae6f87f4f8316066560addfc3be534edc63758d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt @@ -18,8 +18,11 @@ package com.android.systemui.biometrics.dagger import android.content.res.Resources import com.android.internal.R +import com.android.systemui.CoreStartable import com.android.systemui.biometrics.EllipseOverlapDetectorParams import com.android.systemui.biometrics.UdfpsUtils +import com.android.systemui.biometrics.data.repository.BiometricStatusRepository +import com.android.systemui.biometrics.data.repository.BiometricStatusRepositoryImpl import com.android.systemui.biometrics.data.repository.DisplayStateRepository import com.android.systemui.biometrics.data.repository.DisplayStateRepositoryImpl import com.android.systemui.biometrics.data.repository.FacePropertyRepository @@ -33,11 +36,14 @@ import com.android.systemui.biometrics.data.repository.PromptRepositoryImpl import com.android.systemui.biometrics.udfps.BoundingBoxOverlapDetector import com.android.systemui.biometrics.udfps.EllipseOverlapDetector import com.android.systemui.biometrics.udfps.OverlapDetector +import com.android.systemui.biometrics.ui.binder.SideFpsOverlayViewBinder import com.android.systemui.dagger.SysUISingleton import com.android.systemui.util.concurrency.ThreadFactory import dagger.Binds import dagger.Module import dagger.Provides +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap import java.util.concurrent.Executor import javax.inject.Qualifier @@ -45,6 +51,11 @@ import javax.inject.Qualifier @Module interface BiometricsModule { + @Binds + @IntoMap + @ClassKey(SideFpsOverlayViewBinder::class) + fun bindsSideFpsOverlayViewBinder(viewBinder: SideFpsOverlayViewBinder): CoreStartable + @Binds @SysUISingleton fun faceSettings(impl: FaceSettingsRepositoryImpl): FaceSettingsRepository @@ -55,6 +66,10 @@ interface BiometricsModule { @SysUISingleton fun biometricPromptRepository(impl: PromptRepositoryImpl): PromptRepository + @Binds + @SysUISingleton + fun biometricStatusRepository(impl: BiometricStatusRepositoryImpl): BiometricStatusRepository + @Binds @SysUISingleton fun fingerprintRepository( diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..ad2136af4b860698fe92407ec0f8afe3993ee60e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.data.repository + +import android.hardware.biometrics.AuthenticationStateListener +import android.hardware.biometrics.BiometricManager +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_OTHER +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS +import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING +import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR +import com.android.systemui.biometrics.shared.model.AuthenticationReason +import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.shareIn + +/** A repository for the state of biometric authentication. */ +interface BiometricStatusRepository { + /** + * The logical reason for the current fingerprint auth operation if one is on-going, otherwise + * [NotRunning]. + */ + val fingerprintAuthenticationReason: Flow<AuthenticationReason> +} + +@SysUISingleton +class BiometricStatusRepositoryImpl +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + private val biometricManager: BiometricManager? +) : BiometricStatusRepository { + + override val fingerprintAuthenticationReason: Flow<AuthenticationReason> = + conflatedCallbackFlow { + val updateFingerprintAuthenticateReason = { reason: AuthenticationReason -> + trySendWithFailureLogging( + reason, + TAG, + "Error sending fingerprintAuthenticateReason reason" + ) + } + + val authenticationStateListener = + object : AuthenticationStateListener.Stub() { + override fun onAuthenticationStarted(requestReason: Int) { + val authenticationReason = + when (requestReason) { + REASON_AUTH_BP -> + AuthenticationReason.BiometricPromptAuthentication + REASON_AUTH_KEYGUARD -> + AuthenticationReason.DeviceEntryAuthentication + REASON_AUTH_OTHER -> AuthenticationReason.OtherAuthentication + REASON_AUTH_SETTINGS -> + AuthenticationReason.SettingsAuthentication( + SettingsOperations.OTHER + ) + REASON_ENROLL_ENROLLING -> + AuthenticationReason.SettingsAuthentication( + SettingsOperations.ENROLL_ENROLLING + ) + REASON_ENROLL_FIND_SENSOR -> + AuthenticationReason.SettingsAuthentication( + SettingsOperations.ENROLL_FIND_SENSOR + ) + else -> AuthenticationReason.Unknown + } + updateFingerprintAuthenticateReason(authenticationReason) + } + + override fun onAuthenticationStopped() { + updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning) + } + } + + updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning) + biometricManager?.registerAuthenticationStateListener(authenticationStateListener) + awaitClose { + biometricManager?.unregisterAuthenticationStateListener( + authenticationStateListener + ) + } + } + .shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1) + + companion object { + private const val TAG = "BiometricStatusRepositoryImpl" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt index b9b2fd8875d9f3051efefe96f42c465d4e89e6cc..ec3fd9f7da359cd76b1bb32cbe60fb13aaca2636 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt @@ -15,6 +15,8 @@ */ package com.android.systemui.biometrics.domain +import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor +import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractorImpl import com.android.systemui.biometrics.domain.interactor.CredentialInteractor import com.android.systemui.biometrics.domain.interactor.CredentialInteractorImpl import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor @@ -36,6 +38,12 @@ interface BiometricsDomainLayerModule { impl: PromptSelectorInteractorImpl ): PromptSelectorInteractor + @Binds + @SysUISingleton + fun providesBiometricStatusInteractor( + impl: BiometricStatusInteractorImpl + ): BiometricStatusInteractor + @Binds @SysUISingleton fun providesCredentialInteractor(impl: CredentialInteractorImpl): CredentialInteractor diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt new file mode 100644 index 0000000000000000000000000000000000000000..55a2d3d7563e4f34aa0546ea4185fa8cc5a57545 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.domain.interactor + +import android.app.ActivityTaskManager +import com.android.systemui.biometrics.data.repository.BiometricStatusRepository +import com.android.systemui.biometrics.shared.model.AuthenticationReason +import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +/** Encapsulates business logic for interacting with biometric authentication state. */ +interface BiometricStatusInteractor { + /** + * The logical reason for the current side fingerprint sensor auth operation if one is on-going, + * filtered for when the overlay should be shown, otherwise [NotRunning]. + */ + val sfpsAuthenticationReason: Flow<AuthenticationReason> +} + +class BiometricStatusInteractorImpl +@Inject +constructor( + private val activityTaskManager: ActivityTaskManager, + biometricStatusRepository: BiometricStatusRepository, +) : BiometricStatusInteractor { + + override val sfpsAuthenticationReason: Flow<AuthenticationReason> = + biometricStatusRepository.fingerprintAuthenticationReason.map { reason: AuthenticationReason + -> + if (reason.isReasonToAlwaysUpdateSfpsOverlay(activityTaskManager)) { + reason + } else { + AuthenticationReason.NotRunning + } + } + + companion object { + private const val TAG = "BiometricStatusInteractor" + } +} + +/** True if the sfps overlay should always be updated for this request source, false otherwise. */ +private fun AuthenticationReason.isReasonToAlwaysUpdateSfpsOverlay( + activityTaskManager: ActivityTaskManager +): Boolean = + when (this) { + AuthenticationReason.DeviceEntryAuthentication -> false + AuthenticationReason.SettingsAuthentication(SettingsOperations.OTHER) -> + when (activityTaskManager.topClass()) { + // TODO(b/186176653): exclude fingerprint overlays from this list view + "com.android.settings.biometrics.fingerprint.FingerprintSettings" -> false + else -> true + } + else -> true + } + +internal fun ActivityTaskManager.topClass(): String = + getTasks(1).firstOrNull()?.topActivity?.className ?: "" diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt index f51379940640b0fbfcbf0bdf213620a885f5e4d4..f4231ac01fee90989a3ee9928ec2ed595f8402c6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt @@ -141,7 +141,6 @@ constructor( } } } - SideFpsSensorLocation( left = sensorLeft, top = sensorTop, @@ -149,7 +148,15 @@ constructor( isSensorVerticalInDefaultOrientation = isSensorVerticalInDefaultOrientation ) } - .distinctUntilChanged() + .distinctUntilChanged( + areEquivalent = { old: SideFpsSensorLocation, new: SideFpsSensorLocation -> + old.left == new.left && + old.top == new.top && + old.length == new.length && + old.isSensorVerticalInDefaultOrientation == + new.isSensorVerticalInDefaultOrientation + } + ) .onEach { logger.sensorLocationStateChanged( it.left, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/SideFpsControllerRefactor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/SideFpsControllerRefactor.kt new file mode 100644 index 0000000000000000000000000000000000000000..899b07e89964257d2410e55b36c54da8443a579d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/SideFpsControllerRefactor.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.shared + +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils +import com.android.systemui.shared.Flags + +/** Helper for reading or using the sidefps controller refactor flag state. */ +@Suppress("NOTHING_TO_INLINE") +object SideFpsControllerRefactor { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the refactor enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.sidefpsControllerRefactor() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationReason.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationReason.kt new file mode 100644 index 0000000000000000000000000000000000000000..0c3debbe0fc47046688838f5a01fd2002b5b4fdf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationReason.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.shared.model + +/** + * The logical reason for a fingerprint auth operation if one is on-going, otherwise [NotRunning]. + */ +sealed interface AuthenticationReason { + /** Device entry requested authentication */ + data object DeviceEntryAuthentication : AuthenticationReason + + /** Settings requested authentication */ + data class SettingsAuthentication(val settingsOperation: SettingsOperations) : + AuthenticationReason + + /** App requested authentication */ + data object BiometricPromptAuthentication : AuthenticationReason + + /** Authentication requested for other reason */ + data object OtherAuthentication : AuthenticationReason + + /** Authentication requested for unknown reason */ + data object Unknown : AuthenticationReason + + /** Authentication is not running */ + data object NotRunning : AuthenticationReason + + /** Settings operations that request biometric authentication */ + enum class SettingsOperations { + ENROLL_ENROLLING, + ENROLL_FIND_SENSOR, + OTHER + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/LottieCallback.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/LottieCallback.kt new file mode 100644 index 0000000000000000000000000000000000000000..0b3005530e6d29d2921b3c2d6b9cf89812f9d6c8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/LottieCallback.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.shared.model + +import com.airbnb.lottie.model.KeyPath + +/** Represents properties of a LottieAnimationView callback */ +data class LottieCallback(val keypath: KeyPath, val color: Int) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt new file mode 100644 index 0000000000000000000000000000000000000000..a8c9446fd689ea78093a7f7d01198d1a7c1d9d14 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.biometrics.ui.binder + +import android.content.Context +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import android.view.LayoutInflater +import android.view.View +import android.view.WindowManager +import android.view.accessibility.AccessibilityEvent +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.airbnb.lottie.LottieAnimationView +import com.airbnb.lottie.LottieComposition +import com.airbnb.lottie.LottieProperty +import com.android.app.animation.Interpolators +import com.android.keyguard.KeyguardPINView +import com.android.systemui.CoreStartable +import com.android.systemui.biometrics.FpsUnlockTracker +import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor +import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor +import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor +import com.android.systemui.biometrics.shared.SideFpsControllerRefactor +import com.android.systemui.biometrics.shared.model.AuthenticationReason.NotRunning +import com.android.systemui.biometrics.shared.model.LottieCallback +import com.android.systemui.biometrics.ui.viewmodel.SideFpsOverlayViewModel +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor +import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.res.R +import com.android.systemui.util.kotlin.sample +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.launch + +/** Binds the side fingerprint sensor indicator view to [SideFpsOverlayViewModel]. */ +@SysUISingleton +class SideFpsOverlayViewBinder +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + @Application private val applicationContext: Context, + private val biometricStatusInteractor: BiometricStatusInteractor, + private val displayStateInteractor: DisplayStateInteractor, + private val deviceEntrySideFpsOverlayInteractor: DeviceEntrySideFpsOverlayInteractor, + private val fpsUnlockTracker: FpsUnlockTracker, + private val layoutInflater: LayoutInflater, + private val sideFpsProgressBarViewModel: SideFpsProgressBarViewModel, + private val sfpsSensorInteractor: SideFpsSensorInteractor, + private val windowManager: WindowManager +) : CoreStartable { + + override fun start() { + if (!SideFpsControllerRefactor.isEnabled) { + return + } + applicationScope + .launch { + combine( + biometricStatusInteractor.sfpsAuthenticationReason, + deviceEntrySideFpsOverlayInteractor.showIndicatorForDeviceEntry, + sideFpsProgressBarViewModel.isVisible, + ::Triple + ) + .sample(displayStateInteractor.isInRearDisplayMode, ::Pair) + .collect { (combinedFlows, isInRearDisplayMode: Boolean) -> + val ( + systemServerAuthReason, + showIndicatorForDeviceEntry, + progressBarIsVisible) = + combinedFlows + if (!isInRearDisplayMode) { + if (progressBarIsVisible) { + hide() + } else if (systemServerAuthReason != NotRunning) { + show() + } else if (showIndicatorForDeviceEntry) { + show() + } else { + hide() + } + } + } + } + .invokeOnCompletion { fpsUnlockTracker.stopTracking() } + } + + private var overlayView: View? = null + private var lottie: LottieAnimationView? = null + + /** Show the side fingerprint sensor indicator */ + private fun show() { + overlayView?.let { + if (it.isAttachedToWindow) { + lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation) + lottie?.pauseAnimation() + windowManager.removeView(it) + } + } + + overlayView = layoutInflater.inflate(R.layout.sidefps_view, null, false) + val overlayViewModel = + SideFpsOverlayViewModel( + applicationContext, + biometricStatusInteractor, + deviceEntrySideFpsOverlayInteractor, + displayStateInteractor, + sfpsSensorInteractor, + sideFpsProgressBarViewModel + ) + bind(overlayView!!, overlayViewModel, fpsUnlockTracker, windowManager) + overlayView!!.visibility = View.INVISIBLE + windowManager.addView(overlayView, overlayViewModel.defaultOverlayViewParams) + } + + /** Hide the side fingerprint sensor indicator */ + private fun hide() { + if (overlayView != null) { + windowManager.removeView(overlayView) + overlayView = null + } + } + + companion object { + private const val TAG = "SideFpsOverlayViewBinder" + + /** Binds overlayView (side fingerprint sensor indicator view) to SideFpsOverlayViewModel */ + fun bind( + overlayView: View, + viewModel: SideFpsOverlayViewModel, + fpsUnlockTracker: FpsUnlockTracker, + windowManager: WindowManager + ) { + overlayView.repeatWhenAttached { + fpsUnlockTracker.startTracking() + + val lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation) + lottie.addLottieOnCompositionLoadedListener { composition: LottieComposition -> + viewModel.setLottieBounds(composition.bounds) + overlayView.visibility = View.VISIBLE + } + it.alpha = 0f + val overlayShowAnimator = + it.animate() + .alpha(1f) + .setDuration(KeyguardPINView.ANIMATION_DURATION) + .setInterpolator(Interpolators.ALPHA_IN) + + overlayShowAnimator.start() + + it.setAccessibilityDelegate( + object : View.AccessibilityDelegate() { + override fun dispatchPopulateAccessibilityEvent( + host: View, + event: AccessibilityEvent + ): Boolean { + return if ( + event.getEventType() === + android.view.accessibility.AccessibilityEvent + .TYPE_WINDOW_STATE_CHANGED + ) { + true + } else { + super.dispatchPopulateAccessibilityEvent(host, event) + } + } + } + ) + + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.lottieCallbacks.collect { callbacks -> + lottie.addOverlayDynamicColor(callbacks) + } + } + + launch { + viewModel.overlayViewParams.collect { params -> + windowManager.updateViewLayout(it, params) + lottie.resumeAnimation() + } + } + + launch { + viewModel.overlayViewProperties.collect { properties -> + it.rotation = properties.overlayViewRotation + lottie.setAnimation(properties.indicatorAsset) + } + } + } + } + } + } +} + +private fun LottieAnimationView.addOverlayDynamicColor(colorCallbacks: List<LottieCallback>) { + addLottieOnCompositionLoadedListener { + for (callback in colorCallbacks) { + addValueCallback(callback.keypath, LottieProperty.COLOR_FILTER) { + PorterDuffColorFilter(callback.color, PorterDuff.Mode.SRC_ATOP) + } + } + resumeAnimation() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..ce726034913fc2ad09c641ee10aa3e9747bc4686 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.biometrics.ui.viewmodel + +import android.content.Context +import android.content.res.Configuration +import android.graphics.Color +import android.graphics.PixelFormat +import android.graphics.Point +import android.graphics.Rect +import android.view.Gravity +import android.view.WindowManager +import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION +import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY +import com.airbnb.lottie.model.KeyPath +import com.android.systemui.biometrics.Utils +import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor +import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor +import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor +import com.android.systemui.biometrics.domain.model.SideFpsSensorLocation +import com.android.systemui.biometrics.shared.model.AuthenticationReason +import com.android.systemui.biometrics.shared.model.DisplayRotation +import com.android.systemui.biometrics.shared.model.LottieCallback +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor +import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel +import com.android.systemui.res.R +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged + +/** Models UI of the side fingerprint sensor indicator view. */ +class SideFpsOverlayViewModel +@Inject +constructor( + @Application private val applicationContext: Context, + biometricStatusInteractor: BiometricStatusInteractor, + deviceEntrySideFpsOverlayInteractor: DeviceEntrySideFpsOverlayInteractor, + displayStateInteractor: DisplayStateInteractor, + sfpsSensorInteractor: SideFpsSensorInteractor, + sideFpsProgressBarViewModel: SideFpsProgressBarViewModel +) { + /** Contains properties of the side fingerprint sensor indicator */ + data class OverlayViewProperties( + /** The raw asset for the indicator animation */ + val indicatorAsset: Int, + /** Rotation of the overlayView */ + val overlayViewRotation: Float, + ) + + private val _lottieBounds: MutableStateFlow<Rect?> = MutableStateFlow(null) + + /** Used for setting lottie bounds once the composition has loaded. */ + fun setLottieBounds(bounds: Rect) { + _lottieBounds.value = bounds + } + + private val displayRotation = displayStateInteractor.currentRotation + private val sensorLocation = sfpsSensorInteractor.sensorLocation + + /** Default LayoutParams for the overlayView */ + val defaultOverlayViewParams: WindowManager.LayoutParams + get() = + WindowManager.LayoutParams( + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS, + PixelFormat.TRANSLUCENT + ) + .apply { + title = TAG + fitInsetsTypes = 0 // overrides default, avoiding status bars during layout + gravity = Gravity.TOP or Gravity.LEFT + layoutInDisplayCutoutMode = + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + privateFlags = PRIVATE_FLAG_TRUSTED_OVERLAY or PRIVATE_FLAG_NO_MOVE_ANIMATION + } + + private val indicatorAsset: Flow<Int> = + combine(displayRotation, sensorLocation) { rotation: DisplayRotation, sensorLocation -> + val yAligned = sensorLocation.isSensorVerticalInDefaultOrientation + val newAsset: Int = + when (rotation) { + DisplayRotation.ROTATION_0 -> + if (yAligned) { + R.raw.sfps_pulse + } else { + R.raw.sfps_pulse_landscape + } + DisplayRotation.ROTATION_180 -> + if (yAligned) { + R.raw.sfps_pulse + } else { + R.raw.sfps_pulse_landscape + } + else -> + if (yAligned) { + R.raw.sfps_pulse_landscape + } else { + R.raw.sfps_pulse + } + } + newAsset + } + .distinctUntilChanged() + + private val overlayViewRotation: Flow<Float> = + combine( + displayRotation, + sensorLocation, + ) { rotation: DisplayRotation, sensorLocation -> + val yAligned = sensorLocation.isSensorVerticalInDefaultOrientation + when (rotation) { + DisplayRotation.ROTATION_90 -> if (yAligned) 0f else 180f + DisplayRotation.ROTATION_180 -> 180f + DisplayRotation.ROTATION_270 -> if (yAligned) 180f else 0f + else -> 0f + } + } + .distinctUntilChanged() + + /** Contains properties (animation asset and view rotation) for overlayView */ + val overlayViewProperties: Flow<OverlayViewProperties> = + combine(indicatorAsset, overlayViewRotation) { asset: Int, rotation: Float -> + OverlayViewProperties(asset, rotation) + } + + /** LayoutParams for placement of overlayView (the side fingerprint sensor indicator view) */ + val overlayViewParams: Flow<WindowManager.LayoutParams> = + combine( + _lottieBounds, + sensorLocation, + displayRotation, + ) { bounds: Rect?, sensorLocation: SideFpsSensorLocation, displayRotation: DisplayRotation + -> + val topLeft = Point(sensorLocation.left, sensorLocation.top) + + if (sensorLocation.isSensorVerticalInDefaultOrientation) { + if (displayRotation == DisplayRotation.ROTATION_0) { + topLeft.x -= bounds!!.width() + } else if (displayRotation == DisplayRotation.ROTATION_270) { + topLeft.y -= bounds!!.height() + } + } else { + if (displayRotation == DisplayRotation.ROTATION_180) { + topLeft.y -= bounds!!.height() + } else if (displayRotation == DisplayRotation.ROTATION_270) { + topLeft.x -= bounds!!.width() + } + } + defaultOverlayViewParams.apply { + x = topLeft.x + y = topLeft.y + } + } + + /** List of LottieCallbacks use for adding dynamic color to the overlayView */ + val lottieCallbacks: Flow<List<LottieCallback>> = + combine( + biometricStatusInteractor.sfpsAuthenticationReason, + deviceEntrySideFpsOverlayInteractor.showIndicatorForDeviceEntry.distinctUntilChanged(), + sideFpsProgressBarViewModel.isVisible, + ) { reason: AuthenticationReason, showIndicatorForDeviceEntry: Boolean, progressBarIsVisible + -> + val callbacks = mutableListOf<LottieCallback>() + if (showIndicatorForDeviceEntry) { + val indicatorColor = + com.android.settingslib.Utils.getColorAttrDefaultColor( + applicationContext, + com.android.internal.R.attr.materialColorPrimaryFixed + ) + val outerRimColor = + com.android.settingslib.Utils.getColorAttrDefaultColor( + applicationContext, + com.android.internal.R.attr.materialColorPrimaryFixedDim + ) + val chevronFill = + com.android.settingslib.Utils.getColorAttrDefaultColor( + applicationContext, + com.android.internal.R.attr.materialColorOnPrimaryFixed + ) + callbacks.add(LottieCallback(KeyPath(".blue600", "**"), indicatorColor)) + callbacks.add(LottieCallback(KeyPath(".blue400", "**"), outerRimColor)) + callbacks.add(LottieCallback(KeyPath(".black", "**"), chevronFill)) + } else { + if (!isDarkMode(applicationContext)) { + callbacks.add(LottieCallback(KeyPath(".black", "**"), Color.WHITE)) + } + for (key in listOf(".blue600", ".blue400")) { + callbacks.add( + LottieCallback( + KeyPath(key, "**"), + applicationContext.getColor( + com.android.settingslib.color.R.color.settingslib_color_blue400 + ), + ) + ) + } + } + callbacks + } + + companion object { + private const val TAG = "SideFpsOverlayViewModel" + } +} + +private fun isDarkMode(context: Context): Boolean { + val darkMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK + return darkMode == Configuration.UI_MODE_NIGHT_YES +} diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt index 1e0e16c5472f9b9eca31052488b72266bdf6baf7..c2a1d8fe26c3bcc23d91a6ae92cc32a4add5c09d 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt @@ -18,6 +18,7 @@ package com.android.systemui.bouncer.data.repository import android.os.Build import android.util.Log +import com.android.systemui.biometrics.shared.SideFpsControllerRefactor import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel import com.android.systemui.dagger.SysUISingleton @@ -121,6 +122,7 @@ interface KeyguardBouncerRepository { fun setAlternateBouncerUIAvailable(isAvailable: Boolean) + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR fun setSideFpsShowing(isShowing: Boolean) } @@ -261,7 +263,9 @@ constructor( _isBackButtonEnabled.value = isBackButtonEnabled } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR override fun setSideFpsShowing(isShowing: Boolean) { + SideFpsControllerRefactor.assertInLegacyMode() _sideFpsShowing.value = isShowing } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt index aa7758f9380f80eefff3ae6e6f8441a0a909b6c7..621ca5dc6f5a36749d2ce002ecc94f23d9816982 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt @@ -28,6 +28,7 @@ import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.DejankUtils +import com.android.systemui.biometrics.shared.SideFpsControllerRefactor import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN @@ -116,9 +117,11 @@ constructor( /** Allow for interaction when just about fully visible */ val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR val sideFpsShowing: Flow<Boolean> = repository.sideFpsShowing private var currentUserActiveUnlockRunning = false + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR /** This callback needs to be a class field so it does not get garbage collected. */ val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() { @@ -135,7 +138,10 @@ constructor( } init { - keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR + if (!SideFpsControllerRefactor.isEnabled) { + keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) + } applicationScope.launch { trustRepository.isCurrentUserActiveUnlockRunning.collect { currentUserActiveUnlockRunning = it @@ -333,8 +339,10 @@ constructor( repository.setPrimaryStartDisappearAnimation(runnable) } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR /** Determine whether to show the side fps animation. */ fun updateSideFpsVisibility() { + SideFpsControllerRefactor.assertInLegacyMode() val sfpsEnabled: Boolean = context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer) val fpsDetectionRunning: Boolean = keyguardUpdateMonitor.isFingerprintDetectionRunning diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt index ac3d4b6c853b84f18cc1c97c8b51112b797f41c3..5dcd6615509d93639456be1dfcc230fd52042560 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt @@ -27,6 +27,7 @@ import com.android.keyguard.KeyguardSecurityContainerController import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardSecurityView import com.android.keyguard.dagger.KeyguardBouncerComponent +import com.android.systemui.biometrics.shared.SideFpsControllerRefactor import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE import com.android.systemui.bouncer.ui.BouncerViewDelegate @@ -233,15 +234,21 @@ object KeyguardBouncerViewBinder { .collect { view.systemUiVisibility = it } } - launch { - viewModel.shouldUpdateSideFps.collect { - viewModel.updateSideFpsVisibility() + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR + if (!SideFpsControllerRefactor.isEnabled) { + launch { + viewModel.shouldUpdateSideFps.collect { + viewModel.updateSideFpsVisibility() + } } } - launch { - viewModel.sideFpsShowing.collect { - securityContainerController.updateSideFpsVisibility(it) + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR + if (!SideFpsControllerRefactor.isEnabled) { + launch { + viewModel.sideFpsShowing.collect { + securityContainerController.updateSideFpsVisibility(it) + } } } awaitCancellation() diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt index 649ae2fb1007fd1282e3c46c86119d214f899f89..1c9d1f01e89e7a6927e68cedbd72d6aa592ee69a 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.bouncer.ui.viewmodel import android.view.View +import com.android.systemui.biometrics.shared.SideFpsControllerRefactor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel import com.android.systemui.bouncer.ui.BouncerView @@ -61,9 +62,11 @@ constructor( /** Observe whether keyguard is authenticated already. */ val keyguardAuthenticated: Flow<Boolean> = interactor.keyguardAuthenticatedBiometrics + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR /** Observe whether the side fps is showing. */ val sideFpsShowing: Flow<Boolean> = interactor.sideFpsShowing + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR /** Observe whether we should update fps is showing. */ val shouldUpdateSideFps: Flow<Unit> = merge( @@ -87,7 +90,9 @@ constructor( interactor.onMessageShown() } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR fun updateSideFpsVisibility() { + SideFpsControllerRefactor.assertInLegacyMode() interactor.updateSideFpsVisibility() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt index 96386f3dc59693e59f9d725e207e40409ed5afbd..9a13558d3327e4f1c4b2af2b04e307fa4c5226a7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt @@ -41,6 +41,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn /** Encapsulates state about device entry fingerprint auth mechanism. */ @@ -61,6 +62,9 @@ interface DeviceEntryFingerprintAuthRepository { /** Provide the current status of fingerprint authentication. */ val authenticationStatus: Flow<FingerprintAuthenticationStatus> + + /** Indicates whether to update the side fingerprint sensor indicator visibility. */ + val shouldUpdateIndicatorVisibility: Flow<Boolean> } /** @@ -256,6 +260,37 @@ constructor( awaitClose { keyguardUpdateMonitor.removeCallback(callback) } } + override val shouldUpdateIndicatorVisibility: Flow<Boolean> = + conflatedCallbackFlow { + val sendShouldUpdateIndicatorVisibility = + { shouldUpdateIndicatorVisibility: Boolean -> + trySendWithFailureLogging( + shouldUpdateIndicatorVisibility, + TAG, + "Error sending shouldUpdateIndicatorVisibility " + + "$shouldUpdateIndicatorVisibility" + ) + } + + val callback = + object : KeyguardUpdateMonitorCallback() { + override fun onBiometricRunningStateChanged( + running: Boolean, + biometricSourceType: BiometricSourceType? + ) { + sendShouldUpdateIndicatorVisibility(true) + } + override fun onStrongAuthStateChanged(userId: Int) { + sendShouldUpdateIndicatorVisibility(true) + } + } + sendShouldUpdateIndicatorVisibility(false) + keyguardUpdateMonitor.registerCallback(callback) + awaitClose { keyguardUpdateMonitor.removeCallback(callback) } + } + .flowOn(mainDispatcher) + .shareIn(scope, started = SharingStarted.WhileSubscribed(), replay = 1) + companion object { const val TAG = "DeviceEntryFingerprintAuthRepositoryImpl" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt new file mode 100644 index 0000000000000000000000000000000000000000..de15fd6a958fdfa7b0a099c174cab3d32f13896b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.content.Context +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository +import com.android.systemui.res.R +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge + +/** + * Encapsulates business logic for device entry events that impact the side fingerprint sensor + * overlay. + */ +@SysUISingleton +class DeviceEntrySideFpsOverlayInteractor +@Inject +constructor( + @Application private val context: Context, + deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository, + private val primaryBouncerInteractor: PrimaryBouncerInteractor, + alternateBouncerInteractor: AlternateBouncerInteractor, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, +) { + + init { + alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, TAG) + } + + private val showIndicatorForPrimaryBouncer: Flow<Boolean> = + merge( + primaryBouncerInteractor.isShowing, + primaryBouncerInteractor.startingToHide, + primaryBouncerInteractor.startingDisappearAnimation.filterNotNull(), + deviceEntryFingerprintAuthRepository.shouldUpdateIndicatorVisibility.filter { it } + ) + .map { shouldShowIndicatorForPrimaryBouncer() } + + private val showIndicatorForAlternateBouncer: Flow<Boolean> = + alternateBouncerInteractor.isVisible + + /** + * Indicates whether the primary or alternate bouncers request showing the side fingerprint + * sensor indicator. + */ + val showIndicatorForDeviceEntry: Flow<Boolean> = + combine(showIndicatorForPrimaryBouncer, showIndicatorForAlternateBouncer) { + showForPrimaryBouncer, + showForAlternateBouncer -> + showForPrimaryBouncer || showForAlternateBouncer + } + + private fun shouldShowIndicatorForPrimaryBouncer(): Boolean { + val sfpsEnabled: Boolean = + context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer) + val sfpsDetectionRunning = keyguardUpdateMonitor.isFingerprintDetectionRunning + val isUnlockingWithFpAllowed = keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed + return primaryBouncerInteractor.isBouncerShowing() && + sfpsEnabled && + sfpsDetectionRunning && + isUnlockingWithFpAllowed && + !primaryBouncerInteractor.isAnimatingAway() + } + + companion object { + private const val TAG = "DeviceEntrySideFpsOverlayInteractor" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt index 560e4adf956a96ba3ebaf1ebac30bc6e424e3ec4..9a6dca3c495879d042411f0de8739aae4ae70bc4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt @@ -21,6 +21,7 @@ import android.graphics.Point import com.android.systemui.CoreStartable import com.android.systemui.Flags import com.android.systemui.biometrics.SideFpsController +import com.android.systemui.biometrics.shared.SideFpsControllerRefactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.ui.view.SideFpsProgressBar @@ -46,6 +47,7 @@ constructor( private val viewModel: SideFpsProgressBarViewModel, private val view: SideFpsProgressBar, @Application private val applicationScope: CoroutineScope, + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR private val sfpsController: dagger.Lazy<SideFpsController>, private val logger: SideFpsLogger, private val commandRegistry: CommandRegistry, @@ -109,12 +111,14 @@ constructor( view.updateView(visible, location, length, thickness, rotation) // We have to hide the SFPS indicator as the progress bar will // be shown at the same location - if (visible) { - logger.hidingSfpsIndicator() - sfpsController.get().hideIndicator() - } else if (fpDetectRunning) { - logger.showingSfpsIndicator() - sfpsController.get().showIndicator() + if (!SideFpsControllerRefactor.isEnabled) { + if (visible) { + logger.hidingSfpsIndicator() + sfpsController.get().hideIndicator() + } else if (fpDetectRunning) { + logger.showingSfpsIndicator() + sfpsController.get().showIndicator() + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt index 2d0712c7f9cc2ffce22e6c94753f72a59a3f90ac..1dbf1f14b569e00ff2d31a00e35ce05fea81fc5a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel import android.animation.ValueAnimator import android.content.Context import android.graphics.Point +import androidx.annotation.VisibleForTesting import androidx.core.animation.doOnEnd import com.android.systemui.Flags import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor @@ -213,4 +214,9 @@ constructor( } } } + + @VisibleForTesting + fun setVisible(isVisible: Boolean) { + _visible.value = isVisible + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..27d93eb31922ce32e1d4a666cabbf8bd498aa27e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.data.repository + +import android.hardware.biometrics.AuthenticationStateListener +import android.hardware.biometrics.BiometricManager +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_OTHER +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS +import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING +import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR +import android.platform.test.annotations.RequiresFlagsEnabled +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.shared.model.AuthenticationReason +import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR +import com.android.systemui.util.mockito.withArgCaptor +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule + +@RequiresFlagsEnabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR) +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class BiometricStatusRepositoryTest : SysuiTestCase() { + @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule() + @Mock private lateinit var biometricManager: BiometricManager + + private lateinit var underTest: BiometricStatusRepository + + private val testScope = TestScope(StandardTestDispatcher()) + + @Before + fun setUp() { + underTest = BiometricStatusRepositoryImpl(testScope.backgroundScope, biometricManager) + } + + @Test + fun updatesFingerprintAuthenticationReason_whenBiometricPromptAuthenticationStarted() = + testScope.runTest { + val fingerprintAuthenticationReason by + collectLastValue(underTest.fingerprintAuthenticationReason) + runCurrent() + + val listener = biometricManager.captureListener() + + assertThat(fingerprintAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + + listener.onAuthenticationStarted(REASON_AUTH_BP) + assertThat(fingerprintAuthenticationReason) + .isEqualTo(AuthenticationReason.BiometricPromptAuthentication) + } + + @Test + fun updatesFingerprintAuthenticationReason_whenDeviceEntryAuthenticationStarted() = + testScope.runTest { + val fingerprintAuthenticationReason by + collectLastValue(underTest.fingerprintAuthenticationReason) + runCurrent() + + val listener = biometricManager.captureListener() + + assertThat(fingerprintAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + + listener.onAuthenticationStarted(REASON_AUTH_KEYGUARD) + assertThat(fingerprintAuthenticationReason) + .isEqualTo(AuthenticationReason.DeviceEntryAuthentication) + } + + @Test + fun updatesFingerprintAuthenticationReason_whenOtherAuthenticationStarted() = + testScope.runTest { + val fingerprintAuthenticationReason by + collectLastValue(underTest.fingerprintAuthenticationReason) + runCurrent() + + val listener = biometricManager.captureListener() + + assertThat(fingerprintAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + + listener.onAuthenticationStarted(REASON_AUTH_OTHER) + assertThat(fingerprintAuthenticationReason) + .isEqualTo(AuthenticationReason.OtherAuthentication) + } + + @Test + fun updatesFingerprintAuthenticationReason_whenSettingsAuthenticationStarted() = + testScope.runTest { + val fingerprintAuthenticationReason by + collectLastValue(underTest.fingerprintAuthenticationReason) + runCurrent() + + val listener = biometricManager.captureListener() + + assertThat(fingerprintAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + + listener.onAuthenticationStarted(REASON_AUTH_SETTINGS) + assertThat(fingerprintAuthenticationReason) + .isEqualTo(AuthenticationReason.SettingsAuthentication(SettingsOperations.OTHER)) + } + + @Test + fun updatesFingerprintAuthenticationReason_whenEnrollmentAuthenticationStarted() = + testScope.runTest { + val fingerprintAuthenticationReason by + collectLastValue(underTest.fingerprintAuthenticationReason) + runCurrent() + + val listener = biometricManager.captureListener() + + assertThat(fingerprintAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + + listener.onAuthenticationStarted(REASON_ENROLL_FIND_SENSOR) + assertThat(fingerprintAuthenticationReason) + .isEqualTo( + AuthenticationReason.SettingsAuthentication( + SettingsOperations.ENROLL_FIND_SENSOR + ) + ) + + listener.onAuthenticationStarted(REASON_ENROLL_ENROLLING) + assertThat(fingerprintAuthenticationReason) + .isEqualTo( + AuthenticationReason.SettingsAuthentication(SettingsOperations.ENROLL_ENROLLING) + ) + } + + @Test + fun updatesFingerprintAuthenticationReason_whenAuthenticationStopped() = + testScope.runTest { + val fingerprintAuthenticationReason by + collectLastValue(underTest.fingerprintAuthenticationReason) + runCurrent() + + val listener = biometricManager.captureListener() + + listener.onAuthenticationStarted(REASON_AUTH_BP) + listener.onAuthenticationStopped() + assertThat(fingerprintAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + } +} + +private fun BiometricManager.captureListener() = + withArgCaptor<AuthenticationStateListener> { + verify(this@captureListener).registerAuthenticationStateListener(capture()) + } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..6978923879b45ef79bfaa97734e6301a27387a76 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.domain.interactor + +import android.app.ActivityManager +import android.app.ActivityTaskManager +import android.content.ComponentName +import android.platform.test.annotations.RequiresFlagsEnabled +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository +import com.android.systemui.biometrics.shared.model.AuthenticationReason +import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.`when` +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule + +@RequiresFlagsEnabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR) +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class BiometricStatusInteractorImplTest : SysuiTestCase() { + @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule() + @Mock private lateinit var activityTaskManager: ActivityTaskManager + + private lateinit var biometricStatusRepository: FakeBiometricStatusRepository + private lateinit var underTest: BiometricStatusInteractorImpl + + private val testScope = TestScope(StandardTestDispatcher()) + + @Before + fun setup() { + biometricStatusRepository = FakeBiometricStatusRepository() + underTest = BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository) + } + + @Test + fun updatesSfpsAuthenticationReason_whenBiometricPromptAuthenticationStarted() = + testScope.runTest { + val sfpsAuthenticationReason by collectLastValue(underTest.sfpsAuthenticationReason) + runCurrent() + + assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.BiometricPromptAuthentication + ) + assertThat(sfpsAuthenticationReason) + .isEqualTo(AuthenticationReason.BiometricPromptAuthentication) + } + + @Test + fun doesNotUpdateSfpsAuthenticationReason_whenDeviceEntryAuthenticationStarted() = + testScope.runTest { + val sfpsAuthenticationReason by collectLastValue(underTest.sfpsAuthenticationReason) + runCurrent() + + assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.DeviceEntryAuthentication + ) + assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + } + + @Test + fun updatesSfpsAuthenticationReason_whenOtherAuthenticationStarted() = + testScope.runTest { + val sfpsAuthenticationReason by collectLastValue(underTest.sfpsAuthenticationReason) + runCurrent() + + assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.OtherAuthentication + ) + assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.OtherAuthentication) + } + + @Test + fun doesNotUpdateSfpsAuthenticationReason_whenOtherSettingsAuthenticationStarted() = + testScope.runTest { + val sfpsAuthenticationReason by collectLastValue(underTest.sfpsAuthenticationReason) + runCurrent() + + assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + + `when`(activityTaskManager.getTasks(Mockito.anyInt())) + .thenReturn(listOf(fpSettingsTask())) + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.SettingsAuthentication(SettingsOperations.OTHER) + ) + assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + } + + @Test + fun updatesSfpsAuthenticationReason_whenEnrollmentAuthenticationStarted() = + testScope.runTest { + val sfpsAuthenticationReason by collectLastValue(underTest.sfpsAuthenticationReason) + runCurrent() + + assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.SettingsAuthentication(SettingsOperations.ENROLL_FIND_SENSOR) + ) + assertThat(sfpsAuthenticationReason) + .isEqualTo( + AuthenticationReason.SettingsAuthentication( + SettingsOperations.ENROLL_FIND_SENSOR + ) + ) + + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.SettingsAuthentication(SettingsOperations.ENROLL_ENROLLING) + ) + assertThat(sfpsAuthenticationReason) + .isEqualTo( + AuthenticationReason.SettingsAuthentication(SettingsOperations.ENROLL_ENROLLING) + ) + } + + @Test + fun updatesFingerprintAuthenticationReason_whenAuthenticationStopped() = + testScope.runTest { + val sfpsAuthenticationReason by collectLastValue(underTest.sfpsAuthenticationReason) + runCurrent() + + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.BiometricPromptAuthentication + ) + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.NotRunning + ) + assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + } +} + +private fun fpSettingsTask() = settingsTask(".biometrics.fingerprint.FingerprintSettings") + +private fun settingsTask(cls: String) = + ActivityManager.RunningTaskInfo().apply { + topActivity = ComponentName.createRelative("com.android.settings", cls) + } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..b4ae00ddd608c75ba4d78ce4763c3e71f705047a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt @@ -0,0 +1,484 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.ui.binder + +import android.animation.Animator +import android.app.ActivityTaskManager +import android.graphics.Rect +import android.hardware.biometrics.SensorLocationInternal +import android.hardware.display.DisplayManager +import android.hardware.display.DisplayManagerGlobal +import android.os.Handler +import android.testing.TestableLooper +import android.view.Display +import android.view.DisplayInfo +import android.view.LayoutInflater +import android.view.View +import android.view.ViewPropertyAnimator +import android.view.WindowInsets +import android.view.WindowManager +import android.view.WindowMetrics +import androidx.test.filters.SmallTest +import com.airbnb.lottie.LottieAnimationView +import com.android.keyguard.KeyguardSecurityModel +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider +import com.android.systemui.biometrics.FpsUnlockTracker +import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository +import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor +import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractorImpl +import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl +import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor +import com.android.systemui.biometrics.shared.model.AuthenticationReason +import com.android.systemui.biometrics.shared.model.DisplayRotation +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.biometrics.shared.model.SensorStrength +import com.android.systemui.biometrics.ui.viewmodel.SideFpsOverlayViewModel +import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.bouncer.ui.BouncerView +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.display.data.repository.FakeDisplayRepository +import com.android.systemui.dump.logcatLogBuffer +import com.android.systemui.keyguard.DismissCallbackRegistry +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.FakeTrustRepository +import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor +import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel +import com.android.systemui.log.SideFpsLogger +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.res.R +import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.unfold.compat.ScreenSizeFoldProvider +import com.android.systemui.user.domain.interactor.SelectedUserInteractor +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock +import java.util.Optional +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.any +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +class SideFpsOverlayViewBinderTest : SysuiTestCase() { + @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule() + @Mock private lateinit var activityTaskManager: ActivityTaskManager + @Mock private lateinit var displayManager: DisplayManager + @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor + @Mock + private lateinit var fingerprintInteractiveToAuthProvider: FingerprintInteractiveToAuthProvider + @Mock private lateinit var fpsUnlockTracker: FpsUnlockTracker + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock private lateinit var layoutInflater: LayoutInflater + @Mock private lateinit var screenSizeFoldProvider: ScreenSizeFoldProvider + @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor + @Mock private lateinit var sideFpsView: View + @Mock private lateinit var windowManager: WindowManager + + private val contextDisplayInfo = DisplayInfo() + + private val bouncerRepository = FakeKeyguardBouncerRepository() + private val biometricSettingsRepository = FakeBiometricSettingsRepository() + private val biometricStatusRepository = FakeBiometricStatusRepository() + private val deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository() + private val displayRepository = FakeDisplayRepository() + private val displayStateRepository = FakeDisplayStateRepository() + private val fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + + private lateinit var underTest: SideFpsOverlayViewBinder + + private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor + private lateinit var biometricStatusInteractor: BiometricStatusInteractor + private lateinit var deviceEntrySideFpsOverlayInteractor: DeviceEntrySideFpsOverlayInteractor + private lateinit var displayStateInteractor: DisplayStateInteractorImpl + private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor + private lateinit var sfpsSensorInteractor: SideFpsSensorInteractor + + private lateinit var sideFpsProgressBarViewModel: SideFpsProgressBarViewModel + + private lateinit var viewModel: SideFpsOverlayViewModel + + private var displayWidth: Int = 0 + private var displayHeight: Int = 0 + private var boundsWidth: Int = 0 + private var boundsHeight: Int = 0 + + private lateinit var deviceConfig: DeviceConfig + private lateinit var sensorLocation: SensorLocationInternal + + private val testScope = TestScope(StandardTestDispatcher()) + private val fakeExecutor = FakeExecutor(FakeSystemClock()) + + enum class DeviceConfig { + X_ALIGNED, + Y_ALIGNED, + } + + @Before + fun setup() { + mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) + + allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread + + mContext = spy(mContext) + + val resources = mContext.resources + whenever(mContext.display) + .thenReturn( + Display(mock(DisplayManagerGlobal::class.java), 1, contextDisplayInfo, resources) + ) + + alternateBouncerInteractor = + AlternateBouncerInteractor( + mock(StatusBarStateController::class.java), + mock(KeyguardStateController::class.java), + bouncerRepository, + fingerprintPropertyRepository, + biometricSettingsRepository, + FakeSystemClock(), + keyguardUpdateMonitor, + testScope.backgroundScope, + ) + + biometricStatusInteractor = + BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository) + + displayStateInteractor = + DisplayStateInteractorImpl( + testScope.backgroundScope, + mContext, + fakeExecutor, + displayStateRepository, + displayRepository, + ) + displayStateInteractor.setScreenSizeFoldProvider(screenSizeFoldProvider) + + primaryBouncerInteractor = + PrimaryBouncerInteractor( + bouncerRepository, + mock(BouncerView::class.java), + mock(Handler::class.java), + mock(KeyguardStateController::class.java), + mock(KeyguardSecurityModel::class.java), + mock(PrimaryBouncerCallbackInteractor::class.java), + mock(FalsingCollector::class.java), + mock(DismissCallbackRegistry::class.java), + mContext, + keyguardUpdateMonitor, + FakeTrustRepository(), + testScope.backgroundScope, + selectedUserInteractor, + faceAuthInteractor + ) + + deviceEntrySideFpsOverlayInteractor = + DeviceEntrySideFpsOverlayInteractor( + mContext, + deviceEntryFingerprintAuthRepository, + primaryBouncerInteractor, + alternateBouncerInteractor, + keyguardUpdateMonitor + ) + + whenever(fingerprintInteractiveToAuthProvider.enabledForCurrentUser) + .thenReturn(MutableStateFlow(false)) + + sfpsSensorInteractor = + SideFpsSensorInteractor( + mContext, + fingerprintPropertyRepository, + windowManager, + displayStateInteractor, + Optional.of(fingerprintInteractiveToAuthProvider), + SideFpsLogger(logcatLogBuffer("SfpsLogger")) + ) + + sideFpsProgressBarViewModel = + SideFpsProgressBarViewModel( + mContext, + deviceEntryFingerprintAuthRepository, + sfpsSensorInteractor, + displayStateInteractor, + testScope.backgroundScope, + ) + + viewModel = + SideFpsOverlayViewModel( + mContext, + biometricStatusInteractor, + deviceEntrySideFpsOverlayInteractor, + displayStateInteractor, + sfpsSensorInteractor, + sideFpsProgressBarViewModel + ) + + underTest = + SideFpsOverlayViewBinder( + testScope.backgroundScope, + mContext, + biometricStatusInteractor, + displayStateInteractor, + deviceEntrySideFpsOverlayInteractor, + fpsUnlockTracker, + layoutInflater, + sideFpsProgressBarViewModel, + sfpsSensorInteractor, + windowManager + ) + + context.addMockSystemService(DisplayManager::class.java, displayManager) + context.addMockSystemService(WindowManager::class.java, windowManager) + + `when`(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sideFpsView) + `when`(sideFpsView.requireViewById<LottieAnimationView>(eq(R.id.sidefps_animation))) + .thenReturn(mock(LottieAnimationView::class.java)) + with(mock(ViewPropertyAnimator::class.java)) { + `when`(sideFpsView.animate()).thenReturn(this) + `when`(alpha(Mockito.anyFloat())).thenReturn(this) + `when`(setStartDelay(Mockito.anyLong())).thenReturn(this) + `when`(setDuration(Mockito.anyLong())).thenReturn(this) + `when`(setListener(any())).thenAnswer { + (it.arguments[0] as Animator.AnimatorListener).onAnimationEnd( + mock(Animator::class.java) + ) + this + } + } + } + + @Test + fun verifyIndicatorNotAdded_whenInRearDisplayMode() { + testScope.runTest { + setupTestConfiguration( + DeviceConfig.X_ALIGNED, + rotation = DisplayRotation.ROTATION_0, + isInRearDisplayMode = true + ) + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.NotRunning + ) + sideFpsProgressBarViewModel.setVisible(false) + updatePrimaryBouncer( + isShowing = true, + isAnimatingAway = false, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = true + ) + runCurrent() + + verify(windowManager, never()).addView(any(), any()) + } + } + + @Test + fun verifyIndicatorShowAndHide_onPrimaryBouncerShowAndHide() { + testScope.runTest { + setupTestConfiguration( + DeviceConfig.X_ALIGNED, + rotation = DisplayRotation.ROTATION_0, + isInRearDisplayMode = false + ) + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.NotRunning + ) + sideFpsProgressBarViewModel.setVisible(false) + // Show primary bouncer + updatePrimaryBouncer( + isShowing = true, + isAnimatingAway = false, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = true + ) + runCurrent() + + verify(windowManager).addView(any(), any()) + + // Hide primary bouncer + updatePrimaryBouncer( + isShowing = false, + isAnimatingAway = false, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = true + ) + runCurrent() + + verify(windowManager).removeView(any()) + } + } + + @Test + fun verifyIndicatorShowAndHide_onAlternateBouncerShowAndHide() { + testScope.runTest { + setupTestConfiguration( + DeviceConfig.X_ALIGNED, + rotation = DisplayRotation.ROTATION_0, + isInRearDisplayMode = false + ) + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.NotRunning + ) + sideFpsProgressBarViewModel.setVisible(false) + // Show alternate bouncer + bouncerRepository.setAlternateVisible(true) + runCurrent() + + verify(windowManager).addView(any(), any()) + + // Hide alternate bouncer + bouncerRepository.setAlternateVisible(false) + runCurrent() + + verify(windowManager).removeView(any()) + } + } + + @Test + fun verifyIndicatorShownAndHidden_onSystemServerAuthenticationStartedAndStopped() { + testScope.runTest { + setupTestConfiguration( + DeviceConfig.X_ALIGNED, + rotation = DisplayRotation.ROTATION_0, + isInRearDisplayMode = false + ) + sideFpsProgressBarViewModel.setVisible(false) + updatePrimaryBouncer( + isShowing = false, + isAnimatingAway = false, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = true + ) + // System server authentication started + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.BiometricPromptAuthentication + ) + runCurrent() + + verify(windowManager).addView(any(), any()) + + // System server authentication stopped + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.NotRunning + ) + runCurrent() + + verify(windowManager).removeView(any()) + } + } + + private fun updatePrimaryBouncer( + isShowing: Boolean, + isAnimatingAway: Boolean, + fpsDetectionRunning: Boolean, + isUnlockingWithFpAllowed: Boolean, + ) { + bouncerRepository.setPrimaryShow(isShowing) + bouncerRepository.setPrimaryStartingToHide(false) + val primaryStartDisappearAnimation = if (isAnimatingAway) Runnable {} else null + bouncerRepository.setPrimaryStartDisappearAnimation(primaryStartDisappearAnimation) + + whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning) + .thenReturn(fpsDetectionRunning) + whenever(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed) + .thenReturn(isUnlockingWithFpAllowed) + mContext.orCreateTestableResources.addOverride( + R.bool.config_show_sidefps_hint_on_bouncer, + true + ) + } + + private suspend fun TestScope.setupTestConfiguration( + deviceConfig: DeviceConfig, + rotation: DisplayRotation = DisplayRotation.ROTATION_0, + isInRearDisplayMode: Boolean, + ) { + this@SideFpsOverlayViewBinderTest.deviceConfig = deviceConfig + + when (deviceConfig) { + DeviceConfig.X_ALIGNED -> { + displayWidth = 3000 + displayHeight = 1500 + boundsWidth = 200 + boundsHeight = 100 + sensorLocation = SensorLocationInternal("", 2500, 0, boundsWidth / 2) + } + DeviceConfig.Y_ALIGNED -> { + displayWidth = 2500 + displayHeight = 2000 + boundsWidth = 100 + boundsHeight = 200 + sensorLocation = SensorLocationInternal("", displayWidth, 300, boundsHeight / 2) + } + } + + whenever(windowManager.maximumWindowMetrics) + .thenReturn( + WindowMetrics( + Rect(0, 0, displayWidth, displayHeight), + mock(WindowInsets::class.java), + ) + ) + + contextDisplayInfo.uniqueId = DISPLAY_ID + + fingerprintPropertyRepository.setProperties( + sensorId = 1, + strength = SensorStrength.STRONG, + sensorType = FingerprintSensorType.POWER_BUTTON, + sensorLocations = mapOf(DISPLAY_ID to sensorLocation) + ) + + displayStateRepository.setIsInRearDisplayMode(isInRearDisplayMode) + displayStateRepository.setCurrentRotation(rotation) + displayRepository.emitDisplayChangeEvent(0) + underTest.start() + runCurrent() + } + + companion object { + private const val DISPLAY_ID = "displayId" + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..2267bdcafad840051e8065c01f894a5e1fc6270e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt @@ -0,0 +1,576 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.ui.viewmodel + +import android.app.ActivityTaskManager +import android.content.res.Configuration.UI_MODE_NIGHT_NO +import android.content.res.Configuration.UI_MODE_NIGHT_YES +import android.graphics.Color +import android.graphics.Rect +import android.hardware.biometrics.SensorLocationInternal +import android.hardware.display.DisplayManagerGlobal +import android.os.Handler +import android.view.Display +import android.view.DisplayInfo +import android.view.WindowInsets +import android.view.WindowManager +import android.view.WindowMetrics +import androidx.test.filters.SmallTest +import com.airbnb.lottie.model.KeyPath +import com.android.keyguard.KeyguardSecurityModel +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.settingslib.Utils +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider +import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository +import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor +import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractorImpl +import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl +import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor +import com.android.systemui.biometrics.shared.model.AuthenticationReason +import com.android.systemui.biometrics.shared.model.DisplayRotation +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.biometrics.shared.model.LottieCallback +import com.android.systemui.biometrics.shared.model.SensorStrength +import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.bouncer.ui.BouncerView +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.display.data.repository.FakeDisplayRepository +import com.android.systemui.dump.logcatLogBuffer +import com.android.systemui.keyguard.DismissCallbackRegistry +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.FakeTrustRepository +import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor +import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel +import com.android.systemui.log.SideFpsLogger +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.res.R +import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.unfold.compat.ScreenSizeFoldProvider +import com.android.systemui.user.domain.interactor.SelectedUserInteractor +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import java.util.Optional +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.Mockito.spy +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class SideFpsOverlayViewModelTest : SysuiTestCase() { + @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule() + + @Mock private lateinit var activityTaskManager: ActivityTaskManager + @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor + @Mock + private lateinit var fingerprintInteractiveToAuthProvider: FingerprintInteractiveToAuthProvider + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock private lateinit var screenSizeFoldProvider: ScreenSizeFoldProvider + @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor + @Mock private lateinit var windowManager: WindowManager + + private val contextDisplayInfo = DisplayInfo() + + private val bouncerRepository = FakeKeyguardBouncerRepository() + private val biometricSettingsRepository = FakeBiometricSettingsRepository() + private val biometricStatusRepository = FakeBiometricStatusRepository() + private val deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository() + private val displayRepository = FakeDisplayRepository() + private val displayStateRepository = FakeDisplayStateRepository() + private val fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + + private val indicatorColor = + Utils.getColorAttrDefaultColor( + context, + com.android.internal.R.attr.materialColorPrimaryFixed + ) + private val outerRimColor = + Utils.getColorAttrDefaultColor( + context, + com.android.internal.R.attr.materialColorPrimaryFixedDim + ) + private val chevronFill = + Utils.getColorAttrDefaultColor( + context, + com.android.internal.R.attr.materialColorOnPrimaryFixed + ) + private val color_blue400 = + context.getColor(com.android.settingslib.color.R.color.settingslib_color_blue400) + + private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor + private lateinit var biometricStatusInteractor: BiometricStatusInteractor + private lateinit var deviceEntrySideFpsOverlayInteractor: DeviceEntrySideFpsOverlayInteractor + private lateinit var displayStateInteractor: DisplayStateInteractorImpl + private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor + private lateinit var sfpsSensorInteractor: SideFpsSensorInteractor + + private lateinit var sideFpsProgressBarViewModel: SideFpsProgressBarViewModel + + private lateinit var underTest: SideFpsOverlayViewModel + + private var displayWidth: Int = 0 + private var displayHeight: Int = 0 + private var boundsWidth: Int = 0 + private var boundsHeight: Int = 0 + + private lateinit var deviceConfig: DeviceConfig + private lateinit var sensorLocation: SensorLocationInternal + + private val testScope = TestScope(StandardTestDispatcher()) + private val fakeExecutor = FakeExecutor(FakeSystemClock()) + + enum class DeviceConfig { + X_ALIGNED, + Y_ALIGNED, + } + + @Before + fun setup() { + mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) + + mContext = spy(mContext) + + val resources = mContext.resources + whenever(mContext.display) + .thenReturn( + Display(mock(DisplayManagerGlobal::class.java), 1, contextDisplayInfo, resources) + ) + + alternateBouncerInteractor = + AlternateBouncerInteractor( + mock(StatusBarStateController::class.java), + mock(KeyguardStateController::class.java), + bouncerRepository, + fingerprintPropertyRepository, + biometricSettingsRepository, + FakeSystemClock(), + keyguardUpdateMonitor, + testScope.backgroundScope, + ) + + biometricStatusInteractor = + BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository) + + displayStateInteractor = + DisplayStateInteractorImpl( + testScope.backgroundScope, + mContext, + fakeExecutor, + displayStateRepository, + displayRepository, + ) + displayStateInteractor.setScreenSizeFoldProvider(screenSizeFoldProvider) + + primaryBouncerInteractor = + PrimaryBouncerInteractor( + bouncerRepository, + mock(BouncerView::class.java), + mock(Handler::class.java), + mock(KeyguardStateController::class.java), + mock(KeyguardSecurityModel::class.java), + mock(PrimaryBouncerCallbackInteractor::class.java), + mock(FalsingCollector::class.java), + mock(DismissCallbackRegistry::class.java), + mContext, + keyguardUpdateMonitor, + FakeTrustRepository(), + testScope.backgroundScope, + selectedUserInteractor, + faceAuthInteractor + ) + + deviceEntrySideFpsOverlayInteractor = + DeviceEntrySideFpsOverlayInteractor( + mContext, + deviceEntryFingerprintAuthRepository, + primaryBouncerInteractor, + alternateBouncerInteractor, + keyguardUpdateMonitor + ) + + whenever(fingerprintInteractiveToAuthProvider.enabledForCurrentUser) + .thenReturn(MutableStateFlow(false)) + + sfpsSensorInteractor = + SideFpsSensorInteractor( + mContext, + fingerprintPropertyRepository, + windowManager, + displayStateInteractor, + Optional.of(fingerprintInteractiveToAuthProvider), + SideFpsLogger(logcatLogBuffer("SfpsLogger")) + ) + + sideFpsProgressBarViewModel = + SideFpsProgressBarViewModel( + mContext, + deviceEntryFingerprintAuthRepository, + sfpsSensorInteractor, + displayStateInteractor, + testScope.backgroundScope, + ) + + underTest = + SideFpsOverlayViewModel( + mContext, + biometricStatusInteractor, + deviceEntrySideFpsOverlayInteractor, + displayStateInteractor, + sfpsSensorInteractor, + sideFpsProgressBarViewModel, + ) + } + + @Test + fun updatesOverlayViewProperties_onDisplayRotationChange_xAlignedSensor() { + testScope.runTest { + setupTestConfiguration( + DeviceConfig.X_ALIGNED, + rotation = DisplayRotation.ROTATION_0, + isInRearDisplayMode = false + ) + + val overlayViewProperties by collectLastValue(underTest.overlayViewProperties) + + runCurrent() + + assertThat(overlayViewProperties?.indicatorAsset).isEqualTo(R.raw.sfps_pulse_landscape) + assertThat(overlayViewProperties?.overlayViewRotation).isEqualTo(0f) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90) + + assertThat(overlayViewProperties?.indicatorAsset).isEqualTo(R.raw.sfps_pulse) + assertThat(overlayViewProperties?.overlayViewRotation).isEqualTo(180f) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180) + + assertThat(overlayViewProperties?.indicatorAsset).isEqualTo(R.raw.sfps_pulse_landscape) + assertThat(overlayViewProperties?.overlayViewRotation).isEqualTo(180f) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270) + + assertThat(overlayViewProperties?.indicatorAsset).isEqualTo(R.raw.sfps_pulse) + assertThat(overlayViewProperties?.overlayViewRotation).isEqualTo(0f) + } + } + + @Test + fun updatesOverlayViewProperties_onDisplayRotationChange_yAlignedSensor() { + testScope.runTest { + setupTestConfiguration( + DeviceConfig.Y_ALIGNED, + rotation = DisplayRotation.ROTATION_0, + isInRearDisplayMode = false + ) + + val overlayViewProperties by collectLastValue(underTest.overlayViewProperties) + + runCurrent() + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0) + assertThat(overlayViewProperties?.indicatorAsset).isEqualTo(R.raw.sfps_pulse) + assertThat(overlayViewProperties?.overlayViewRotation).isEqualTo(0f) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90) + + assertThat(overlayViewProperties?.indicatorAsset).isEqualTo(R.raw.sfps_pulse_landscape) + assertThat(overlayViewProperties?.overlayViewRotation).isEqualTo(0f) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180) + assertThat(overlayViewProperties?.indicatorAsset).isEqualTo(R.raw.sfps_pulse) + assertThat(overlayViewProperties?.overlayViewRotation).isEqualTo(180f) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270) + + assertThat(overlayViewProperties?.indicatorAsset).isEqualTo(R.raw.sfps_pulse_landscape) + assertThat(overlayViewProperties?.overlayViewRotation).isEqualTo(180f) + } + } + + @Test + fun updatesOverlayViewParams_onDisplayRotationChange_xAlignedSensor() { + testScope.runTest { + setupTestConfiguration( + DeviceConfig.X_ALIGNED, + rotation = DisplayRotation.ROTATION_0, + isInRearDisplayMode = false + ) + + val overlayViewParams by collectLastValue(underTest.overlayViewParams) + + underTest.setLottieBounds(Rect(0, 0, boundsWidth, boundsHeight)) + runCurrent() + + assertThat(overlayViewParams).isNotNull() + assertThat(overlayViewParams!!.x).isEqualTo(sensorLocation.sensorLocationX) + assertThat(overlayViewParams!!.y).isEqualTo(0) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90) + assertThat(overlayViewParams).isNotNull() + assertThat(overlayViewParams!!.x).isEqualTo(0) + assertThat(overlayViewParams!!.y) + .isEqualTo( + displayHeight - sensorLocation.sensorLocationX - sensorLocation.sensorRadius * 2 + ) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180) + assertThat(overlayViewParams).isNotNull() + assertThat(overlayViewParams!!.x) + .isEqualTo( + displayWidth - sensorLocation.sensorLocationX - sensorLocation.sensorRadius * 2 + ) + assertThat(overlayViewParams!!.y).isEqualTo(displayHeight - boundsHeight) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270) + assertThat(overlayViewParams).isNotNull() + assertThat(overlayViewParams!!.x).isEqualTo(displayWidth - boundsWidth) + assertThat(overlayViewParams!!.y).isEqualTo(sensorLocation.sensorLocationX) + } + } + + @Test + fun updatesOverlayViewParams_onDisplayRotationChange_yAlignedSensor() { + testScope.runTest { + setupTestConfiguration( + DeviceConfig.Y_ALIGNED, + rotation = DisplayRotation.ROTATION_0, + isInRearDisplayMode = false + ) + + val overlayViewParams by collectLastValue(underTest.overlayViewParams) + + underTest.setLottieBounds(Rect(0, 0, boundsWidth, boundsHeight)) + runCurrent() + + assertThat(overlayViewParams).isNotNull() + assertThat(overlayViewParams!!.x).isEqualTo(displayWidth - boundsWidth) + assertThat(overlayViewParams!!.y).isEqualTo(sensorLocation.sensorLocationY) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90) + assertThat(overlayViewParams).isNotNull() + assertThat(overlayViewParams!!.x).isEqualTo(sensorLocation.sensorLocationY) + assertThat(overlayViewParams!!.y).isEqualTo(0) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180) + assertThat(overlayViewParams).isNotNull() + assertThat(overlayViewParams!!.x).isEqualTo(0) + assertThat(overlayViewParams!!.y) + .isEqualTo( + displayHeight - sensorLocation.sensorLocationY - sensorLocation.sensorRadius * 2 + ) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270) + assertThat(overlayViewParams).isNotNull() + assertThat(overlayViewParams!!.x) + .isEqualTo( + displayWidth - sensorLocation.sensorLocationY - sensorLocation.sensorRadius * 2 + ) + assertThat(overlayViewParams!!.y).isEqualTo(displayHeight - boundsHeight) + } + } + + @Test + fun updatesLottieCallbacks_onShowIndicatorForDeviceEntry() { + testScope.runTest { + val lottieCallbacks by collectLastValue(underTest.lottieCallbacks) + + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.NotRunning + ) + sideFpsProgressBarViewModel.setVisible(false) + + updatePrimaryBouncer( + isShowing = true, + isAnimatingAway = false, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = true + ) + runCurrent() + + assertThat(lottieCallbacks) + .contains(LottieCallback(KeyPath(".blue600", "**"), indicatorColor)) + assertThat(lottieCallbacks) + .contains(LottieCallback(KeyPath(".blue400", "**"), outerRimColor)) + assertThat(lottieCallbacks) + .contains(LottieCallback(KeyPath(".black", "**"), chevronFill)) + } + } + + @Test + fun updatesLottieCallbacks_onShowIndicatorForSystemServer_inDarkMode() { + testScope.runTest { + val lottieCallbacks by collectLastValue(underTest.lottieCallbacks) + setDarkMode(true) + + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.BiometricPromptAuthentication + ) + sideFpsProgressBarViewModel.setVisible(false) + + updatePrimaryBouncer( + isShowing = false, + isAnimatingAway = false, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = true + ) + runCurrent() + + assertThat(lottieCallbacks) + .contains(LottieCallback(KeyPath(".blue600", "**"), color_blue400)) + assertThat(lottieCallbacks) + .contains(LottieCallback(KeyPath(".blue400", "**"), color_blue400)) + } + } + + @Test + fun updatesLottieCallbacks_onShowIndicatorForSystemServer_inLightMode() { + testScope.runTest { + val lottieCallbacks by collectLastValue(underTest.lottieCallbacks) + setDarkMode(false) + + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.BiometricPromptAuthentication + ) + sideFpsProgressBarViewModel.setVisible(false) + + updatePrimaryBouncer( + isShowing = false, + isAnimatingAway = false, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = true + ) + runCurrent() + + assertThat(lottieCallbacks) + .contains(LottieCallback(KeyPath(".black", "**"), Color.WHITE)) + assertThat(lottieCallbacks) + .contains(LottieCallback(KeyPath(".blue600", "**"), color_blue400)) + assertThat(lottieCallbacks) + .contains(LottieCallback(KeyPath(".blue400", "**"), color_blue400)) + } + } + + private fun setDarkMode(inDarkMode: Boolean) { + val uiMode = + if (inDarkMode) { + UI_MODE_NIGHT_YES + } else { + UI_MODE_NIGHT_NO + } + + mContext.resources.configuration.uiMode = uiMode + } + + private fun updatePrimaryBouncer( + isShowing: Boolean, + isAnimatingAway: Boolean, + fpsDetectionRunning: Boolean, + isUnlockingWithFpAllowed: Boolean, + ) { + bouncerRepository.setPrimaryShow(isShowing) + bouncerRepository.setPrimaryStartingToHide(false) + val primaryStartDisappearAnimation = if (isAnimatingAway) Runnable {} else null + bouncerRepository.setPrimaryStartDisappearAnimation(primaryStartDisappearAnimation) + + whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning) + .thenReturn(fpsDetectionRunning) + whenever(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed) + .thenReturn(isUnlockingWithFpAllowed) + mContext.orCreateTestableResources.addOverride( + R.bool.config_show_sidefps_hint_on_bouncer, + true + ) + } + + private suspend fun TestScope.setupTestConfiguration( + deviceConfig: DeviceConfig, + rotation: DisplayRotation = DisplayRotation.ROTATION_0, + isInRearDisplayMode: Boolean, + ) { + this@SideFpsOverlayViewModelTest.deviceConfig = deviceConfig + + when (deviceConfig) { + DeviceConfig.X_ALIGNED -> { + displayWidth = 3000 + displayHeight = 1500 + boundsWidth = 200 + boundsHeight = 100 + sensorLocation = SensorLocationInternal("", 2500, 0, boundsWidth / 2) + } + DeviceConfig.Y_ALIGNED -> { + displayWidth = 2500 + displayHeight = 2000 + boundsWidth = 100 + boundsHeight = 200 + sensorLocation = SensorLocationInternal("", displayWidth, 300, boundsHeight / 2) + } + } + + whenever(windowManager.maximumWindowMetrics) + .thenReturn( + WindowMetrics( + Rect(0, 0, displayWidth, displayHeight), + mock(WindowInsets::class.java), + ) + ) + + contextDisplayInfo.uniqueId = DISPLAY_ID + + fingerprintPropertyRepository.setProperties( + sensorId = 1, + strength = SensorStrength.STRONG, + sensorType = FingerprintSensorType.POWER_BUTTON, + sensorLocations = mapOf(DISPLAY_ID to sensorLocation) + ) + + displayStateRepository.setIsInRearDisplayMode(isInRearDisplayMode) + + displayStateRepository.setCurrentRotation(rotation) + + displayRepository.emitDisplayChangeEvent(0) + runCurrent() + } + + companion object { + private const val DISPLAY_ID = "displayId" + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt index 37a093e4c23f472319862c3d8a238f4c7cc84a2b..dacf23a5a567b625b08c3020e8b61b75d91f513b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt @@ -37,6 +37,7 @@ import com.android.systemui.keyguard.data.repository.FakeTrustRepository import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R +import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any @@ -342,6 +343,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { assertThat(underTest.willDismissWithAction()).isFalse() } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Test fun testSideFpsVisibility() { updateSideFpsVisibilityParameters( @@ -355,6 +357,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { verify(repository).setSideFpsShowing(true) } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Test fun testSideFpsVisibility_notVisible() { updateSideFpsVisibilityParameters( @@ -368,6 +371,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { verify(repository).setSideFpsShowing(false) } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Test fun testSideFpsVisibility_sfpsNotEnabled() { updateSideFpsVisibilityParameters( @@ -381,6 +385,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { verify(repository).setSideFpsShowing(false) } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Test fun testSideFpsVisibility_fpsDetectionNotRunning() { updateSideFpsVisibilityParameters( @@ -394,6 +399,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { verify(repository).setSideFpsShowing(false) } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Test fun testSideFpsVisibility_UnlockingWithFpNotAllowed() { updateSideFpsVisibilityParameters( @@ -407,6 +413,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { verify(repository).setSideFpsShowing(false) } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Test fun testSideFpsVisibility_AnimatingAway() { updateSideFpsVisibilityParameters( @@ -492,6 +499,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { isUnlockingWithFpAllowed: Boolean, isAnimatingAway: Boolean ) { + mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) whenever(repository.primaryBouncerShow.value).thenReturn(isVisible) resources.addOverride(R.bool.config_show_sidefps_hint_on_bouncer, sfpsEnabled) whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..70d3f81de35f03f8a9c44b53499c472c56309837 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.os.Handler +import android.platform.test.annotations.RequiresFlagsEnabled +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardSecurityModel +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.bouncer.ui.BouncerView +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.DismissCallbackRegistry +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.FakeTrustRepository +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.res.R +import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.user.domain.interactor.SelectedUserInteractor +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule + +@OptIn(ExperimentalCoroutinesApi::class) +@RequiresFlagsEnabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR) +@SmallTest +@RunWith(JUnit4::class) +class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { + @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule() + + @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor + + private val bouncerRepository = FakeKeyguardBouncerRepository() + private val biometricSettingsRepository = FakeBiometricSettingsRepository() + + private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor + private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor + + private lateinit var underTest: DeviceEntrySideFpsOverlayInteractor + + private val testScope = TestScope(StandardTestDispatcher()) + + @Before + fun setup() { + primaryBouncerInteractor = + PrimaryBouncerInteractor( + bouncerRepository, + mock(BouncerView::class.java), + mock(Handler::class.java), + mock(KeyguardStateController::class.java), + mock(KeyguardSecurityModel::class.java), + mock(PrimaryBouncerCallbackInteractor::class.java), + mock(FalsingCollector::class.java), + mock(DismissCallbackRegistry::class.java), + mContext, + keyguardUpdateMonitor, + FakeTrustRepository(), + testScope.backgroundScope, + mSelectedUserInteractor, + faceAuthInteractor + ) + alternateBouncerInteractor = + AlternateBouncerInteractor( + mock(StatusBarStateController::class.java), + mock(KeyguardStateController::class.java), + bouncerRepository, + FakeFingerprintPropertyRepository(), + biometricSettingsRepository, + FakeSystemClock(), + keyguardUpdateMonitor, + testScope.backgroundScope, + ) + underTest = + DeviceEntrySideFpsOverlayInteractor( + mContext, + FakeDeviceEntryFingerprintAuthRepository(), + primaryBouncerInteractor, + alternateBouncerInteractor, + keyguardUpdateMonitor + ) + } + + @Test + fun updatesShowIndicatorForDeviceEntry_onPrimaryBouncerShowing() = + testScope.runTest { + val showIndicatorForDeviceEntry by + collectLastValue(underTest.showIndicatorForDeviceEntry) + runCurrent() + + updatePrimaryBouncer( + isShowing = true, + isAnimatingAway = false, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = true + ) + assertThat(showIndicatorForDeviceEntry).isEqualTo(true) + } + + @Test + fun updatesShowIndicatorForDeviceEntry_onPrimaryBouncerHidden() = + testScope.runTest { + val showIndicatorForDeviceEntry by + collectLastValue(underTest.showIndicatorForDeviceEntry) + runCurrent() + + updatePrimaryBouncer( + isShowing = false, + isAnimatingAway = false, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = true + ) + assertThat(showIndicatorForDeviceEntry).isEqualTo(false) + } + + @Test + fun updatesShowIndicatorForDeviceEntry_fromPrimaryBouncer_whenFpsDetectionNotRunning() { + testScope.runTest { + val showIndicatorForDeviceEntry by + collectLastValue(underTest.showIndicatorForDeviceEntry) + runCurrent() + + updatePrimaryBouncer( + isShowing = true, + isAnimatingAway = false, + fpsDetectionRunning = false, + isUnlockingWithFpAllowed = true + ) + assertThat(showIndicatorForDeviceEntry).isEqualTo(false) + } + } + + @Test + fun updatesShowIndicatorForDeviceEntry_fromPrimaryBouncer_onUnlockingWithFpDisallowed() { + testScope.runTest { + val showIndicatorForDeviceEntry by + collectLastValue(underTest.showIndicatorForDeviceEntry) + runCurrent() + + updatePrimaryBouncer( + isShowing = true, + isAnimatingAway = false, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = false + ) + assertThat(showIndicatorForDeviceEntry).isEqualTo(false) + } + } + + @Test + fun updatesShowIndicatorForDeviceEntry_onPrimaryBouncerAnimatingAway() { + testScope.runTest { + val showIndicatorForDeviceEntry by + collectLastValue(underTest.showIndicatorForDeviceEntry) + runCurrent() + + updatePrimaryBouncer( + isShowing = true, + isAnimatingAway = true, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = true + ) + assertThat(showIndicatorForDeviceEntry).isEqualTo(false) + } + } + + @Test + fun updatesShowIndicatorForDeviceEntry_onAlternateBouncerRequest() = + testScope.runTest { + val showIndicatorForDeviceEntry by + collectLastValue(underTest.showIndicatorForDeviceEntry) + runCurrent() + + bouncerRepository.setAlternateVisible(true) + assertThat(showIndicatorForDeviceEntry).isEqualTo(true) + + bouncerRepository.setAlternateVisible(false) + assertThat(showIndicatorForDeviceEntry).isEqualTo(false) + } + + private fun updatePrimaryBouncer( + isShowing: Boolean, + isAnimatingAway: Boolean, + fpsDetectionRunning: Boolean, + isUnlockingWithFpAllowed: Boolean, + ) { + bouncerRepository.setPrimaryShow(isShowing) + bouncerRepository.setPrimaryStartingToHide(false) + val primaryStartDisappearAnimation = if (isAnimatingAway) Runnable {} else null + bouncerRepository.setPrimaryStartDisappearAnimation(primaryStartDisappearAnimation) + + whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning) + .thenReturn(fpsDetectionRunning) + whenever(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed) + .thenReturn(isUnlockingWithFpAllowed) + mContext.orCreateTestableResources.addOverride( + R.bool.config_show_sidefps_hint_on_bouncer, + true + ) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..1c8bd3b58dfe8e7687e1d76ea8ce609d0c75f6e5 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.biometrics.data.repository + +import com.android.systemui.biometrics.shared.model.AuthenticationReason +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeBiometricStatusRepository : BiometricStatusRepository { + private val _fingerprintAuthenticationReason = + MutableStateFlow<AuthenticationReason>(AuthenticationReason.NotRunning) + override val fingerprintAuthenticationReason: StateFlow<AuthenticationReason> = + _fingerprintAuthenticationReason.asStateFlow() + + fun setFingerprintAuthenticationReason(reason: AuthenticationReason) { + _fingerprintAuthenticationReason.value = reason + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt index f84481c556644aa6a39c683dcd318a0133865b12..ff5179a7042c2f150f82af0ad71da186fdb2f5b9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt @@ -1,5 +1,6 @@ package com.android.systemui.bouncer.data.repository +import com.android.systemui.biometrics.shared.SideFpsControllerRefactor import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel import com.android.systemui.dagger.SysUISingleton @@ -113,7 +114,9 @@ class FakeKeyguardBouncerRepository @Inject constructor() : KeyguardBouncerRepos _isBackButtonEnabled.value = isBackButtonEnabled } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR override fun setSideFpsShowing(isShowing: Boolean) { + SideFpsControllerRefactor.assertInLegacyMode() _sideFpsShowing.value = isShowing } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt index c9160efb75a472f624d9c2ae5acc3da3009dc622..1d44929a20f0db70bdf6acc79e7d16c30f43d98b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt @@ -54,6 +54,11 @@ class FakeDeviceEntryFingerprintAuthRepository @Inject constructor() : private var _authenticationStatus = MutableStateFlow<FingerprintAuthenticationStatus?>(null) override val authenticationStatus: Flow<FingerprintAuthenticationStatus> get() = _authenticationStatus.filterNotNull() + + private var _shouldUpdateIndicatorVisibility = MutableStateFlow(false) + override val shouldUpdateIndicatorVisibility: Flow<Boolean> + get() = _shouldUpdateIndicatorVisibility + fun setAuthenticationStatus(status: FingerprintAuthenticationStatus) { _authenticationStatus.value = status } diff --git a/services/core/Android.bp b/services/core/Android.bp index a0ccbf3acf0a67ffc98b0e05a8d134ad5f680565..f5a80d80f2713577d1e5d31c23c3405a10bbdbf5 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -200,6 +200,7 @@ java_library_static { "notification_flags_lib", "biometrics_flags_lib", "am_flags_lib", + "com_android_systemui_shared_flags_lib", "com_android_wm_shell_flags_lib", "com.android.server.utils_aconfig-java", "service-jobscheduler-deviceidle.flags-aconfig-java", diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index 0629e6373b6eda08fd0619f8933a5b235828cc62..dafea9a199fdc6a1616efca58b8549b002e8827b 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -36,6 +36,7 @@ import android.annotation.Nullable; import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; +import android.hardware.biometrics.AuthenticationStateListener; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.ComponentInfoInternal; @@ -384,6 +385,26 @@ public class AuthService extends SystemService { } } + @Override + public void registerAuthenticationStateListener(AuthenticationStateListener listener) + throws RemoteException { + checkInternalPermission(); + final IFingerprintService fingerprintService = mInjector.getFingerprintService(); + if (fingerprintService != null) { + fingerprintService.registerAuthenticationStateListener(listener); + } + } + + @Override + public void unregisterAuthenticationStateListener(AuthenticationStateListener listener) + throws RemoteException { + checkInternalPermission(); + final IFingerprintService fingerprintService = mInjector.getFingerprintService(); + if (fingerprintService != null) { + fingerprintService.unregisterAuthenticationStateListener(listener); + } + } + @Override public void invalidateAuthenticatorIds(int userId, int fromSensorId, IInvalidationCallback callback) throws RemoteException { diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java index a47135fd032ff8843d42685ad9ed6ef2ebf39f44..f9568ea9d72bad6ce27c16eae82a365415ebd529 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -27,7 +27,7 @@ import android.hardware.biometrics.AuthenticateOptions; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; -import android.hardware.biometrics.BiometricOverlayConstants; +import android.hardware.biometrics.BiometricRequestConstants; import android.os.IBinder; import android.os.RemoteException; import android.security.KeyStore; @@ -430,19 +430,19 @@ public abstract class AuthenticationClient<T, O extends AuthenticateOptions> return mLockoutTracker; } - protected int getShowOverlayReason() { + protected int getRequestReason() { if (isKeyguard()) { - return BiometricOverlayConstants.REASON_AUTH_KEYGUARD; + return BiometricRequestConstants.REASON_AUTH_KEYGUARD; } else if (isBiometricPrompt()) { // BP reason always takes precedent over settings, since callers from within // settings can always invoke BP. - return BiometricOverlayConstants.REASON_AUTH_BP; + return BiometricRequestConstants.REASON_AUTH_BP; } else if (isSettings()) { // This is pretty much only for FingerprintManager#authenticate usage from // FingerprintSettings. - return BiometricOverlayConstants.REASON_AUTH_SETTINGS; + return BiometricRequestConstants.REASON_AUTH_SETTINGS; } else { - return BiometricOverlayConstants.REASON_AUTH_OTHER; + return BiometricRequestConstants.REASON_AUTH_OTHER; } } } diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java new file mode 100644 index 0000000000000000000000000000000000000000..58635353c7806c90627b8f7fbb96afc9d11730a5 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors; + +import android.annotation.NonNull; +import android.hardware.biometrics.AuthenticationStateListener; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Low-level callback interface between BiometricManager and AuthService. Allows core system + * services (e.g. SystemUI) to register and unregister listeners for updates about the current + * state of biometric authentication. + * @hide */ +public class AuthenticationStateListeners implements IBinder.DeathRecipient { + + private static final String TAG = "AuthenticationStateListeners"; + + @NonNull + private final CopyOnWriteArrayList<AuthenticationStateListener> mAuthenticationStateListeners = + new CopyOnWriteArrayList<>(); + + /** + * Enables clients to register an AuthenticationStateListener for updates about the current + * state of biometric authentication. + * @param listener listener to register + */ + public void registerAuthenticationStateListener( + @NonNull AuthenticationStateListener listener) { + mAuthenticationStateListeners.add(listener); + try { + listener.asBinder().linkToDeath(this, 0 /* flags */); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to link to death", e); + } + } + + /** + * Enables clients to unregister an AuthenticationStateListener. + * @param listener listener to register + */ + public void unregisterAuthenticationStateListener( + @NonNull AuthenticationStateListener listener) { + mAuthenticationStateListeners.remove(listener); + } + + /** + * Defines behavior in response to authentication starting + * @param requestReason reason from [BiometricRequestConstants.RequestReason] for requesting + * authentication starting + */ + public void onAuthenticationStarted(int requestReason) { + for (AuthenticationStateListener listener: mAuthenticationStateListeners) { + try { + listener.onAuthenticationStarted(requestReason); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception in notifying listener that authentication " + + "started", e); + } + } + } + + /** + * Defines behavior in response to authentication stopping + */ + public void onAuthenticationStopped() { + for (AuthenticationStateListener listener: mAuthenticationStateListeners) { + try { + listener.onAuthenticationStopped(); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception in notifying listener that authentication " + + "stopped", e); + } + } + } + + @Override + public void binderDied() { + // Do nothing, handled below + } + + @Override + public void binderDied(IBinder who) { + Slog.w(TAG, "Callback binder died: " + who); + if (mAuthenticationStateListeners.removeIf(listener -> listener.asBinder().equals(who))) { + Slog.w(TAG, "Removed dead listener for " + who); + } else { + Slog.w(TAG, "No dead listeners found"); + } + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java index 483ce75eae98df91f002fed6c931427ac0c6d31a..2c4d30b622addafe49d7e8c99bd8420b190ef772 100644 --- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java @@ -19,7 +19,7 @@ package com.android.server.biometrics.sensors; import android.annotation.NonNull; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; -import android.hardware.biometrics.BiometricOverlayConstants; +import android.hardware.biometrics.BiometricRequestConstants; import android.hardware.fingerprint.FingerprintManager; import android.os.IBinder; import android.os.RemoteException; @@ -135,14 +135,14 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> implements En return true; } - protected int getOverlayReasonFromEnrollReason(@FingerprintManager.EnrollReason int reason) { + protected int getRequestReasonFromEnrollReason(@FingerprintManager.EnrollReason int reason) { switch (reason) { case FingerprintManager.ENROLL_FIND_SENSOR: - return BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR; + return BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR; case FingerprintManager.ENROLL_ENROLL: - return BiometricOverlayConstants.REASON_ENROLL_ENROLLING; + return BiometricRequestConstants.REASON_ENROLL_ENROLLING; default: - return BiometricOverlayConstants.REASON_UNKNOWN; + return BiometricRequestConstants.REASON_UNKNOWN; } } } diff --git a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java index aeb6b6e2a90737b4485496b0d030dfe0aa71dcc8..3d20efc88c77b850601e11039f78fbe33e4a529e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java +++ b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java @@ -16,9 +16,11 @@ package com.android.server.biometrics.sensors; +import static com.android.systemui.shared.Flags.sidefpsControllerRefactor; + import android.annotation.NonNull; import android.annotation.Nullable; -import android.hardware.biometrics.BiometricOverlayConstants; +import android.hardware.biometrics.BiometricRequestConstants; import android.hardware.fingerprint.ISidefpsController; import android.hardware.fingerprint.IUdfpsOverlayController; import android.hardware.fingerprint.IUdfpsOverlayControllerCallback; @@ -42,6 +44,8 @@ public final class SensorOverlays { private static final String TAG = "SensorOverlays"; @NonNull private final Optional<IUdfpsOverlayController> mUdfpsOverlayController; + + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @NonNull private final Optional<ISidefpsController> mSidefpsController; /** @@ -57,6 +61,17 @@ public final class SensorOverlays { mSidefpsController = Optional.ofNullable(sidefpsController); } + /** + * Create an overlay controller for each modality. + * + * @param udfpsOverlayController under display fps or null if not present on device + */ + public SensorOverlays(@Nullable IUdfpsOverlayController udfpsOverlayController) { + mUdfpsOverlayController = Optional.ofNullable(udfpsOverlayController); + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR + mSidefpsController = Optional.empty(); + } + /** * Show the overlay. * @@ -64,13 +79,15 @@ public final class SensorOverlays { * @param reason reason for showing * @param client client performing operation */ - public void show(int sensorId, @BiometricOverlayConstants.ShowReason int reason, + public void show(int sensorId, @BiometricRequestConstants.RequestReason int reason, @NonNull AcquisitionClient<?> client) { - if (mSidefpsController.isPresent()) { - try { - mSidefpsController.get().show(sensorId, reason); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception when showing the side-fps overlay", e); + if (!sidefpsControllerRefactor()) { + if (mSidefpsController.isPresent()) { + try { + mSidefpsController.get().show(sensorId, reason); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when showing the side-fps overlay", e); + } } } @@ -98,11 +115,13 @@ public final class SensorOverlays { * @param sensorId sensor id */ public void hide(int sensorId) { - if (mSidefpsController.isPresent()) { - try { - mSidefpsController.get().hide(sensorId); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception when hiding the side-fps overlay", e); + if (!sidefpsControllerRefactor()) { + if (mSidefpsController.isPresent()) { + try { + mSidefpsController.get().hide(sensorId); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when hiding the side-fps overlay", e); + } } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index 769554315b6e78fd08d556d1a8e7393bcb02fda6..83b306b07c277075aefd2ed995769dcb3594d44b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -33,6 +33,7 @@ import android.app.ActivityManager; import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; +import android.hardware.biometrics.AuthenticationStateListener; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.IBiometricSensorReceiver; @@ -81,6 +82,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.server.SystemService; import com.android.server.biometrics.Utils; import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricStateCallback; import com.android.server.biometrics.sensors.ClientMonitorCallback; @@ -128,6 +130,8 @@ public class FingerprintService extends SystemService { private final BiometricStateCallback<ServiceProvider, FingerprintSensorPropertiesInternal> mBiometricStateCallback; @NonNull + private final AuthenticationStateListeners mAuthenticationStateListeners; + @NonNull private final Handler mHandler; @NonNull private final FingerprintServiceRegistry mRegistry; @@ -891,6 +895,7 @@ public class FingerprintService extends SystemService { return providers; }); } + @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public void addAuthenticatorsRegisteredCallback( @@ -900,6 +905,24 @@ public class FingerprintService extends SystemService { mRegistry.addAllRegisteredCallback(callback); } + @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) + @Override + public void registerAuthenticationStateListener( + @NonNull AuthenticationStateListener listener) { + super.registerAuthenticationStateListener_enforcePermission(); + + mAuthenticationStateListeners.registerAuthenticationStateListener(listener); + } + + @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) + @Override + public void unregisterAuthenticationStateListener( + @NonNull AuthenticationStateListener listener) { + super.unregisterAuthenticationStateListener_enforcePermission(); + + mAuthenticationStateListeners.unregisterAuthenticationStateListener(listener); + } + @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) { @@ -957,6 +980,7 @@ public class FingerprintService extends SystemService { } } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public void setSidefpsController(@NonNull ISidefpsController controller) { @@ -1014,6 +1038,7 @@ public class FingerprintService extends SystemService { mLockoutResetDispatcher = new LockoutResetDispatcher(context); mLockPatternUtils = new LockPatternUtils(context); mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context)); + mAuthenticationStateListeners = new AuthenticationStateListeners(); mFingerprintProvider = fingerprintProvider != null ? fingerprintProvider : (name) -> { final String fqName = IFingerprint.DESCRIPTOR + "/" + name; @@ -1022,9 +1047,9 @@ public class FingerprintService extends SystemService { if (fp != null) { try { return new FingerprintProvider(getContext(), - mBiometricStateCallback, fp.getSensorProps(), name, - mLockoutResetDispatcher, mGestureAvailabilityDispatcher, - mBiometricContext); + mBiometricStateCallback, mAuthenticationStateListeners, + fp.getSensorProps(), name, mLockoutResetDispatcher, + mGestureAvailabilityDispatcher, mBiometricContext); } catch (RemoteException e) { Slog.e(TAG, "Remote exception in getSensorProps: " + fqName); } @@ -1086,13 +1111,13 @@ public class FingerprintService extends SystemService { Fingerprint21UdfpsMock.CONFIG_ENABLE_TEST_UDFPS, 0 /* default */, UserHandle.USER_CURRENT) != 0) { fingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(), - mBiometricStateCallback, hidlSensor, - mLockoutResetDispatcher, mGestureAvailabilityDispatcher, + mBiometricStateCallback, mAuthenticationStateListeners, + hidlSensor, mLockoutResetDispatcher, mGestureAvailabilityDispatcher, BiometricContext.getInstance(getContext())); } else { fingerprint21 = Fingerprint21.newInstance(getContext(), - mBiometricStateCallback, hidlSensor, mHandler, - mLockoutResetDispatcher, mGestureAvailabilityDispatcher); + mBiometricStateCallback, mAuthenticationStateListeners, hidlSensor, + mHandler, mLockoutResetDispatcher, mGestureAvailabilityDispatcher); } providers.add(fingerprint21); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java index a15d1a4df39f053a8b6b7d43594a208c11771140..fc37d7020a211979674cd811c41f1e6f293cd390 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java @@ -135,6 +135,7 @@ public interface ServiceProvider extends void onPowerPressed(); + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR /** * Sets side-fps controller * @param controller side-fps controller diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index 337c3c299e54a67692b2a846f3099b1187130419..29c5a3de3a80911bd00f33823ef7c7a1dfde0d79 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; +import static com.android.systemui.shared.Flags.sidefpsControllerRefactor; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.TaskStackListener; @@ -48,6 +50,7 @@ import com.android.server.biometrics.log.OperationContextExt; import com.android.server.biometrics.log.Probe; import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.AuthenticationClient; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; @@ -84,6 +87,7 @@ public class FingerprintAuthenticationClient private final int mSkipWaitForPowerVendorAcquireMessage; private final long mFingerUpIgnoresPower = 500; private final AuthSessionCoordinator mAuthSessionCoordinator; + @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners; @Nullable private ICancellationSignal mCancellationSignal; private boolean mIsPointerDown; @@ -110,7 +114,9 @@ public class FingerprintAuthenticationClient boolean isStrongBiometric, @Nullable TaskStackListener taskStackListener, @Nullable IUdfpsOverlayController udfpsOverlayController, + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Nullable ISidefpsController sidefpsController, + @NonNull AuthenticationStateListeners authenticationStateListeners, boolean allowBackgroundAuthentication, @NonNull FingerprintSensorPropertiesInternal sensorProps, @NonNull Handler handler, @@ -136,7 +142,12 @@ public class FingerprintAuthenticationClient false /* shouldVibrate */, biometricStrength); setRequestId(requestId); - mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); + if (sidefpsControllerRefactor()) { + mSensorOverlays = new SensorOverlays(udfpsOverlayController); + } else { + mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); + } + mAuthenticationStateListeners = authenticationStateListeners; mSensorProps = sensorProps; mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */); mHandler = handler; @@ -216,6 +227,9 @@ public class FingerprintAuthenticationClient if (authenticated) { mState = STATE_STOPPED; mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } } else { mState = STATE_STARTED_PAUSED_ATTEMPTED; } @@ -241,6 +255,9 @@ public class FingerprintAuthenticationClient // controlled by the HAL, the framework must stop the sensor before finishing the // client. mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */); cancel(); } @@ -266,11 +283,17 @@ public class FingerprintAuthenticationClient } mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } } @Override protected void startHalOperation() { - mSensorOverlays.show(getSensorId(), getShowOverlayReason(), this); + mSensorOverlays.show(getSensorId(), getRequestReason(), this); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStarted(getRequestReason()); + } try { mCancellationSignal = doAuthenticate(); @@ -280,6 +303,9 @@ public class FingerprintAuthenticationClient BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } mCallback.onClientFinished(this, false /* success */); } } @@ -323,6 +349,9 @@ public class FingerprintAuthenticationClient @Override protected void stopHalOperation() { mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } unsubscribeBiometricContext(); if (mCancellationSignal != null) { @@ -423,6 +452,9 @@ public class FingerprintAuthenticationClient } mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } mCallback.onClientFinished(this, false /* success */); } @@ -450,6 +482,9 @@ public class FingerprintAuthenticationClient } mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } mCallback.onClientFinished(this, false /* success */); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java index e2413ee1c016241447611429922538e6981fe627..e58e5ae117b57370fe3e4cdf6fb95a736c9b5ef5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java @@ -16,10 +16,12 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; +import static com.android.systemui.shared.Flags.sidefpsControllerRefactor; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.hardware.biometrics.BiometricOverlayConstants; +import android.hardware.biometrics.BiometricRequestConstants; import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.fingerprint.FingerprintAuthenticateOptions; import android.hardware.fingerprint.IUdfpsOverlayController; @@ -66,7 +68,12 @@ public class FingerprintDetectClient extends AcquisitionClient<AidlSession> true /* shouldVibrate */, biometricLogger, biometricContext); setRequestId(requestId); mIsStrongBiometric = isStrongBiometric; - mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController*/); + if (sidefpsControllerRefactor()) { + mSensorOverlays = new SensorOverlays(udfpsOverlayController); + } else { + mSensorOverlays = new SensorOverlays( + udfpsOverlayController, null /* sideFpsController */); + } mOptions = options; } @@ -93,7 +100,7 @@ public class FingerprintDetectClient extends AcquisitionClient<AidlSession> @Override protected void startHalOperation() { - mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD, + mSensorOverlays.show(getSensorId(), BiometricRequestConstants.REASON_AUTH_KEYGUARD, this); try { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index 06550d8b4fce756e890ea6a576cb527aab76bd41..c0761ed8f32b76f4b710fbfc3586483f4345056a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; +import static com.android.systemui.shared.Flags.sidefpsControllerRefactor; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -42,6 +44,7 @@ import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.log.CallbackWithProbe; import com.android.server.biometrics.log.OperationContextExt; import com.android.server.biometrics.log.Probe; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; @@ -65,6 +68,7 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement @NonNull private final CallbackWithProbe<Probe> mALSProbeCallback; private final @FingerprintManager.EnrollReason int mEnrollReason; + @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners; @Nullable private ICancellationSignal mCancellationSignal; private final int mMaxTemplatesPerUser; private boolean mIsPointerDown; @@ -80,15 +84,18 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement } } - public FingerprintEnrollClient(@NonNull Context context, - @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, long requestId, + public FingerprintEnrollClient( + @NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, + @NonNull IBinder token, long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, @NonNull FingerprintSensorPropertiesInternal sensorProps, @Nullable IUdfpsOverlayController udfpsOverlayController, + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Nullable ISidefpsController sidefpsController, + @NonNull AuthenticationStateListeners authenticationStateListeners, int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) { // UDFPS haptics occur when an image is acquired (instead of when the result is known) super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, @@ -96,7 +103,13 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement logger, biometricContext); setRequestId(requestId); mSensorProps = sensorProps; - mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); + if (sidefpsControllerRefactor()) { + mSensorOverlays = new SensorOverlays(udfpsOverlayController); + } else { + mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); + } + mAuthenticationStateListeners = authenticationStateListeners; + mMaxTemplatesPerUser = maxTemplatesPerUser; mALSProbeCallback = getLogger().getAmbientLightProbe(true /* startWithClient */); @@ -130,7 +143,11 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement if (remaining == 0) { mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } } + } @Override @@ -159,8 +176,10 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement @Override public void onError(int errorCode, int vendorCode) { super.onError(errorCode, vendorCode); - mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } } @Override @@ -171,8 +190,12 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement @Override protected void startHalOperation() { - mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason), + mSensorOverlays.show(getSensorId(), getRequestReasonFromEnrollReason(mEnrollReason), this); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStarted( + getRequestReasonFromEnrollReason(mEnrollReason)); + } BiometricNotificationUtils.cancelBadCalibrationNotification(getContext()); try { @@ -210,6 +233,10 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement @Override protected void stopHalOperation() { mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } + unsubscribeBiometricContext(); if (mCancellationSignal != null) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index 9985b06833c947c70d2a5e279e18dc5cb5eb648b..032ab87196f80e71916b550117f02b713ba2c88b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -64,6 +64,7 @@ import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.AuthenticationClient; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.BiometricStateCallback; @@ -108,6 +109,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @NonNull private final BiometricStateCallback mBiometricStateCallback; @NonNull + private final AuthenticationStateListeners mAuthenticationStateListeners; + @NonNull private final String mHalInstanceName; @NonNull private final Handler mHandler; @@ -122,6 +125,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @NonNull private final BiometricContext mBiometricContext; @Nullable private IFingerprint mDaemon; @Nullable private IUdfpsOverlayController mUdfpsOverlayController; + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Nullable private ISidefpsController mSidefpsController; private AuthSessionCoordinator mAuthSessionCoordinator; @Nullable private AuthenticationStatsCollector mAuthenticationStatsCollector; @@ -157,16 +161,19 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi public FingerprintProvider(@NonNull Context context, @NonNull BiometricStateCallback biometricStateCallback, + @NonNull AuthenticationStateListeners authenticationStateListeners, @NonNull SensorProps[] props, @NonNull String halInstanceName, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @NonNull BiometricContext biometricContext) { - this(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher, - gestureAvailabilityDispatcher, biometricContext, null /* daemon */); + this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName, + lockoutResetDispatcher, gestureAvailabilityDispatcher, biometricContext, + null /* daemon */); } @VisibleForTesting FingerprintProvider(@NonNull Context context, @NonNull BiometricStateCallback biometricStateCallback, + @NonNull AuthenticationStateListeners authenticationStateListeners, @NonNull SensorProps[] props, @NonNull String halInstanceName, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @@ -174,6 +181,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi IFingerprint daemon) { mContext = context; mBiometricStateCallback = biometricStateCallback; + mAuthenticationStateListeners = authenticationStateListeners; mHalInstanceName = halInstanceName; mFingerprintSensors = new SensorList<>(ActivityManager.getService()); mHandler = new Handler(Looper.getMainLooper()); @@ -434,7 +442,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mBiometricContext, mFingerprintSensors.get(sensorId).getSensorProperties(), mUdfpsOverlayController, mSidefpsController, - maxTemplatesPerUser, enrollReason); + mAuthenticationStateListeners, maxTemplatesPerUser, enrollReason); scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback( mBiometricStateCallback, new ClientMonitorCallback() { @Override @@ -498,7 +506,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mBiometricContext, isStrongBiometric, mTaskStackListener, mUdfpsOverlayController, mSidefpsController, - allowBackgroundAuthentication, + mAuthenticationStateListeners, allowBackgroundAuthentication, mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler, Utils.getCurrentStrength(sensorId), SystemClock.elapsedRealtimeClock(), @@ -732,6 +740,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Override public void setSidefpsController(@NonNull ISidefpsController controller) { mSidefpsController = controller; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java index 8bfa560b80317c42cfedbe6538cff73c0ff8874f..d3cecd0e34c7387fc7145e82ae63f8055b0fc623 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java @@ -68,6 +68,7 @@ import com.android.server.biometrics.sensors.AcquisitionClient; import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.AuthenticationConsumer; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.BiometricStateCallback; @@ -115,6 +116,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider final Context mContext; @NonNull private final BiometricStateCallback mBiometricStateCallback; + @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners; private final ActivityTaskManager mActivityTaskManager; @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties; private final BiometricScheduler mScheduler; @@ -128,6 +130,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider @Nullable private IBiometricsFingerprint mDaemon; @NonNull private final HalResultController mHalResultController; @Nullable private IUdfpsOverlayController mUdfpsOverlayController; + + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Nullable private ISidefpsController mSidefpsController; @NonNull private final BiometricContext mBiometricContext; @Nullable private AuthenticationStatsCollector mAuthenticationStatsCollector; @@ -330,6 +334,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider @VisibleForTesting Fingerprint21(@NonNull Context context, @NonNull BiometricStateCallback biometricStateCallback, + @NonNull AuthenticationStateListeners authenticationStateListeners, @NonNull FingerprintSensorPropertiesInternal sensorProps, @NonNull BiometricScheduler scheduler, @NonNull Handler handler, @@ -338,6 +343,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider @NonNull BiometricContext biometricContext) { mContext = context; mBiometricStateCallback = biometricStateCallback; + mAuthenticationStateListeners = authenticationStateListeners; mBiometricContext = biometricContext; mSensorProperties = sensorProps; @@ -378,6 +384,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider public static Fingerprint21 newInstance(@NonNull Context context, @NonNull BiometricStateCallback biometricStateCallback, + @NonNull AuthenticationStateListeners authenticationStateListeners, @NonNull FingerprintSensorPropertiesInternal sensorProps, @NonNull Handler handler, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @@ -388,8 +395,9 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider gestureAvailabilityDispatcher); final HalResultController controller = new HalResultController(sensorProps.sensorId, context, handler, scheduler); - return new Fingerprint21(context, biometricStateCallback, sensorProps, scheduler, handler, - lockoutResetDispatcher, controller, BiometricContext.getInstance(context)); + return new Fingerprint21(context, biometricStateCallback, authenticationStateListeners, + sensorProps, scheduler, handler, lockoutResetDispatcher, controller, + BiometricContext.getInstance(context)); } @Override @@ -719,7 +727,10 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mSensorProperties.sensorId, createLogger(BiometricsProtoEnums.ACTION_ENROLL, BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason); + mBiometricContext, mUdfpsOverlayController, + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR + mSidefpsController, + mAuthenticationStateListeners, enrollReason); mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { @@ -761,10 +772,14 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), mBiometricContext, null /* sensorProps */, - mUdfpsOverlayController, mSidefpsController, + mUdfpsOverlayController, + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR + mSidefpsController, + mAuthenticationStateListeners, mContext.getResources().getInteger( com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser), enrollReason); + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { @@ -880,6 +895,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mBiometricContext, isStrongBiometric, mTaskStackListener, mUdfpsOverlayController, mSidefpsController, + mAuthenticationStateListeners, allowBackgroundAuthentication, mSensorProperties, mHandler, Utils.getCurrentStrength(mSensorId), null /* clock */, mLockoutTracker); mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); @@ -898,6 +914,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mBiometricContext, isStrongBiometric, mTaskStackListener, mLockoutTracker, mUdfpsOverlayController, mSidefpsController, + mAuthenticationStateListeners, allowBackgroundAuthentication, mSensorProperties, Utils.getCurrentStrength(mSensorId)); mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); @@ -1123,6 +1140,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mUdfpsOverlayController = controller; } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Override public void setSidefpsController(@NonNull ISidefpsController controller) { mSidefpsController = controller; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java index c1a9370b542325c59d18cb67e09cd11cd8aceb26..88dae6fcc453775fbb8647fa08820cb567c44bfa 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java @@ -41,6 +41,7 @@ import android.util.SparseBooleanArray; import com.android.internal.R; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.sensors.AuthenticationConsumer; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.BiometricStateCallback; @@ -248,6 +249,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage public static Fingerprint21UdfpsMock newInstance(@NonNull Context context, @NonNull BiometricStateCallback biometricStateCallback, + @NonNull AuthenticationStateListeners authenticationStateListeners, @NonNull FingerprintSensorPropertiesInternal sensorProps, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @@ -259,8 +261,9 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage new TestableBiometricScheduler(TAG, handler, gestureAvailabilityDispatcher); final MockHalResultController controller = new MockHalResultController(sensorProps.sensorId, context, handler, scheduler); - return new Fingerprint21UdfpsMock(context, biometricStateCallback, sensorProps, scheduler, - handler, lockoutResetDispatcher, controller, biometricContext); + return new Fingerprint21UdfpsMock(context, biometricStateCallback, + authenticationStateListeners, sensorProps, scheduler, handler, + lockoutResetDispatcher, controller, biometricContext); } private static abstract class FakeFingerRunnable implements Runnable { @@ -388,14 +391,15 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage private Fingerprint21UdfpsMock(@NonNull Context context, @NonNull BiometricStateCallback biometricStateCallback, + @NonNull AuthenticationStateListeners authenticationStateListeners, @NonNull FingerprintSensorPropertiesInternal sensorProps, @NonNull TestableBiometricScheduler scheduler, @NonNull Handler handler, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull MockHalResultController controller, @NonNull BiometricContext biometricContext) { - super(context, biometricStateCallback, sensorProps, scheduler, handler, - lockoutResetDispatcher, controller, biometricContext); + super(context, biometricStateCallback, authenticationStateListeners, sensorProps, scheduler, + handler, lockoutResetDispatcher, controller, biometricContext); mScheduler = scheduler; mScheduler.init(this); mHandler = handler; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java index 9966e910d502ba4c7c6eaf4581ef30e11ab13e99..4c1d4d6d6d124a5527d51cc50764b04b76c2e221 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors.fingerprint.hidl; +import static com.android.systemui.shared.Flags.sidefpsControllerRefactor; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.TaskStackListener; @@ -40,6 +42,7 @@ import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.log.CallbackWithProbe; import com.android.server.biometrics.log.Probe; import com.android.server.biometrics.sensors.AuthenticationClient; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; @@ -68,6 +71,7 @@ class FingerprintAuthenticationClient @NonNull private final SensorOverlays mSensorOverlays; @NonNull private final FingerprintSensorPropertiesInternal mSensorProps; @NonNull private final CallbackWithProbe<Probe> mALSProbeCallback; + @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners; private boolean mIsPointerDown; @@ -81,7 +85,9 @@ class FingerprintAuthenticationClient @NonNull TaskStackListener taskStackListener, @NonNull LockoutFrameworkImpl lockoutTracker, @Nullable IUdfpsOverlayController udfpsOverlayController, + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Nullable ISidefpsController sidefpsController, + @NonNull AuthenticationStateListeners authenticationStateListeners, boolean allowBackgroundAuthentication, @NonNull FingerprintSensorPropertiesInternal sensorProps, @Authenticators.Types int sensorStrength) { @@ -91,7 +97,12 @@ class FingerprintAuthenticationClient false /* shouldVibrate */, sensorStrength); setRequestId(requestId); mLockoutFrameworkImpl = lockoutTracker; - mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); + if (sidefpsControllerRefactor()) { + mSensorOverlays = new SensorOverlays(udfpsOverlayController); + } else { + mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); + } + mAuthenticationStateListeners = authenticationStateListeners; mSensorProps = sensorProps; mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */); } @@ -128,6 +139,9 @@ class FingerprintAuthenticationClient mState = STATE_STOPPED; resetFailedAttempts(getTargetUserId()); mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } } else { mState = STATE_STARTED_PAUSED_ATTEMPTED; final @LockoutTracker.LockoutMode int lockoutMode = @@ -141,6 +155,9 @@ class FingerprintAuthenticationClient // controlled by the HAL, the framework must stop the sensor before finishing the // client. mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */); cancel(); } @@ -156,6 +173,9 @@ class FingerprintAuthenticationClient } mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } } private void resetFailedAttempts(int userId) { @@ -205,7 +225,10 @@ class FingerprintAuthenticationClient @Override protected void startHalOperation() { - mSensorOverlays.show(getSensorId(), getShowOverlayReason(), this); + mSensorOverlays.show(getSensorId(), getRequestReason(), this); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStarted(getRequestReason()); + } try { // GroupId was never used. In fact, groupId is always the same as userId. @@ -215,6 +238,9 @@ class FingerprintAuthenticationClient onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } mCallback.onClientFinished(this, false /* success */); } } @@ -222,6 +248,9 @@ class FingerprintAuthenticationClient @Override protected void stopHalOperation() { mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } try { getFreshDaemon().cancel(); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java index 0d7f9f23e0e110f077d9c1ad2e0bf97cc3ec23a6..6e029d2268e20e6b12ec1063857e5d2838c30d98 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java @@ -16,12 +16,14 @@ package com.android.server.biometrics.sensors.fingerprint.hidl; +import static com.android.systemui.shared.Flags.sidefpsControllerRefactor; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricFingerprintConstants; -import android.hardware.biometrics.BiometricOverlayConstants; +import android.hardware.biometrics.BiometricRequestConstants; import android.hardware.biometrics.fingerprint.PointerContext; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.fingerprint.FingerprintAuthenticateOptions; @@ -71,7 +73,12 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint> options.getOpPackageName(), 0 /* cookie */, options.getSensorId(), true /* shouldVibrate */, biometricLogger, biometricContext); setRequestId(requestId); - mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController */); + if (sidefpsControllerRefactor()) { + mSensorOverlays = new SensorOverlays(udfpsOverlayController); + } else { + mSensorOverlays = new SensorOverlays( + udfpsOverlayController, null /* sideFpsController */); + } mIsStrongBiometric = isStrongBiometric; } @@ -97,7 +104,7 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint> @Override protected void startHalOperation() { - mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD, + mSensorOverlays.show(getSensorId(), BiometricRequestConstants.REASON_AUTH_KEYGUARD, this); try { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java index 382e7e2121f49206e3d36f5f0bfe657627e41ef4..26332ff6893c3bd6139535dcfbf05eb6a1b8ecd5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors.fingerprint.hidl; +import static com.android.systemui.shared.Flags.sidefpsControllerRefactor; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -34,6 +36,7 @@ import android.util.Slog; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; @@ -58,22 +61,31 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint @NonNull private final SensorOverlays mSensorOverlays; private final @FingerprintManager.EnrollReason int mEnrollReason; + @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners; private boolean mIsPointerDown; - FingerprintEnrollClient(@NonNull Context context, - @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token, - long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId, + FingerprintEnrollClient( + @NonNull Context context, @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, + @NonNull IBinder token, long requestId, + @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int timeoutSec, int sensorId, @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext, @Nullable IUdfpsOverlayController udfpsOverlayController, + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Nullable ISidefpsController sidefpsController, + @NonNull AuthenticationStateListeners authenticationStateListeners, @FingerprintManager.EnrollReason int enrollReason) { super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, timeoutSec, sensorId, true /* shouldVibrate */, biometricLogger, biometricContext); setRequestId(requestId); - mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); + if (sidefpsControllerRefactor()) { + mSensorOverlays = new SensorOverlays(udfpsOverlayController); + } else { + mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); + } + mAuthenticationStateListeners = authenticationStateListeners; mEnrollReason = enrollReason; if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) { @@ -110,8 +122,12 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint @Override protected void startHalOperation() { - mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason), + mSensorOverlays.show(getSensorId(), getRequestReasonFromEnrollReason(mEnrollReason), this); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStarted( + getRequestReasonFromEnrollReason(mEnrollReason)); + } BiometricNotificationUtils.cancelBadCalibrationNotification(getContext()); try { @@ -122,6 +138,9 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } mCallback.onClientFinished(this, false /* success */); } } @@ -129,6 +148,9 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint @Override protected void stopHalOperation() { mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } try { getFreshDaemon().cancel(); @@ -149,6 +171,9 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint if (remaining == 0) { mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } } } @@ -170,6 +195,9 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint super.onError(errorCode, vendorCode); mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } } @Override diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java index a78f2dcf2ab272c8c42c7cea2791bd394520f8f2..3b5cae328b3ca5ae15d21abf69443094ea1c7093 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java @@ -23,6 +23,8 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE; import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED; import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_SUCCESS; +import static com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR; + import static junit.framework.Assert.assertEquals; import static org.junit.Assert.assertThrows; @@ -41,6 +43,7 @@ import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.hardware.biometrics.AuthenticationStateListener; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.Flags; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; @@ -108,6 +111,7 @@ public class AuthServiceTest { @Mock IFaceService mFaceService; @Mock + AppOpsManager mAppOpsManager; @Mock private VirtualDeviceManagerInternal mVdmInternal; @@ -404,6 +408,23 @@ public class AuthServiceTest { eq(TEST_OP_PACKAGE_NAME)); } + @Test + public void testRegisterAuthenticationStateListener_callsFingerprintService() + throws Exception { + mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR); + setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */); + + mAuthService = new AuthService(mContext, mInjector); + mAuthService.onStart(); + + final AuthenticationStateListener listener = mock(AuthenticationStateListener.class); + + mAuthService.mImpl.registerAuthenticationStateListener(listener); + + waitForIdle(); + verify(mFingerprintService).registerAuthenticationStateListener( + eq(listener)); + } @Test public void testRegisterKeyguardCallback_callsBiometricServiceRegisterKeyguardCallback() diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java index 5012335b533fa77b90710f3eb41fe927fa2df30c..94cb860ae7107805a1a7f751419006cacc2803bb 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors; +import static com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -23,10 +25,11 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.hardware.biometrics.BiometricOverlayConstants; +import android.hardware.biometrics.BiometricRequestConstants; import android.hardware.fingerprint.ISidefpsController; import android.hardware.fingerprint.IUdfpsOverlayController; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsDisabled; import androidx.test.filters.SmallTest; @@ -40,6 +43,7 @@ import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.List; +@RequiresFlagsDisabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR) @Presubmit @SmallTest public class SensorOverlaysTest { @@ -97,7 +101,7 @@ public class SensorOverlaysTest { private void testShow(IUdfpsOverlayController udfps, ISidefpsController sidefps) throws Exception { final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps); - final int reason = BiometricOverlayConstants.REASON_UNKNOWN; + final int reason = BiometricRequestConstants.REASON_UNKNOWN; sensorOverlays.show(SENSOR_ID, reason, mAcquisitionClient); if (udfps != null) { diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java index 79a528c59f497bd04b1dea4a09a5d615ee1f71a1..c24227fcd1f76e816464014e94e46d8b97288846 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java @@ -18,6 +18,8 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED; +import static com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -56,6 +58,7 @@ import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.TestableContext; import androidx.test.filters.SmallTest; @@ -68,6 +71,7 @@ import com.android.server.biometrics.log.CallbackWithProbe; import com.android.server.biometrics.log.OperationContextExt; import com.android.server.biometrics.log.Probe; import com.android.server.biometrics.sensors.AuthSessionCoordinator; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.LockoutTracker; @@ -91,6 +95,8 @@ import java.util.function.Consumer; @SmallTest public class FingerprintAuthenticationClientTest { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private static final int SENSOR_ID = 4; private static final int USER_ID = 8; private static final long OP_ID = 7; @@ -128,6 +134,8 @@ public class FingerprintAuthenticationClientTest { @Mock private ISidefpsController mSideFpsController; @Mock + private AuthenticationStateListeners mAuthenticationStateListeners; + @Mock private FingerprintSensorPropertiesInternal mSensorProps; @Mock private ClientMonitorCallback mCallback; @@ -384,6 +392,7 @@ public class FingerprintAuthenticationClientTest { private void showHideOverlay(Consumer<FingerprintAuthenticationClient> block) throws RemoteException { + mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR); final FingerprintAuthenticationClient client = createClient(); client.start(mCallback); @@ -397,6 +406,49 @@ public class FingerprintAuthenticationClientTest { verify(mSideFpsController).hide(anyInt()); } + @Test + public void showHideOverlay_cancel_sidefpsControllerRemovalRefactor() throws RemoteException { + showHideOverlay_sidefpsControllerRemovalRefactor(c -> c.cancel()); + } + + @Test + public void showHideOverlay_stop_sidefpsControllerRemovalRefactor() throws RemoteException { + showHideOverlay_sidefpsControllerRemovalRefactor(c -> c.stopHalOperation()); + } + + @Test + public void showHideOverlay_error_sidefpsControllerRemovalRefactor() throws RemoteException { + showHideOverlay_sidefpsControllerRemovalRefactor(c -> c.onError(0, 0)); + verify(mCallback).onClientFinished(any(), eq(false)); + } + + @Test + public void showHideOverlay_lockout_sidefpsControllerRemovalRefactor() throws RemoteException { + showHideOverlay_sidefpsControllerRemovalRefactor(c -> c.onLockoutTimed(5000)); + } + + @Test + public void showHideOverlay_lockoutPerm_sidefpsControllerRemovalRefactor() + throws RemoteException { + showHideOverlay_sidefpsControllerRemovalRefactor(c -> c.onLockoutPermanent()); + } + + private void showHideOverlay_sidefpsControllerRemovalRefactor( + Consumer<FingerprintAuthenticationClient> block) throws RemoteException { + mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR); + final FingerprintAuthenticationClient client = createClient(); + + client.start(mCallback); + + verify(mUdfpsOverlayController).showUdfpsOverlay(eq(REQUEST_ID), anyInt(), anyInt(), any()); + verify(mAuthenticationStateListeners).onAuthenticationStarted(anyInt()); + + block.accept(client); + + verify(mUdfpsOverlayController).hideUdfpsOverlay(anyInt()); + verify(mAuthenticationStateListeners).onAuthenticationStopped(); + } + @Test public void cancelsAuthWhenNotInForeground() throws Exception { final ActivityManager.RunningTaskInfo topTask = new ActivityManager.RunningTaskInfo(); @@ -502,7 +554,8 @@ public class FingerprintAuthenticationClientTest { mBiometricLogger, mBiometricContext, true /* isStrongBiometric */, null /* taskStackListener */, - mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication, + mUdfpsOverlayController, mSideFpsController, mAuthenticationStateListeners, + allowBackgroundAuthentication, mSensorProps, new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock, lockoutTracker) { diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java index c7eb1db3e74bb9c9f46687c104329a9d4e44d10e..e7d4a2e463f7dc325cd2bc5f3dfa4511289ebdce 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; +import static com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR; + import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; @@ -38,6 +40,7 @@ import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.TestableContext; import androidx.test.filters.SmallTest; @@ -48,6 +51,7 @@ import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.log.CallbackWithProbe; import com.android.server.biometrics.log.OperationContextExt; import com.android.server.biometrics.log.Probe; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; @@ -68,6 +72,8 @@ import java.util.function.Consumer; @SmallTest public class FingerprintEnrollClientTest { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private static final byte[] HAT = new byte[69]; private static final int USER_ID = 8; private static final long REQUEST_ID = 9; @@ -98,6 +104,8 @@ public class FingerprintEnrollClientTest { @Mock private ISidefpsController mSideFpsController; @Mock + private AuthenticationStateListeners mAuthenticationStateListeners; + @Mock private FingerprintSensorPropertiesInternal mSensorProps; @Mock private ClientMonitorCallback mCallback; @@ -271,6 +279,7 @@ public class FingerprintEnrollClientTest { private void showHideOverlay(Consumer<FingerprintEnrollClient> block) throws RemoteException { + mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR); final FingerprintEnrollClient client = createClient(); client.start(mCallback); @@ -284,6 +293,44 @@ public class FingerprintEnrollClientTest { verify(mSideFpsController).hide(anyInt()); } + @Test + public void showHideOverlay_cancel_sidefpsControllerRemovalRefactor() throws RemoteException { + showHideOverlay_sidefpsControllerRemovalRefactor(c -> c.cancel()); + } + + @Test + public void showHideOverlay_stop_sidefpsControllerRemovalRefactor() throws RemoteException { + showHideOverlay_sidefpsControllerRemovalRefactor(c -> c.stopHalOperation()); + } + + @Test + public void showHideOverlay_error_sidefpsControllerRemovalRefactor() throws RemoteException { + showHideOverlay_sidefpsControllerRemovalRefactor(c -> c.onError(0, 0)); + verify(mCallback).onClientFinished(any(), eq(false)); + } + + @Test + public void showHideOverlay_result_sidefpsControllerRemovalRefactor() throws RemoteException { + showHideOverlay_sidefpsControllerRemovalRefactor( + c -> c.onEnrollResult(new Fingerprint("", 1, 1), 0)); + } + + private void showHideOverlay_sidefpsControllerRemovalRefactor( + Consumer<FingerprintEnrollClient> block) throws RemoteException { + mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR); + final FingerprintEnrollClient client = createClient(); + + client.start(mCallback); + + verify(mUdfpsOverlayController).showUdfpsOverlay(eq(REQUEST_ID), anyInt(), anyInt(), any()); + verify(mAuthenticationStateListeners).onAuthenticationStarted(anyInt()); + + block.accept(client); + + verify(mUdfpsOverlayController).hideUdfpsOverlay(anyInt()); + verify(mAuthenticationStateListeners).onAuthenticationStopped(); + } + private FingerprintEnrollClient createClient() throws RemoteException { return createClient(500); } @@ -296,6 +343,7 @@ public class FingerprintEnrollClientTest { mClientMonitorCallbackConverter, 0 /* userId */, HAT, "owner", mBiometricUtils, 8 /* sensorId */, mBiometricLogger, mBiometricContext, mSensorProps, mUdfpsOverlayController, - mSideFpsController, 6 /* maxTemplatesPerUser */, FingerprintManager.ENROLL_ENROLL); + mSideFpsController, mAuthenticationStateListeners, 6 /* maxTemplatesPerUser */, + FingerprintManager.ENROLL_ENROLL); } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java index 8f6efffcbff8a30d79bb8b7e721b4808b9a8a3f1..4cfb83fa1c69fc051c962a4f7799962a3c9cceaa 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java @@ -43,6 +43,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.BiometricStateCallback; @@ -74,6 +75,8 @@ public class FingerprintProviderTest { @Mock private GestureAvailabilityDispatcher mGestureAvailabilityDispatcher; @Mock + private AuthenticationStateListeners mAuthenticationStateListeners; + @Mock private BiometricStateCallback mBiometricStateCallback; @Mock private BiometricContext mBiometricContext; @@ -110,8 +113,9 @@ public class FingerprintProviderTest { mLockoutResetDispatcher = new LockoutResetDispatcher(mContext); mFingerprintProvider = new FingerprintProvider(mContext, - mBiometricStateCallback, mSensorProps, TAG, mLockoutResetDispatcher, - mGestureAvailabilityDispatcher, mBiometricContext, mDaemon); + mBiometricStateCallback, mAuthenticationStateListeners, mSensorProps, TAG, + mLockoutResetDispatcher, mGestureAvailabilityDispatcher, mBiometricContext, + mDaemon); } @Test diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java index b32b89aaefb03c310519d5bac87df9737a99f8fa..0d3f1921c9475ea63b7d31797a1930a5360a9e2d 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java @@ -40,6 +40,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.R; import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.BiometricStateCallback; import com.android.server.biometrics.sensors.LockoutResetDispatcher; @@ -70,6 +71,8 @@ public class Fingerprint21Test { @Mock private BiometricScheduler mScheduler; @Mock + private AuthenticationStateListeners mAuthenticationStateListeners; + @Mock private BiometricStateCallback mBiometricStateCallback; @Mock private BiometricContext mBiometricContext; @@ -102,9 +105,10 @@ public class Fingerprint21Test { componentInfo, FingerprintSensorProperties.TYPE_UNKNOWN, resetLockoutRequiresHardwareAuthToken); - mFingerprint21 = new TestableFingerprint21(mContext, mBiometricStateCallback, sensorProps, - mScheduler, new Handler(Looper.getMainLooper()), mLockoutResetDispatcher, - mHalResultController, mBiometricContext); + mFingerprint21 = new TestableFingerprint21(mContext, mBiometricStateCallback, + mAuthenticationStateListeners, sensorProps, mScheduler, + new Handler(Looper.getMainLooper()), mLockoutResetDispatcher, mHalResultController, + mBiometricContext); } @Test @@ -126,13 +130,14 @@ public class Fingerprint21Test { TestableFingerprint21(@NonNull Context context, @NonNull BiometricStateCallback biometricStateCallback, + @NonNull AuthenticationStateListeners authenticationStateListeners, @NonNull FingerprintSensorPropertiesInternal sensorProps, @NonNull BiometricScheduler scheduler, @NonNull Handler handler, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull HalResultController controller, @NonNull BiometricContext biometricContext) { - super(context, biometricStateCallback, sensorProps, scheduler, handler, - lockoutResetDispatcher, controller, biometricContext); + super(context, biometricStateCallback, authenticationStateListeners, sensorProps, + scheduler, handler, lockoutResetDispatcher, controller, biometricContext); } @Override