diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 1ceb6b3ca1725..d1bc968a5be66 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -143,6 +143,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba @Nullable private IUdfpsHbmListener mUdfpsHbmListener; @Nullable private SidefpsController mSidefpsController; @Nullable private IBiometricContextListener mBiometricContextListener; + @Nullable private UdfpsLogger mUdfpsLogger; @VisibleForTesting IBiometricSysuiReceiver mReceiver; @VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener; @Nullable private final List mFaceProps; @@ -289,6 +290,8 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba } }); mUdfpsController.setAuthControllerUpdateUdfpsLocation(this::updateUdfpsLocation); + mUdfpsController.setUdfpsDisplayMode(new UdfpsDisplayMode(mContext, mExecution, + this, mUdfpsLogger)); mUdfpsBounds = mUdfpsProps.get(0).getLocation().getRect(); } @@ -688,6 +691,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba @NonNull WakefulnessLifecycle wakefulnessLifecycle, @NonNull UserManager userManager, @NonNull LockPatternUtils lockPatternUtils, + @NonNull UdfpsLogger udfpsLogger, @NonNull StatusBarStateController statusBarStateController, @NonNull InteractionJankMonitor jankMonitor, @Main Handler handler, @@ -705,6 +709,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba mFaceManager = faceManager; mUdfpsControllerFactory = udfpsControllerFactory; mSidefpsControllerFactory = sidefpsControllerFactory; + mUdfpsLogger = udfpsLogger; mDisplayManager = displayManager; mWindowManager = windowManager; mInteractionJankMonitor = jankMonitor; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index f6dedd95198b7..2578df33c167b 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 UdfpsDisplayModeProvider 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(UdfpsDisplayModeProvider 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..b80b8a0ff2192 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt @@ -0,0 +1,100 @@ +/* + * 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 android.content.Context +import android.os.RemoteException +import android.os.Trace +import com.android.systemui.util.concurrency.Execution + +private const val TAG = "UdfpsDisplayMode" + +/** + * UdfpsDisplayMode configures the display for optimal UDFPS operation. For example, sets the + * display refresh rate that's optimal for UDFPS. + */ +class UdfpsDisplayMode +constructor( + private val context: Context, + private val execution: Execution, + private val authController: AuthController, + private val logger: UdfpsLogger +) : UdfpsDisplayModeProvider { + + // The request is reset to null after it's processed. + private var currentRequest: Request? = null + + override fun enable(onEnabled: Runnable?) { + execution.isMainThread() + logger.v(TAG, "enable") + + if (currentRequest != null) { + logger.e(TAG, "enable | already requested") + return + } + if (authController.udfpsHbmListener == null) { + logger.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) + logger.v(TAG, "enable | requested optimal refresh rate for UDFPS") + } catch (e: RemoteException) { + logger.e(TAG, "enable", e) + } + + onEnabled?.run() ?: logger.w(TAG, "enable | onEnabled is null") + Trace.endSection() + } + + override fun disable(onDisabled: Runnable?) { + execution.isMainThread() + logger.v(TAG, "disable") + + val request = currentRequest + if (request == null) { + logger.w(TAG, "disable | already disabled") + return + } + + Trace.beginSection("UdfpsDisplayMode.disable") + + try { + // Allow DisplayManager to unset the UDFPS refresh rate. + authController.udfpsHbmListener!!.onHbmDisabled(request.displayId) + logger.v(TAG, "disable | removed the UDFPS refresh rate request") + } catch (e: RemoteException) { + logger.e(TAG, "disable", e) + } + + currentRequest = null + onDisabled?.run() ?: logger.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/src/com/android/systemui/biometrics/UdfpsLogger.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt new file mode 100644 index 0000000000000..39199d194cc94 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt @@ -0,0 +1,45 @@ +/* + * 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 com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.LogLevel.ERROR +import com.android.systemui.log.LogLevel.VERBOSE +import com.android.systemui.log.LogLevel.WARNING +import com.android.systemui.log.dagger.UdfpsLog +import com.google.errorprone.annotations.CompileTimeConstant +import javax.inject.Inject + +private const val TAG = "UdfpsLogger" + +/** Helper class for logging for Udfps */ +class UdfpsLogger @Inject constructor(@UdfpsLog private val logBuffer: LogBuffer) { + fun e(tag: String, @CompileTimeConstant msg: String) = log(tag, msg, ERROR) + + fun e(tag: String, @CompileTimeConstant msg: String, throwable: Throwable?) { + logBuffer.log(tag, ERROR, {}, { msg }, exception = throwable) + } + + fun v(tag: String, @CompileTimeConstant msg: String) = log(tag, msg, VERBOSE) + + fun w(tag: String, @CompileTimeConstant msg: String) = log(tag, msg, WARNING) + + fun log(tag: String, @CompileTimeConstant msg: String, level: LogLevel) { + logBuffer.log(tag, level, msg) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java index c0acc71880b51..8e45067c8e13b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -160,6 +160,8 @@ public class AuthControllerTest extends SysuiTestCase { @Mock private StatusBarStateController mStatusBarStateController; @Mock + private UdfpsLogger mUdfpsLogger; + @Mock private InteractionJankMonitor mInteractionJankMonitor; @Captor private ArgumentCaptor mFpAuthenticatorsRegisteredCaptor; @@ -978,7 +980,7 @@ public class AuthControllerTest extends SysuiTestCase { super(context, execution, commandQueue, activityTaskManager, windowManager, fingerprintManager, faceManager, udfpsControllerFactory, sidefpsControllerFactory, mDisplayManager, mWakefulnessLifecycle, - mUserManager, mLockPatternUtils, statusBarStateController, + mUserManager, mLockPatternUtils, mUdfpsLogger, statusBarStateController, mInteractionJankMonitor, mHandler, mBackgroundExecutor, vibratorHelper); } 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 3be1a56b45443..11e58801bb7e0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -126,8 +126,6 @@ public class UdfpsControllerTest extends SysuiTestCase { @Mock private WindowManager mWindowManager; @Mock - private UdfpsDisplayModeProvider mDisplayModeProvider; - @Mock private StatusBarStateController mStatusBarStateController; @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @@ -168,6 +166,8 @@ public class UdfpsControllerTest extends SysuiTestCase { @Mock private LatencyTracker mLatencyTracker; private FakeExecutor mFgExecutor; + @Mock + private UdfpsDisplayMode mUdfpsDisplayMode; // Stuff for configuring mocks @Mock @@ -257,7 +257,6 @@ public class UdfpsControllerTest extends SysuiTestCase { mVibrator, mUdfpsHapticsSimulator, mUdfpsShell, - Optional.of(mDisplayModeProvider), mKeyguardStateController, mDisplayManager, mHandler, @@ -274,6 +273,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..7864f21bd1d6d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2020 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 UdfpsLogger mUdfpsLogger; + @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, + mUdfpsLogger); + } + + @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); + } +}