diff options
4 files changed, 213 insertions, 35 deletions
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 259f3498d4c3..503463171ae7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -44,12 +44,18 @@ import com.android.systemui.biometrics.ui.viewmodel.DefaultUdfpsTouchOverlayView import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.classifier.FalsingCollector import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.power.data.repository.FakePowerRepository +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.power.shared.model.WakeSleepReason +import com.android.systemui.power.shared.model.WakefulnessState import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.LockscreenShadeTransitionController +import com.android.systemui.statusbar.phone.ScreenOffAnimationController import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.phone.SystemUIDialogManager import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController @@ -58,6 +64,10 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.user.domain.interactor.SelectedUserInteractor 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 @@ -68,6 +78,7 @@ import org.mockito.ArgumentMatchers.eq import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.mock +import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit @@ -120,6 +131,9 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { @Mock private lateinit var shadeInteractor: ShadeInteractor @Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams> @Mock private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor + private lateinit var powerRepository: FakePowerRepository + private lateinit var powerInteractor: PowerInteractor + private lateinit var testScope: TestScope private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true } private var overlayParams: UdfpsOverlayParams = UdfpsOverlayParams() @@ -127,6 +141,15 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { @Before fun setup() { + testScope = TestScope(StandardTestDispatcher()) + powerRepository = FakePowerRepository() + powerInteractor = + PowerInteractor( + powerRepository, + mock(FalsingCollector::class.java), + mock(ScreenOffAnimationController::class.java), + statusBarStateController, + ) whenever(inflater.inflate(R.layout.udfps_view, null, false)).thenReturn(udfpsView) whenever(inflater.inflate(R.layout.udfps_bp_view, null)) .thenReturn(mock(UdfpsBpView::class.java)) @@ -178,6 +201,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { { defaultUdfpsTouchOverlayViewModel }, shadeInteractor, udfpsOverlayInteractor, + powerInteractor, + testScope, ) block() } @@ -277,6 +302,66 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { } } + @Test + fun showUdfpsOverlay_awake() = + testScope.runTest { + withReason(REASON_AUTH_KEYGUARD) { + mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE) + powerRepository.updateWakefulness( + rawState = WakefulnessState.AWAKE, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.OTHER, + ) + controllerOverlay.show(udfpsController, overlayParams) + runCurrent() + verify(windowManager).addView(any(), any()) + } + } + + @Test + fun showUdfpsOverlay_whileGoingToSleep() = + testScope.runTest { + withReason(REASON_AUTH_KEYGUARD) { + mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE) + powerRepository.updateWakefulness( + rawState = WakefulnessState.STARTING_TO_SLEEP, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.OTHER, + ) + controllerOverlay.show(udfpsController, overlayParams) + runCurrent() + verify(windowManager, never()).addView(any(), any()) + + // we hide to end the job that listens for the finishedGoingToSleep signal + controllerOverlay.hide() + } + } + + @Test + fun showUdfpsOverlay_afterFinishedGoingToSleep() = + testScope.runTest { + withReason(REASON_AUTH_KEYGUARD) { + mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE) + powerRepository.updateWakefulness( + rawState = WakefulnessState.STARTING_TO_SLEEP, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.OTHER, + ) + controllerOverlay.show(udfpsController, overlayParams) + runCurrent() + verify(windowManager, never()).addView(any(), any()) + + powerRepository.updateWakefulness( + rawState = WakefulnessState.ASLEEP, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.OTHER, + ) + runCurrent() + verify(windowManager) + .addView(eq(controllerOverlay.getTouchOverlay()), layoutParamsCaptor.capture()) + } + } + private fun showUdfpsOverlay() { val didShow = controllerOverlay.show(udfpsController, overlayParams) 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 529403a710b5..561cdbbf66ce 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -86,6 +86,7 @@ import com.android.systemui.biometrics.ui.viewmodel.DefaultUdfpsTouchOverlayView import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; +import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; @@ -94,10 +95,15 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.power.data.repository.FakePowerRepository; +import com.android.systemui.power.domain.interactor.PowerInteractor; +import com.android.systemui.power.shared.model.WakeSleepReason; +import com.android.systemui.power.shared.model.WakefulnessState; import com.android.systemui.res.R; import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.VibratorHelper; +import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.SystemUIDialogManager; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; @@ -125,6 +131,8 @@ import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.List; +import kotlinx.coroutines.CoroutineScope; + @SmallTest @RunWith(AndroidJUnit4.class) @RunWithLooper(setAsMainLooper = true) @@ -238,6 +246,8 @@ public class UdfpsControllerTest extends SysuiTestCase { private ScreenLifecycle.Observer mScreenObserver; private FingerprintSensorPropertiesInternal mOpticalProps; private FingerprintSensorPropertiesInternal mUltrasonicProps; + private PowerInteractor mPowerInteractor; + private FakePowerRepository mPowerRepository; @Mock private InputManager mInputManager; @Mock @@ -253,6 +263,19 @@ public class UdfpsControllerTest extends SysuiTestCase { @Before public void setUp() { + mPowerRepository = new FakePowerRepository(); + mPowerInteractor = new PowerInteractor( + mPowerRepository, + mock(FalsingCollector.class), + mock(ScreenOffAnimationController.class), + mStatusBarStateController + ); + mPowerRepository.updateWakefulness( + WakefulnessState.AWAKE, + WakeSleepReason.POWER_BUTTON, + WakeSleepReason.OTHER, + /* powerButtonLaunchGestureTriggered */ false + ); mContext.getOrCreateTestableResources() .addOverride(com.android.internal.R.bool.config_ignoreUdfpsVote, false); @@ -346,7 +369,9 @@ public class UdfpsControllerTest extends SysuiTestCase { mKeyguardTransitionInteractor, mDeviceEntryUdfpsTouchOverlayViewModel, mDefaultUdfpsTouchOverlayViewModel, - mUdfpsOverlayInteractor + mUdfpsOverlayInteractor, + mPowerInteractor, + mock(CoroutineScope.class) ); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); mOverlayController = mOverlayCaptor.getValue(); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 716209d18a01..2c3ebe901850 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -80,6 +80,7 @@ import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlay 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.dagger.qualifiers.Main; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor; import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor; @@ -90,6 +91,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.LockscreenShadeTransitionController; @@ -116,6 +118,7 @@ import java.util.concurrent.Executor; import javax.inject.Inject; +import kotlinx.coroutines.CoroutineScope; import kotlinx.coroutines.ExperimentalCoroutinesApi; /** @@ -173,6 +176,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { mDefaultUdfpsTouchOverlayViewModel; @NonNull private final AlternateBouncerInteractor mAlternateBouncerInteractor; @NonNull private final UdfpsOverlayInteractor mUdfpsOverlayInteractor; + @NonNull private final PowerInteractor mPowerInteractor; + @NonNull private final CoroutineScope mScope; @NonNull private final InputManager mInputManager; @NonNull private final UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate; @NonNull private final SelectedUserInteractor mSelectedUserInteractor; @@ -296,7 +301,9 @@ public class UdfpsController implements DozeReceiver, Dumpable { mDeviceEntryUdfpsTouchOverlayViewModel, mDefaultUdfpsTouchOverlayViewModel, mShadeInteractor, - mUdfpsOverlayInteractor + mUdfpsOverlayInteractor, + mPowerInteractor, + mScope ))); } @@ -678,7 +685,9 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull KeyguardTransitionInteractor keyguardTransitionInteractor, Lazy<DeviceEntryUdfpsTouchOverlayViewModel> deviceEntryUdfpsTouchOverlayViewModel, Lazy<DefaultUdfpsTouchOverlayViewModel> defaultUdfpsTouchOverlayViewModel, - @NonNull UdfpsOverlayInteractor udfpsOverlayInteractor) { + @NonNull UdfpsOverlayInteractor udfpsOverlayInteractor, + @NonNull PowerInteractor powerInteractor, + @Application CoroutineScope scope) { mContext = context; mExecution = execution; mVibrator = vibrator; @@ -720,6 +729,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { mShadeInteractor = shadeInteractor; mAlternateBouncerInteractor = alternateBouncerInteractor; mUdfpsOverlayInteractor = udfpsOverlayInteractor; + mPowerInteractor = powerInteractor; + mScope = scope; mInputManager = inputManager; mUdfpsKeyguardAccessibilityDelegate = udfpsKeyguardAccessibilityDelegate; mSelectedUserInteractor = selectedUserInteractor; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index a209eae5673b..921e39532f58 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -44,6 +44,7 @@ import android.view.accessibility.AccessibilityManager.TouchExplorationStateChan import androidx.annotation.LayoutRes import androidx.annotation.VisibleForTesting import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.Flags.udfpsViewPerformance import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams @@ -53,10 +54,13 @@ import com.android.systemui.biometrics.ui.viewmodel.DefaultUdfpsTouchOverlayView import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.power.shared.model.WakefulnessState import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -67,7 +71,13 @@ import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.user.domain.interactor.SelectedUserInteractor import dagger.Lazy +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch private const val TAG = "UdfpsControllerOverlay" @@ -82,36 +92,45 @@ const val SETTING_REMOVE_ENROLLMENT_UI = "udfps_overlay_remove_enrollment_ui" @ExperimentalCoroutinesApi @UiThread class UdfpsControllerOverlay @JvmOverloads constructor( - private val context: Context, - private val inflater: LayoutInflater, - private val windowManager: WindowManager, - private val accessibilityManager: AccessibilityManager, - private val statusBarStateController: StatusBarStateController, - private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager, - private val keyguardUpdateMonitor: KeyguardUpdateMonitor, - private val dialogManager: SystemUIDialogManager, - private val dumpManager: DumpManager, - private val transitionController: LockscreenShadeTransitionController, - private val configurationController: ConfigurationController, - private val keyguardStateController: KeyguardStateController, - private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, - private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider, - val requestId: Long, - @RequestReason val requestReason: Int, - private val controllerCallback: IUdfpsOverlayControllerCallback, - private val onTouch: (View, MotionEvent, Boolean) -> Boolean, - private val activityTransitionAnimator: ActivityTransitionAnimator, - private val primaryBouncerInteractor: PrimaryBouncerInteractor, - private val alternateBouncerInteractor: AlternateBouncerInteractor, - private val isDebuggable: Boolean = Build.IS_DEBUGGABLE, - private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate, - private val transitionInteractor: KeyguardTransitionInteractor, - private val selectedUserInteractor: SelectedUserInteractor, - private val deviceEntryUdfpsTouchOverlayViewModel: Lazy<DeviceEntryUdfpsTouchOverlayViewModel>, - private val defaultUdfpsTouchOverlayViewModel: Lazy<DefaultUdfpsTouchOverlayViewModel>, - private val shadeInteractor: ShadeInteractor, - private val udfpsOverlayInteractor: UdfpsOverlayInteractor, + private val context: Context, + private val inflater: LayoutInflater, + private val windowManager: WindowManager, + private val accessibilityManager: AccessibilityManager, + private val statusBarStateController: StatusBarStateController, + private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val dialogManager: SystemUIDialogManager, + private val dumpManager: DumpManager, + private val transitionController: LockscreenShadeTransitionController, + private val configurationController: ConfigurationController, + private val keyguardStateController: KeyguardStateController, + private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, + private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider, + val requestId: Long, + @RequestReason val requestReason: Int, + private val controllerCallback: IUdfpsOverlayControllerCallback, + private val onTouch: (View, MotionEvent, Boolean) -> Boolean, + private val activityTransitionAnimator: ActivityTransitionAnimator, + private val primaryBouncerInteractor: PrimaryBouncerInteractor, + private val alternateBouncerInteractor: AlternateBouncerInteractor, + private val isDebuggable: Boolean = Build.IS_DEBUGGABLE, + private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate, + private val transitionInteractor: KeyguardTransitionInteractor, + private val selectedUserInteractor: SelectedUserInteractor, + private val deviceEntryUdfpsTouchOverlayViewModel: + Lazy<DeviceEntryUdfpsTouchOverlayViewModel>, + private val defaultUdfpsTouchOverlayViewModel: Lazy<DefaultUdfpsTouchOverlayViewModel>, + private val shadeInteractor: ShadeInteractor, + private val udfpsOverlayInteractor: UdfpsOverlayInteractor, + private val powerInteractor: PowerInteractor, + @Application private val scope: CoroutineScope, ) { + private val isFinishedGoingToSleep: Flow<Unit> = + powerInteractor.detailedWakefulness + .filter { it.internalWakefulnessState == WakefulnessState.ASLEEP } + .map { } // map to Unit + private var listenForAsleepJob: Job? = null + private var addViewRunnable: Runnable? = null private var overlayViewLegacy: UdfpsView? = null private set private var overlayTouchView: UdfpsTouchOverlay? = null @@ -192,7 +211,8 @@ class UdfpsControllerOverlay @JvmOverloads constructor( if (requestReason.isImportantForAccessibility()) { importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO } - windowManager.addView(this, coreLayoutParams.updateDimensions(null)) + + addViewNowOrLater(this, null) when (requestReason) { REASON_AUTH_KEYGUARD -> UdfpsTouchOverlayBinder.bind( @@ -225,7 +245,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor( importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO } - windowManager.addView(this, coreLayoutParams.updateDimensions(animation)) + addViewNowOrLater(this, animation) sensorRect = sensorBounds } } @@ -257,6 +277,41 @@ class UdfpsControllerOverlay @JvmOverloads constructor( return false } + private fun addViewNowOrLater(view: View, animation: UdfpsAnimationViewController<*>?) { + if (udfpsViewPerformance()) { + addViewRunnable = kotlinx.coroutines.Runnable { + windowManager.addView( + view, + coreLayoutParams.updateDimensions(animation) + ) + } + if (powerInteractor.detailedWakefulness.value.internalWakefulnessState + != WakefulnessState.STARTING_TO_SLEEP) { + addViewIfPending() + } else { + listenForAsleepJob?.cancel() + listenForAsleepJob = scope.launch { + isFinishedGoingToSleep.collect { + addViewIfPending() + } + } + } + } else { + windowManager.addView( + view, + coreLayoutParams.updateDimensions(animation) + ) + } + } + + private fun addViewIfPending() { + addViewRunnable?.let { + listenForAsleepJob?.cancel() + it.run() + } + addViewRunnable = null + } + fun inflateUdfpsAnimation( view: UdfpsView, controller: UdfpsController @@ -368,6 +423,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor( overlayViewLegacy = null overlayTouchView = null overlayTouchListener = null + listenForAsleepJob?.cancel() return wasShowing } @@ -412,7 +468,8 @@ class UdfpsControllerOverlay @JvmOverloads constructor( if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) { if (!shouldRotate(animation)) { Log.v( - TAG, "Skip rotating UDFPS bounds " + Surface.rotationToString(rot) + + TAG, + "Skip rotating UDFPS bounds " + Surface.rotationToString(rot) + " animation=$animation" + " isGoingToSleep=${keyguardUpdateMonitor.isGoingToSleep}" + " isOccluded=${keyguardStateController.isOccluded}" |