diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 3b41a2d22380e..c015a21c7db48 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -294,6 +294,8 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, } }); mUdfpsController.setAuthControllerUpdateUdfpsLocation(this::updateUdfpsLocation); + mUdfpsController.setUdfpsDisplayMode(new UdfpsDisplayMode(mContext, mExecution, + this)); mUdfpsBounds = mUdfpsProps.get(0).getLocation().getRect(); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index d17f7878f5f30..b49d4523a765e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -123,7 +123,6 @@ public class UdfpsController implements DozeReceiver { @NonNull private final PowerManager mPowerManager; @NonNull private final AccessibilityManager mAccessibilityManager; @NonNull private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; - @Nullable private final UdfpsDisplayModeProvider mUdfpsDisplayMode; @NonNull private final ConfigurationController mConfigurationController; @NonNull private final SystemClock mSystemClock; @NonNull private final UnlockedScreenOffAnimationController @@ -139,6 +138,7 @@ public class UdfpsController implements DozeReceiver { // TODO(b/229290039): UDFPS controller should manage its dimensions on its own. Remove this. @Nullable private Runnable mAuthControllerUpdateUdfpsLocation; @Nullable private final AlternateUdfpsTouchProvider mAlternateTouchProvider; + @Nullable private UdfpsDisplayMode mUdfpsDisplayMode; // Tracks the velocity of a touch to help filter out the touches that move too fast. @Nullable private VelocityTracker mVelocityTracker; @@ -319,6 +319,10 @@ public class UdfpsController implements DozeReceiver { mAuthControllerUpdateUdfpsLocation = r; } + public void setUdfpsDisplayMode(UdfpsDisplayMode udfpsDisplayMode) { + mUdfpsDisplayMode = udfpsDisplayMode; + } + /** * Calculate the pointer speed given a velocity tracker and the pointer id. * This assumes that the velocity tracker has already been passed all relevant motion events. @@ -594,7 +598,6 @@ public class UdfpsController implements DozeReceiver { @NonNull VibratorHelper vibrator, @NonNull UdfpsHapticsSimulator udfpsHapticsSimulator, @NonNull UdfpsShell udfpsShell, - @NonNull Optional udfpsDisplayMode, @NonNull KeyguardStateController keyguardStateController, @NonNull DisplayManager displayManager, @Main Handler mainHandler, @@ -626,7 +629,6 @@ public class UdfpsController implements DozeReceiver { mPowerManager = powerManager; mAccessibilityManager = accessibilityManager; mLockscreenShadeTransitionController = lockscreenShadeTransitionController; - mUdfpsDisplayMode = udfpsDisplayMode.orElse(null); screenLifecycle.addObserver(mScreenObserver); mScreenOn = screenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_ON; mConfigurationController = configurationController; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt new file mode 100644 index 0000000000000..e9de7ccc7b4f5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt @@ -0,0 +1,88 @@ +package com.android.systemui.biometrics + +import android.content.Context +import android.os.RemoteException +import android.os.Trace +import android.util.Log +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.util.concurrency.Execution +import javax.inject.Inject + +private const val TAG = "UdfpsDisplayMode" + +/** + * UdfpsDisplayMode that encapsulates pixel-specific code, such as enabling the high-brightness mode + * (HBM) in a display-specific way and freezing the display's refresh rate. + */ +@SysUISingleton +class UdfpsDisplayMode +@Inject +constructor( + private val context: Context, + private val execution: Execution, + private val authController: AuthController +) : UdfpsDisplayModeProvider { + + // The request is reset to null after it's processed. + private var currentRequest: Request? = null + + override fun enable(onEnabled: Runnable?) { + execution.isMainThread() + Log.v(TAG, "enable") + + if (currentRequest != null) { + Log.e(TAG, "enable | already requested") + return + } + if (authController.udfpsHbmListener == null) { + Log.e(TAG, "enable | mDisplayManagerCallback is null") + return + } + + Trace.beginSection("UdfpsDisplayMode.enable") + + // Track this request in one object. + val request = Request(context.displayId) + currentRequest = request + + try { + // This method is a misnomer. It has nothing to do with HBM, its purpose is to set + // the appropriate display refresh rate. + authController.udfpsHbmListener!!.onHbmEnabled(request.displayId) + Log.v(TAG, "enable | requested optimal refresh rate for UDFPS") + } catch (e: RemoteException) { + Log.e(TAG, "enable", e) + } + + onEnabled?.run() ?: Log.w(TAG, "enable | onEnabled is null") + Trace.endSection() + } + + override fun disable(onDisabled: Runnable?) { + execution.isMainThread() + Log.v(TAG, "disable") + + val request = currentRequest + if (request == null) { + Log.w(TAG, "disable | already disabled") + return + } + + Trace.beginSection("UdfpsDisplayMode.disable") + + try { + // Allow DisplayManager to unset the UDFPS refresh rate. + authController.udfpsHbmListener!!.onHbmDisabled(request.displayId) + Log.v(TAG, "disable | removed the UDFPS refresh rate request") + } catch (e: RemoteException) { + Log.e(TAG, "disable", e) + } + + currentRequest = null + onDisabled?.run() ?: Log.w(TAG, "disable | onDisabled is null") + Trace.endSection() + } +} + +/** Tracks a request to enable the UDFPS mode. */ +private data class Request(val displayId: Int) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 80e1f30020aa6..1dd053912ae4a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -169,6 +169,8 @@ public class UdfpsControllerTest extends SysuiTestCase { @Mock private LatencyTracker mLatencyTracker; private FakeExecutor mFgExecutor; + @Mock + private UdfpsDisplayMode mUdfpsDisplayMode; // Stuff for configuring mocks @Mock @@ -258,7 +260,6 @@ public class UdfpsControllerTest extends SysuiTestCase { mVibrator, mUdfpsHapticsSimulator, mUdfpsShell, - Optional.of(mDisplayModeProvider), mKeyguardStateController, mDisplayManager, mHandler, @@ -275,6 +276,7 @@ public class UdfpsControllerTest extends SysuiTestCase { verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture()); mScreenObserver = mScreenObserverCaptor.getValue(); mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID, new UdfpsOverlayParams()); + mUdfpsController.setUdfpsDisplayMode(mUdfpsDisplayMode); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java new file mode 100644 index 0000000000000..7e35b261b352f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2022 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; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.fingerprint.IUdfpsHbmListener; +import android.os.RemoteException; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper.RunWithLooper; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.concurrency.FakeExecution; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@RunWithLooper(setAsMainLooper = true) +public class UdfpsDisplayModeTest extends SysuiTestCase { + private static final int DISPLAY_ID = 0; + + @Mock + private AuthController mAuthController; + @Mock + private IUdfpsHbmListener mDisplayCallback; + @Mock + private Runnable mOnEnabled; + @Mock + private Runnable mOnDisabled; + + private final FakeExecution mExecution = new FakeExecution(); + private UdfpsDisplayMode mHbmController; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + // Force mContext to always return DISPLAY_ID + Context contextSpy = spy(mContext); + when(contextSpy.getDisplayId()).thenReturn(DISPLAY_ID); + + // Set up mocks. + when(mAuthController.getUdfpsHbmListener()).thenReturn(mDisplayCallback); + + // Create a real controller with mock dependencies. + mHbmController = new UdfpsDisplayMode(contextSpy, mExecution, mAuthController); + } + + @Test + public void roundTrip() throws RemoteException { + // Enable the UDFPS mode. + mHbmController.enable(mOnEnabled); + + // Should set the appropriate refresh rate for UDFPS and notify the caller. + verify(mDisplayCallback).onHbmEnabled(eq(DISPLAY_ID)); + verify(mOnEnabled).run(); + + // Disable the UDFPS mode. + mHbmController.disable(mOnDisabled); + + // Should unset the refresh rate and notify the caller. + verify(mOnDisabled).run(); + verify(mDisplayCallback).onHbmDisabled(eq(DISPLAY_ID)); + } + + @Test + public void mustNotEnableMoreThanOnce() throws RemoteException { + // First request to enable the UDFPS mode. + mHbmController.enable(mOnEnabled); + + // Should set the appropriate refresh rate for UDFPS and notify the caller. + verify(mDisplayCallback).onHbmEnabled(eq(DISPLAY_ID)); + verify(mOnEnabled).run(); + + // Second request to enable the UDFPS mode, while it's still enabled. + mHbmController.enable(mOnEnabled); + + // Should ignore the second request. + verifyNoMoreInteractions(mDisplayCallback); + verifyNoMoreInteractions(mOnEnabled); + } + + @Test + public void mustNotDisableMoreThanOnce() throws RemoteException { + // Disable the UDFPS mode. + mHbmController.enable(mOnEnabled); + + // Should set the appropriate refresh rate for UDFPS and notify the caller. + verify(mDisplayCallback).onHbmEnabled(eq(DISPLAY_ID)); + verify(mOnEnabled).run(); + + // First request to disable the UDFPS mode. + mHbmController.disable(mOnDisabled); + + // Should unset the refresh rate and notify the caller. + verify(mOnDisabled).run(); + verify(mDisplayCallback).onHbmDisabled(eq(DISPLAY_ID)); + + // Second request to disable the UDFPS mode, when it's already disabled. + mHbmController.disable(mOnDisabled); + + // Should ignore the second request. + verifyNoMoreInteractions(mOnDisabled); + verifyNoMoreInteractions(mDisplayCallback); + } +}