summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Beverly Tai <beverlyt@google.com> 2023-06-30 03:00:44 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2023-06-30 03:00:44 +0000
commit682c51f544104a49c6f8ca373f1c74aa1184e56d (patch)
tree83b91b02c13dedbb2af471b10b4aebf4ac477b84
parentaaef6a2d2c25e75302e52d371cb73b078b4ebbe3 (diff)
parentcb1b8cb7fa19d8b938dfb45f1d64c046b68b2d67 (diff)
Merge "Modernize UDFPS views for maintainability" into udc-qpr-dev
-rw-r--r--packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml25
-rw-r--r--packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml3
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt71
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt77
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt86
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt117
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt162
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt168
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt149
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt131
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt505
27 files changed, 1985 insertions, 37 deletions
diff --git a/packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml b/packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml
new file mode 100644
index 000000000000..360ef2672e75
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<com.android.systemui.biometrics.UdfpsKeyguardView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/udfps_animation_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <!-- Add fingerprint views here. See udfps_keyguard_view_internal.xml. -->
+
+</com.android.systemui.biometrics.UdfpsKeyguardView>
diff --git a/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml b/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml
index 00af7f4e10e3..530d752732c1 100644
--- a/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml
+++ b/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml
@@ -16,8 +16,7 @@
-->
<com.android.systemui.biometrics.UdfpsKeyguardViewLegacy
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/udfps_animation_view"
+ android:id="@+id/udfps_animation_view_legacy"
android:layout_width="match_parent"
android:layout_height="match_parent">
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index dc9ba8719717..bfe470ca7feb 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -85,6 +85,8 @@ import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter;
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -153,6 +155,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
@NonNull private final SystemUIDialogManager mDialogManager;
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@NonNull private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
+ @NonNull private final Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels;
@NonNull private final VibratorHelper mVibrator;
@NonNull private final FeatureFlags mFeatureFlags;
@NonNull private final FalsingManager mFalsingManager;
@@ -272,7 +275,8 @@ public class UdfpsController implements DozeReceiver, Dumpable {
(view, event, fromUdfpsView) -> onTouch(requestId, event,
fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags,
mPrimaryBouncerInteractor, mAlternateBouncerInteractor, mUdfpsUtils,
- mUdfpsKeyguardAccessibilityDelegate)));
+ mUdfpsKeyguardAccessibilityDelegate,
+ mUdfpsKeyguardViewModels)));
}
@Override
@@ -784,7 +788,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
private boolean shouldTryToDismissKeyguard() {
return mOverlay != null
&& mOverlay.getAnimationViewController()
- instanceof UdfpsKeyguardViewControllerLegacy
+ instanceof UdfpsKeyguardViewControllerAdapter
&& mKeyguardStateController.canDismissLockScreen()
&& !mAttemptedToDismissKeyguard;
}
@@ -829,7 +833,8 @@ public class UdfpsController implements DozeReceiver, Dumpable {
@NonNull InputManager inputManager,
@NonNull UdfpsUtils udfpsUtils,
@NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
- @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate) {
+ @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate,
+ @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider) {
mContext = context;
mExecution = execution;
mVibrator = vibrator;
@@ -895,6 +900,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
return Unit.INSTANCE;
});
mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
+ mUdfpsKeyguardViewModels = udfpsKeyguardViewModelsProvider;
final UdfpsOverlayController mUdfpsOverlayController = new UdfpsOverlayController();
mFingerprintManager.setUdfpsOverlayController(mUdfpsOverlayController);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index e5421471931f..d6ef94d18e71 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -46,15 +46,19 @@ import android.view.accessibility.AccessibilityManager.TouchExplorationStateChan
import androidx.annotation.LayoutRes
import androidx.annotation.VisibleForTesting
import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.settingslib.udfps.UdfpsUtils
import com.android.settingslib.udfps.UdfpsOverlayParams
+import com.android.settingslib.udfps.UdfpsUtils
import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.flags.Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS
+import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -64,6 +68,8 @@ import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.settings.SecureSettings
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import javax.inject.Provider
private const val TAG = "UdfpsControllerOverlay"
@@ -75,6 +81,7 @@ const val SETTING_REMOVE_ENROLLMENT_UI = "udfps_overlay_remove_enrollment_ui"
* request. This state can persist across configuration changes via the [show] and [hide]
* methods.
*/
+@ExperimentalCoroutinesApi
@UiThread
class UdfpsControllerOverlay @JvmOverloads constructor(
private val context: Context,
@@ -105,6 +112,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
private val udfpsUtils: UdfpsUtils,
private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
+ private val udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>,
) {
/** The view, when [isShowing], or null. */
var overlayView: UdfpsView? = null
@@ -243,27 +251,40 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
)
}
REASON_AUTH_KEYGUARD -> {
- UdfpsKeyguardViewControllerLegacy(
- view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) {
- updateSensorLocation(sensorBounds)
- },
- statusBarStateController,
- shadeExpansionStateManager,
- statusBarKeyguardViewManager,
- keyguardUpdateMonitor,
- dumpManager,
- transitionController,
- configurationController,
- keyguardStateController,
- unlockedScreenOffAnimationController,
- dialogManager,
- controller,
- activityLaunchAnimator,
- featureFlags,
- primaryBouncerInteractor,
- alternateBouncerInteractor,
- udfpsKeyguardAccessibilityDelegate,
- )
+ if (featureFlags.isEnabled(REFACTOR_UDFPS_KEYGUARD_VIEWS)) {
+ udfpsKeyguardViewModels.get().setSensorBounds(sensorBounds)
+ UdfpsKeyguardViewController(
+ view.addUdfpsView(R.layout.udfps_keyguard_view),
+ statusBarStateController,
+ shadeExpansionStateManager,
+ dialogManager,
+ dumpManager,
+ alternateBouncerInteractor,
+ udfpsKeyguardViewModels.get(),
+ )
+ } else {
+ UdfpsKeyguardViewControllerLegacy(
+ view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) {
+ updateSensorLocation(sensorBounds)
+ },
+ statusBarStateController,
+ shadeExpansionStateManager,
+ statusBarKeyguardViewManager,
+ keyguardUpdateMonitor,
+ dumpManager,
+ transitionController,
+ configurationController,
+ keyguardStateController,
+ unlockedScreenOffAnimationController,
+ dialogManager,
+ controller,
+ activityLaunchAnimator,
+ featureFlags,
+ primaryBouncerInteractor,
+ alternateBouncerInteractor,
+ udfpsKeyguardAccessibilityDelegate,
+ )
+ }
}
REASON_AUTH_BP -> {
// note: empty controller, currently shows no visual affordance
@@ -415,7 +436,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
}
private fun shouldRotate(animation: UdfpsAnimationViewController<*>?): Boolean {
- if (animation !is UdfpsKeyguardViewControllerLegacy) {
+ if (animation !is UdfpsKeyguardViewControllerAdapter) {
// always rotate view if we're not on the keyguard
return true
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt
new file mode 100644
index 000000000000..8cc15dadffd2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt
@@ -0,0 +1,58 @@
+/*
+ * 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
+
+import android.content.Context
+import android.util.AttributeSet
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** View corresponding with udfps_keyguard_view.xml */
+@ExperimentalCoroutinesApi
+class UdfpsKeyguardView(
+ context: Context,
+ attrs: AttributeSet?,
+) :
+ UdfpsAnimationView(
+ context,
+ attrs,
+ ) {
+ private val fingerprintDrawablePlaceHolder = UdfpsFpDrawable(context)
+ private var visible = false
+
+ override fun calculateAlpha(): Int {
+ return if (mPauseAuth) {
+ 0
+ } else 255 // ViewModels handle animating alpha values
+ }
+
+ override fun getDrawable(): UdfpsDrawable {
+ return fingerprintDrawablePlaceHolder
+ }
+
+ fun useExpandedOverlay(useExpandedOverlay: Boolean) {
+ mUseExpandedOverlay = useExpandedOverlay
+ }
+
+ fun isVisible(): Boolean {
+ return visible
+ }
+
+ fun setVisible(isVisible: Boolean) {
+ visible = isVisible
+ isPauseAuth = !visible
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index 9bafeeca24a8..15bd73193687 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -34,6 +34,7 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionListener
@@ -80,7 +81,8 @@ constructor(
shadeExpansionStateManager,
systemUIDialogManager,
dumpManager,
- ) {
+ ),
+ UdfpsKeyguardViewControllerAdapter {
private val useExpandedOverlay: Boolean =
featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
private var showingUdfpsBouncer = false
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt
new file mode 100644
index 000000000000..2a9f3eafc776
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.controller
+
+import com.android.systemui.biometrics.UdfpsAnimationViewController
+import com.android.systemui.biometrics.UdfpsKeyguardView
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** Class that coordinates non-HBM animations during keyguard authentication. */
+@ExperimentalCoroutinesApi
+open class UdfpsKeyguardViewController(
+ val view: UdfpsKeyguardView,
+ statusBarStateController: StatusBarStateController,
+ shadeExpansionStateManager: ShadeExpansionStateManager,
+ systemUIDialogManager: SystemUIDialogManager,
+ dumpManager: DumpManager,
+ private val alternateBouncerInteractor: AlternateBouncerInteractor,
+ udfpsKeyguardViewModels: UdfpsKeyguardViewModels,
+) :
+ UdfpsAnimationViewController<UdfpsKeyguardView>(
+ view,
+ statusBarStateController,
+ shadeExpansionStateManager,
+ systemUIDialogManager,
+ dumpManager,
+ ),
+ UdfpsKeyguardViewControllerAdapter {
+ override val tag: String
+ get() = TAG
+
+ init {
+ udfpsKeyguardViewModels.bindViews(view)
+ }
+
+ public override fun onViewAttached() {
+ super.onViewAttached()
+ alternateBouncerInteractor.setAlternateBouncerUIAvailable(true)
+ }
+
+ public override fun onViewDetached() {
+ super.onViewDetached()
+ alternateBouncerInteractor.setAlternateBouncerUIAvailable(false)
+ }
+
+ override fun shouldPauseAuth(): Boolean {
+ return !view.isVisible()
+ }
+
+ override fun listenForTouchesOutsideView(): Boolean {
+ return true
+ }
+
+ companion object {
+ private const val TAG = "UdfpsKeyguardViewController"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index a8147d0c2cf6..ff0db34ca06d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -156,7 +156,12 @@ constructor(
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
return ValueAnimator().apply {
- interpolator = Interpolators.LINEAR
+ interpolator =
+ when (toState) {
+ KeyguardState.ALTERNATE_BOUNCER -> Interpolators.FAST_OUT_SLOW_IN
+ else -> Interpolators.LINEAR
+ }
+
duration =
when (toState) {
KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 0cd97b7b025b..45bf20d3ec44 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -21,6 +21,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
@@ -61,6 +62,26 @@ constructor(
val fromDreamingTransition: Flow<TransitionStep> =
repository.transitions.filter { step -> step.from == DREAMING }
+ /** (any)->Lockscreen transition information */
+ val anyStateToLockscreenTransition: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.to == LOCKSCREEN }
+
+ /** (any)->Occluded transition information */
+ val anyStateToOccludedTransition: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.to == OCCLUDED }
+
+ /** (any)->PrimaryBouncer transition information */
+ val anyStateToPrimaryBouncerTransition: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.to == PRIMARY_BOUNCER }
+
+ /** (any)->Dreaming transition information */
+ val anyStateToDreamingTransition: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.to == DREAMING }
+
+ /** (any)->AlternateBouncer transition information */
+ val anyStateToAlternateBouncerTransition: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.to == ALTERNATE_BOUNCER }
+
/** AOD->LOCKSCREEN transition information. */
val aodToLockscreenTransition: Flow<TransitionStep> = repository.transition(AOD, LOCKSCREEN)
@@ -68,6 +89,9 @@ constructor(
val dreamingToLockscreenTransition: Flow<TransitionStep> =
repository.transition(DREAMING, LOCKSCREEN)
+ /** GONE->AOD transition information. */
+ val goneToAodTransition: Flow<TransitionStep> = repository.transition(GONE, AOD)
+
/** GONE->DREAMING transition information. */
val goneToDreamingTransition: Flow<TransitionStep> = repository.transition(GONE, DREAMING)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt
new file mode 100644
index 000000000000..bba0e37d8ed0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.animation.FloatEvaluator
+import android.animation.IntEvaluator
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/** Encapsulates business logic for transitions between UDFPS states on the keyguard. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class UdfpsKeyguardInteractor
+@Inject
+constructor(
+ configRepo: ConfigurationRepository,
+ burnInInteractor: BurnInInteractor,
+ keyguardInteractor: KeyguardInteractor,
+) {
+ private val intEvaluator = IntEvaluator()
+ private val floatEvaluator = FloatEvaluator()
+
+ val dozeAmount = keyguardInteractor.dozeAmount
+ val scaleForResolution = configRepo.scaleForResolution
+
+ /** Burn-in offsets for the UDFPS view to mitigate burn-in on AOD. */
+ val burnInOffsets: Flow<BurnInOffsets> =
+ combine(
+ keyguardInteractor.dozeAmount,
+ burnInInteractor.udfpsBurnInXOffset,
+ burnInInteractor.udfpsBurnInYOffset,
+ burnInInteractor.udfpsBurnInProgress
+ ) { dozeAmount, fullyDozingBurnInX, fullyDozingBurnInY, fullyDozingBurnInProgress ->
+ BurnInOffsets(
+ intEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInX),
+ intEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInY),
+ floatEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInProgress),
+ )
+ }
+}
+
+data class BurnInOffsets(
+ val burnInXOffset: Int, // current x burn in offset based on the aodTransitionAmount
+ val burnInYOffset: Int, // current y burn in offset based on the aodTransitionAmount
+ val burnInProgress: Float, // current progress based on the aodTransitionAmount
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt
new file mode 100644
index 000000000000..ebf1beb132f0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.ui.adapter
+
+/**
+ * Temporary adapter class while
+ * [com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController] is being refactored
+ * before [com.android.systemui.biometrics.UdfpsKeyguardViewControllerLegacy] is removed.
+ *
+ * TODO (b/278719514): Delete once udfps keyguard view is fully refactored.
+ */
+interface UdfpsKeyguardViewControllerAdapter
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
new file mode 100644
index 000000000000..728dd3911663
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.ui.binder
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.airbnb.lottie.LottieAnimationView
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+@ExperimentalCoroutinesApi
+object UdfpsAodFingerprintViewBinder {
+
+ /**
+ * Drives UI for the UDFPS aod fingerprint view. See [UdfpsFingerprintViewBinder] and
+ * [UdfpsBackgroundViewBinder].
+ */
+ @JvmStatic
+ fun bind(
+ view: LottieAnimationView,
+ viewModel: UdfpsAodViewModel,
+ ) {
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.burnInOffsets.collect { burnInOffsets ->
+ view.progress = burnInOffsets.burnInProgress
+ view.translationX = burnInOffsets.burnInXOffset.toFloat()
+ view.translationY = burnInOffsets.burnInYOffset.toFloat()
+ }
+ }
+
+ launch { viewModel.alpha.collect { alpha -> view.alpha = alpha } }
+
+ launch {
+ viewModel.padding.collect { padding ->
+ view.setPadding(padding, padding, padding, padding)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt
new file mode 100644
index 000000000000..26ef4685d286
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.ui.binder
+
+import android.content.res.ColorStateList
+import android.widget.ImageView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+@ExperimentalCoroutinesApi
+object UdfpsBackgroundViewBinder {
+
+ /**
+ * Drives UI for the udfps background view. See [UdfpsAodFingerprintViewBinder] and
+ * [UdfpsFingerprintViewBinder].
+ */
+ @JvmStatic
+ fun bind(
+ view: ImageView,
+ viewModel: BackgroundViewModel,
+ ) {
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.transition.collect {
+ view.alpha = it.alpha
+ view.scaleX = it.scale
+ view.scaleY = it.scale
+ view.imageTintList = ColorStateList.valueOf(it.color)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
new file mode 100644
index 000000000000..0ab8e52fb6c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.ui.binder
+
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.airbnb.lottie.LottieAnimationView
+import com.airbnb.lottie.LottieProperty
+import com.airbnb.lottie.model.KeyPath
+import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+@ExperimentalCoroutinesApi
+object UdfpsFingerprintViewBinder {
+ private var udfpsIconColor = 0
+
+ /**
+ * Drives UI for the UDFPS fingerprint view when it's NOT on aod. See
+ * [UdfpsAodFingerprintViewBinder] and [UdfpsBackgroundViewBinder].
+ */
+ @JvmStatic
+ fun bind(
+ view: LottieAnimationView,
+ viewModel: FingerprintViewModel,
+ ) {
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.transition.collect {
+ view.alpha = it.alpha
+ view.scaleX = it.scale
+ view.scaleY = it.scale
+ if (udfpsIconColor != (it.color)) {
+ udfpsIconColor = it.color
+ view.invalidate()
+ }
+ }
+ }
+
+ launch {
+ viewModel.burnInOffsets.collect { burnInOffsets ->
+ view.translationX = burnInOffsets.burnInXOffset.toFloat()
+ view.translationY = burnInOffsets.burnInYOffset.toFloat()
+ }
+ }
+
+ launch {
+ viewModel.dozeAmount.collect { dozeAmount ->
+ // Lottie progress represents: aod=0 to lockscreen=1
+ view.progress = 1f - dozeAmount
+ }
+ }
+
+ launch {
+ viewModel.padding.collect { padding ->
+ view.setPadding(padding, padding, padding, padding)
+ }
+ }
+ }
+ }
+
+ // Add a callback that updates the color to `udfpsIconColor` whenever invalidate is called
+ view.addValueCallback(KeyPath("**"), LottieProperty.COLOR_FILTER) {
+ PorterDuffColorFilter(udfpsIconColor, PorterDuff.Mode.SRC_ATOP)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
new file mode 100644
index 000000000000..b568a9af9bb8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.view.View
+import com.android.systemui.R
+import com.android.systemui.keyguard.ui.binder.UdfpsAodFingerprintViewBinder
+import com.android.systemui.keyguard.ui.binder.UdfpsBackgroundViewBinder
+import com.android.systemui.keyguard.ui.binder.UdfpsFingerprintViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+object UdfpsKeyguardInternalViewBinder {
+
+ @JvmStatic
+ fun bind(
+ view: View,
+ viewModel: UdfpsKeyguardInternalViewModel,
+ aodViewModel: UdfpsAodViewModel,
+ fingerprintViewModel: FingerprintViewModel,
+ backgroundViewModel: BackgroundViewModel,
+ ) {
+ view.accessibilityDelegate = viewModel.accessibilityDelegate
+
+ // bind child views
+ UdfpsAodFingerprintViewBinder.bind(view.findViewById(R.id.udfps_aod_fp), aodViewModel)
+ UdfpsFingerprintViewBinder.bind(
+ view.findViewById(R.id.udfps_lockscreen_fp),
+ fingerprintViewModel
+ )
+ UdfpsBackgroundViewBinder.bind(
+ view.findViewById(R.id.udfps_keyguard_fp_bg),
+ backgroundViewModel
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt
new file mode 100644
index 000000000000..667abaea0b24
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.ui.binder
+
+import android.graphics.RectF
+import android.view.View
+import android.widget.FrameLayout
+import androidx.asynclayoutinflater.view.AsyncLayoutInflater
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.biometrics.UdfpsKeyguardView
+import com.android.systemui.biometrics.ui.binder.UdfpsKeyguardInternalViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+@ExperimentalCoroutinesApi
+object UdfpsKeyguardViewBinder {
+ /**
+ * Drives UI for the keyguard UDFPS view. Inflates child views on a background thread. For view
+ * binders for its child views, see [UdfpsFingerprintViewBinder], [UdfpsBackgroundViewBinder] &
+ * [UdfpsAodFingerprintViewBinder].
+ */
+ @JvmStatic
+ fun bind(
+ view: UdfpsKeyguardView,
+ viewModel: UdfpsKeyguardViewModel,
+ udfpsKeyguardInternalViewModel: UdfpsKeyguardInternalViewModel,
+ aodViewModel: UdfpsAodViewModel,
+ fingerprintViewModel: FingerprintViewModel,
+ backgroundViewModel: BackgroundViewModel,
+ ) {
+ view.useExpandedOverlay(viewModel.useExpandedOverlay())
+
+ val layoutInflaterFinishListener =
+ AsyncLayoutInflater.OnInflateFinishedListener { inflatedInternalView, _, parent ->
+ UdfpsKeyguardInternalViewBinder.bind(
+ inflatedInternalView,
+ udfpsKeyguardInternalViewModel,
+ aodViewModel,
+ fingerprintViewModel,
+ backgroundViewModel,
+ )
+ if (viewModel.useExpandedOverlay()) {
+ val lp = inflatedInternalView.layoutParams as FrameLayout.LayoutParams
+ lp.width = viewModel.sensorBounds.width()
+ lp.height = viewModel.sensorBounds.height()
+ val relativeToView =
+ getBoundsRelativeToView(
+ inflatedInternalView,
+ RectF(viewModel.sensorBounds),
+ )
+ lp.setMarginsRelative(
+ relativeToView.left.toInt(),
+ relativeToView.top.toInt(),
+ relativeToView.right.toInt(),
+ relativeToView.bottom.toInt(),
+ )
+ parent!!.addView(inflatedInternalView, lp)
+ } else {
+ parent!!.addView(inflatedInternalView)
+ }
+ }
+ val inflater = AsyncLayoutInflater(view.context)
+ inflater.inflate(R.layout.udfps_keyguard_view_internal, view, layoutInflaterFinishListener)
+
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch {
+ combine(aodViewModel.isVisible, fingerprintViewModel.visible) {
+ isAodVisible,
+ isFingerprintVisible ->
+ isAodVisible || isFingerprintVisible
+ }
+ .collect { view.setVisible(it) }
+ }
+ }
+ }
+ }
+
+ /**
+ * Converts coordinates of RectF relative to the screen to coordinates relative to this view.
+ *
+ * @param bounds RectF based off screen coordinates in current orientation
+ */
+ private fun getBoundsRelativeToView(view: View, bounds: RectF): RectF {
+ val pos: IntArray = view.locationOnScreen
+ return RectF(
+ bounds.left - pos[0],
+ bounds.top - pos[1],
+ bounds.right - pos[0],
+ bounds.bottom - pos[1]
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt
new file mode 100644
index 000000000000..667c2f1bd998
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.ui.viewmodel
+
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.keyguard.domain.interactor.BurnInOffsets
+import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** View-model for UDFPS AOD view. */
+@ExperimentalCoroutinesApi
+class UdfpsAodViewModel
+@Inject
+constructor(
+ val interactor: UdfpsKeyguardInteractor,
+ val context: Context,
+) {
+ val alpha: Flow<Float> = interactor.dozeAmount
+ val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets
+ val isVisible: Flow<Boolean> = alpha.map { it != 0f }
+
+ // Padding between the fingerprint icon and its bounding box in pixels.
+ val padding: Flow<Int> =
+ interactor.scaleForResolution.map { scale ->
+ (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
+ .roundToInt()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt
new file mode 100644
index 000000000000..d894a1139eeb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.ui.viewmodel
+
+import com.android.systemui.biometrics.UdfpsKeyguardAccessibilityDelegate
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+class UdfpsKeyguardInternalViewModel
+@Inject
+constructor(val accessibilityDelegate: UdfpsKeyguardAccessibilityDelegate)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt
new file mode 100644
index 000000000000..929f27f4fea3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt
@@ -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 com.android.systemui.keyguard.ui.viewmodel
+
+import android.graphics.Rect
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+class UdfpsKeyguardViewModel
+@Inject
+constructor(
+ private val featureFlags: FeatureFlags,
+) {
+ var sensorBounds: Rect = Rect()
+
+ fun useExpandedOverlay(): Boolean {
+ return featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt
new file mode 100644
index 000000000000..098b481491de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt
@@ -0,0 +1,36 @@
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.graphics.Rect
+import com.android.systemui.biometrics.UdfpsKeyguardView
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.ui.binder.UdfpsKeyguardViewBinder
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class UdfpsKeyguardViewModels
+@Inject
+constructor(
+ private val viewModel: UdfpsKeyguardViewModel,
+ private val internalViewModel: UdfpsKeyguardInternalViewModel,
+ private val aodViewModel: UdfpsAodViewModel,
+ private val lockscreenFingerprintViewModel: FingerprintViewModel,
+ private val lockscreenBackgroundViewModel: BackgroundViewModel,
+) {
+
+ fun setSensorBounds(sensorBounds: Rect) {
+ viewModel.sensorBounds = sensorBounds
+ }
+
+ fun bindViews(view: UdfpsKeyguardView) {
+ UdfpsKeyguardViewBinder.bind(
+ view,
+ viewModel,
+ internalViewModel,
+ aodViewModel,
+ lockscreenFingerprintViewModel,
+ lockscreenBackgroundViewModel
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
new file mode 100644
index 000000000000..fd4b666a80fd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
@@ -0,0 +1,162 @@
+/*
+ * 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.ui.viewmodel
+
+import android.content.Context
+import androidx.annotation.ColorInt
+import com.android.settingslib.Utils.getColorAttrDefaultColor
+import com.android.systemui.R
+import com.android.systemui.keyguard.domain.interactor.BurnInOffsets
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/** View-model for UDFPS lockscreen views. */
+@ExperimentalCoroutinesApi
+open class UdfpsLockscreenViewModel(
+ context: Context,
+ lockscreenColorResId: Int,
+ alternateBouncerColorResId: Int,
+ transitionInteractor: KeyguardTransitionInteractor,
+) {
+ private val toLockscreen: Flow<TransitionViewModel> =
+ transitionInteractor.anyStateToLockscreenTransition.map {
+ TransitionViewModel(
+ alpha =
+ if (it.from == KeyguardState.AOD) {
+ it.value // animate
+ } else {
+ 1f
+ },
+ scale = 1f,
+ color = getColorAttrDefaultColor(context, lockscreenColorResId),
+ )
+ }
+
+ private val toAlternateBouncer: Flow<TransitionViewModel> =
+ transitionInteractor.anyStateToAlternateBouncerTransition.map {
+ TransitionViewModel(
+ alpha = 1f,
+ scale =
+ if (visibleInKeyguardState(it.from)) {
+ 1f
+ } else {
+ it.value
+ },
+ color = getColorAttrDefaultColor(context, alternateBouncerColorResId),
+ )
+ }
+
+ private val fadeOut: Flow<TransitionViewModel> =
+ merge(
+ transitionInteractor.anyStateToGoneTransition,
+ transitionInteractor.anyStateToAodTransition,
+ transitionInteractor.anyStateToOccludedTransition,
+ transitionInteractor.anyStateToPrimaryBouncerTransition,
+ transitionInteractor.anyStateToDreamingTransition,
+ )
+ .map {
+ TransitionViewModel(
+ alpha =
+ if (visibleInKeyguardState(it.from)) {
+ 1f - it.value
+ } else {
+ 0f
+ },
+ scale = 1f,
+ color =
+ if (it.from == KeyguardState.ALTERNATE_BOUNCER) {
+ getColorAttrDefaultColor(context, alternateBouncerColorResId)
+ } else {
+ getColorAttrDefaultColor(context, lockscreenColorResId)
+ },
+ )
+ }
+
+ private fun visibleInKeyguardState(state: KeyguardState): Boolean {
+ return when (state) {
+ KeyguardState.OFF,
+ KeyguardState.DOZING,
+ KeyguardState.DREAMING,
+ KeyguardState.AOD,
+ KeyguardState.PRIMARY_BOUNCER,
+ KeyguardState.GONE,
+ KeyguardState.OCCLUDED -> false
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.ALTERNATE_BOUNCER -> true
+ }
+ }
+
+ val transition: Flow<TransitionViewModel> =
+ merge(
+ toAlternateBouncer,
+ toLockscreen,
+ fadeOut,
+ )
+ val visible: Flow<Boolean> = transition.map { it.alpha != 0f }
+}
+
+@ExperimentalCoroutinesApi
+class FingerprintViewModel
+@Inject
+constructor(
+ val context: Context,
+ transitionInteractor: KeyguardTransitionInteractor,
+ interactor: UdfpsKeyguardInteractor,
+) :
+ UdfpsLockscreenViewModel(
+ context,
+ android.R.attr.textColorPrimary,
+ com.android.internal.R.attr.materialColorOnPrimaryFixed,
+ transitionInteractor,
+ ) {
+ val dozeAmount: Flow<Float> = interactor.dozeAmount
+ val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets
+
+ // Padding between the fingerprint icon and its bounding box in pixels.
+ val padding: Flow<Int> =
+ interactor.scaleForResolution.map { scale ->
+ (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
+ .roundToInt()
+ }
+}
+
+@ExperimentalCoroutinesApi
+class BackgroundViewModel
+@Inject
+constructor(
+ val context: Context,
+ transitionInteractor: KeyguardTransitionInteractor,
+) :
+ UdfpsLockscreenViewModel(
+ context,
+ com.android.internal.R.attr.colorSurface,
+ com.android.internal.R.attr.materialColorPrimaryFixed,
+ transitionInteractor,
+ )
+
+data class TransitionViewModel(
+ val alpha: Float,
+ val scale: Float,
+ @ColorInt val color: Int,
+)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 224875590d75..0e0d0e3f3748 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -43,11 +43,12 @@ import com.android.systemui.R
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -58,6 +59,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -70,6 +72,7 @@ import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
+import javax.inject.Provider
import org.mockito.Mockito.`when` as whenever
private const val REQUEST_ID = 2L
@@ -80,6 +83,7 @@ private const val DISPLAY_HEIGHT = 1920
private const val SENSOR_WIDTH = 30
private const val SENSOR_HEIGHT = 60
+@ExperimentalCoroutinesApi
@SmallTest
@RoboPilotTest
@RunWith(AndroidJUnit4::class)
@@ -116,6 +120,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
@Mock private lateinit var udfpsUtils: UdfpsUtils
@Mock private lateinit var udfpsKeyguardAccessibilityDelegate:
UdfpsKeyguardAccessibilityDelegate
+ @Mock private lateinit var udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>
@Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
@@ -148,6 +153,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
controllerCallback, onTouch, activityLaunchAnimator, featureFlags,
primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable, udfpsUtils,
udfpsKeyguardAccessibilityDelegate,
+ udfpsKeyguardViewModels,
)
block()
}
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 30e54474bbde..eb9ce1d9453a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -83,13 +83,14 @@ import com.android.systemui.biometrics.udfps.InteractionEvent;
import com.android.systemui.biometrics.udfps.NormalizedTouchData;
import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor;
import com.android.systemui.biometrics.udfps.TouchProcessorResult;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -219,6 +220,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
private SecureSettings mSecureSettings;
@Mock
private UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
+ @Mock
+ private Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels;
// Capture listeners so that they can be used to send events
@Captor
@@ -318,7 +321,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, mSessionTracker,
mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils,
mock(KeyguardFaceAuthInteractor.class),
- mUdfpsKeyguardAccessibilityDelegate);
+ mUdfpsKeyguardAccessibilityDelegate, mUdfpsKeyguardViewModels);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
new file mode 100644
index 000000000000..1baca2184e9b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
@@ -0,0 +1,168 @@
+/*
+ * 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 androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.util.mockito.eq
+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.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UdfpsKeyguardInteractorTest : SysuiTestCase() {
+ private val burnInProgress = 1f
+ private val burnInYOffset = 20
+ private val burnInXOffset = 10
+
+ private lateinit var testScope: TestScope
+ private lateinit var configRepository: FakeConfigurationRepository
+ private lateinit var bouncerRepository: KeyguardBouncerRepository
+ private lateinit var keyguardRepository: FakeKeyguardRepository
+ private lateinit var fakeCommandQueue: FakeCommandQueue
+ private lateinit var featureFlags: FakeFeatureFlags
+ private lateinit var burnInInteractor: BurnInInteractor
+
+ @Mock private lateinit var burnInHelper: BurnInHelperWrapper
+
+ private lateinit var underTest: UdfpsKeyguardInteractor
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ testScope = TestScope()
+ configRepository = FakeConfigurationRepository()
+ keyguardRepository = FakeKeyguardRepository()
+ bouncerRepository = FakeKeyguardBouncerRepository()
+ fakeCommandQueue = FakeCommandQueue()
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
+ set(Flags.FACE_AUTH_REFACTOR, false)
+ }
+ burnInInteractor =
+ BurnInInteractor(
+ context,
+ burnInHelper,
+ testScope.backgroundScope,
+ configRepository,
+ FakeSystemClock(),
+ )
+
+ underTest =
+ UdfpsKeyguardInteractor(
+ configRepository,
+ burnInInteractor,
+ KeyguardInteractor(
+ keyguardRepository,
+ fakeCommandQueue,
+ featureFlags,
+ bouncerRepository,
+ configRepository,
+ ),
+ )
+ }
+
+ @Test
+ fun dozeChanges_updatesUdfpsAodModel() =
+ testScope.runTest {
+ val burnInOffsets by collectLastValue(underTest.burnInOffsets)
+ initializeBurnInOffsets()
+
+ // WHEN we're not dozing
+ setAwake()
+ runCurrent()
+
+ // THEN burn in offsets are 0
+ assertThat(burnInOffsets?.burnInProgress).isEqualTo(0f)
+ assertThat(burnInOffsets?.burnInYOffset).isEqualTo(0)
+ assertThat(burnInOffsets?.burnInXOffset).isEqualTo(0)
+
+ // WHEN we're in the middle of the doze amount change
+ keyguardRepository.setDozeAmount(.50f)
+ runCurrent()
+
+ // THEN burn in is updated (between 0 and the full offset)
+ assertThat(burnInOffsets?.burnInProgress).isGreaterThan(0f)
+ assertThat(burnInOffsets?.burnInYOffset).isGreaterThan(0)
+ assertThat(burnInOffsets?.burnInXOffset).isGreaterThan(0)
+ assertThat(burnInOffsets?.burnInProgress).isLessThan(burnInProgress)
+ assertThat(burnInOffsets?.burnInYOffset).isLessThan(burnInYOffset)
+ assertThat(burnInOffsets?.burnInXOffset).isLessThan(burnInXOffset)
+
+ // WHEN we're fully dozing
+ keyguardRepository.setDozeAmount(1f)
+ runCurrent()
+
+ // THEN burn in offsets are updated to final current values (for the given time)
+ assertThat(burnInOffsets?.burnInProgress).isEqualTo(burnInProgress)
+ assertThat(burnInOffsets?.burnInYOffset).isEqualTo(burnInYOffset)
+ assertThat(burnInOffsets?.burnInXOffset).isEqualTo(burnInXOffset)
+ }
+
+ private fun initializeBurnInOffsets() {
+ whenever(burnInHelper.burnInProgressOffset()).thenReturn(burnInProgress)
+ whenever(burnInHelper.burnInOffset(anyInt(), /* xAxis */ eq(true)))
+ .thenReturn(burnInXOffset)
+ whenever(burnInHelper.burnInOffset(anyInt(), /* xAxis */ eq(false)))
+ .thenReturn(burnInYOffset)
+ }
+
+ private fun setAwake() {
+ keyguardRepository.setDozeAmount(0f)
+ burnInInteractor.dozeTimeTick()
+
+ bouncerRepository.setAlternateVisible(false)
+ keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ bouncerRepository.setPrimaryShow(false)
+ keyguardRepository.setWakefulnessModel(
+ WakefulnessModel(
+ WakefulnessState.AWAKE,
+ WakeSleepReason.POWER_BUTTON,
+ WakeSleepReason.POWER_BUTTON,
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt
new file mode 100644
index 000000000000..436c09ca4a05
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt
@@ -0,0 +1,149 @@
+/*
+ * 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.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+class UdfpsAodViewModelTest : SysuiTestCase() {
+ private val defaultPadding = 12
+ private lateinit var underTest: UdfpsAodViewModel
+
+ private lateinit var testScope: TestScope
+ private lateinit var configRepository: FakeConfigurationRepository
+ private lateinit var bouncerRepository: KeyguardBouncerRepository
+ private lateinit var keyguardRepository: FakeKeyguardRepository
+ private lateinit var fakeCommandQueue: FakeCommandQueue
+ private lateinit var featureFlags: FakeFeatureFlags
+
+ @Mock private lateinit var burnInHelper: BurnInHelperWrapper
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ overrideResource(com.android.systemui.R.dimen.lock_icon_padding, defaultPadding)
+ testScope = TestScope()
+ configRepository = FakeConfigurationRepository()
+ keyguardRepository = FakeKeyguardRepository()
+ bouncerRepository = FakeKeyguardBouncerRepository()
+ fakeCommandQueue = FakeCommandQueue()
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
+ set(Flags.FACE_AUTH_REFACTOR, false)
+ }
+
+ val udfpsKeyguardInteractor =
+ UdfpsKeyguardInteractor(
+ configRepository,
+ BurnInInteractor(
+ context,
+ burnInHelper,
+ testScope.backgroundScope,
+ configRepository,
+ FakeSystemClock(),
+ ),
+ KeyguardInteractor(
+ keyguardRepository,
+ fakeCommandQueue,
+ featureFlags,
+ bouncerRepository,
+ configRepository,
+ ),
+ )
+
+ underTest =
+ UdfpsAodViewModel(
+ udfpsKeyguardInteractor,
+ context,
+ )
+ }
+
+ @Test
+ fun alphaAndVisibleUpdates_onDozeAmountChanges() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.alpha)
+ val visible by collectLastValue(underTest.isVisible)
+
+ keyguardRepository.setDozeAmount(0f)
+ runCurrent()
+ assertThat(alpha).isEqualTo(0f)
+ assertThat(visible).isFalse()
+
+ keyguardRepository.setDozeAmount(.65f)
+ runCurrent()
+ assertThat(alpha).isEqualTo(.65f)
+ assertThat(visible).isTrue()
+
+ keyguardRepository.setDozeAmount(.23f)
+ runCurrent()
+ assertThat(alpha).isEqualTo(.23f)
+ assertThat(visible).isTrue()
+
+ keyguardRepository.setDozeAmount(1f)
+ runCurrent()
+ assertThat(alpha).isEqualTo(1f)
+ assertThat(visible).isTrue()
+ }
+
+ @Test
+ fun paddingUpdates_onScaleForResolutionChanges() =
+ testScope.runTest {
+ val padding by collectLastValue(underTest.padding)
+
+ configRepository.setScaleForResolution(1f)
+ runCurrent()
+ assertThat(padding).isEqualTo(defaultPadding)
+
+ configRepository.setScaleForResolution(2f)
+ runCurrent()
+ assertThat(padding).isEqualTo(defaultPadding * 2)
+
+ configRepository.setScaleForResolution(.5f)
+ runCurrent()
+ assertThat(padding).isEqualTo((defaultPadding * .5f).toInt())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
new file mode 100644
index 000000000000..a30e2a601e9d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
@@ -0,0 +1,131 @@
+/*
+ * 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.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+/** Tests UdfpsFingerprintViewModel specific flows. */
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UdfpsFingerprintViewModelTest : SysuiTestCase() {
+ private val defaultPadding = 12
+ private lateinit var underTest: FingerprintViewModel
+
+ private lateinit var testScope: TestScope
+ private lateinit var configRepository: FakeConfigurationRepository
+ private lateinit var bouncerRepository: KeyguardBouncerRepository
+ private lateinit var keyguardRepository: FakeKeyguardRepository
+ private lateinit var fakeCommandQueue: FakeCommandQueue
+ private lateinit var featureFlags: FakeFeatureFlags
+ private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+
+ @Mock private lateinit var burnInHelper: BurnInHelperWrapper
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ overrideResource(com.android.systemui.R.dimen.lock_icon_padding, defaultPadding)
+ testScope = TestScope()
+ configRepository = FakeConfigurationRepository()
+ keyguardRepository = FakeKeyguardRepository()
+ bouncerRepository = FakeKeyguardBouncerRepository()
+ fakeCommandQueue = FakeCommandQueue()
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
+ set(Flags.FACE_AUTH_REFACTOR, false)
+ }
+ bouncerRepository = FakeKeyguardBouncerRepository()
+ transitionRepository = FakeKeyguardTransitionRepository()
+ val transitionInteractor =
+ KeyguardTransitionInteractor(
+ transitionRepository,
+ testScope.backgroundScope,
+ )
+ val udfpsKeyguardInteractor =
+ UdfpsKeyguardInteractor(
+ configRepository,
+ BurnInInteractor(
+ context,
+ burnInHelper,
+ testScope.backgroundScope,
+ configRepository,
+ FakeSystemClock(),
+ ),
+ KeyguardInteractor(
+ keyguardRepository,
+ fakeCommandQueue,
+ featureFlags,
+ bouncerRepository,
+ configRepository,
+ ),
+ )
+
+ underTest =
+ FingerprintViewModel(
+ context,
+ transitionInteractor,
+ udfpsKeyguardInteractor,
+ )
+ }
+
+ @Test
+ fun paddingUpdates_onScaleForResolutionChanges() =
+ testScope.runTest {
+ val padding by collectLastValue(underTest.padding)
+
+ configRepository.setScaleForResolution(1f)
+ runCurrent()
+ assertThat(padding).isEqualTo(defaultPadding)
+
+ configRepository.setScaleForResolution(2f)
+ runCurrent()
+ assertThat(padding).isEqualTo(defaultPadding * 2)
+
+ configRepository.setScaleForResolution(.5f)
+ runCurrent()
+ assertThat(padding).isEqualTo((defaultPadding * .5).toInt())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
new file mode 100644
index 000000000000..d58ceee40c68
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
@@ -0,0 +1,505 @@
+/*
+ * 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.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.Utils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.collect.Range
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+/** Tests UDFPS lockscreen view model transitions. */
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UdfpsLockscreenViewModelTest : SysuiTestCase() {
+ private val lockscreenColorResId = android.R.attr.textColorPrimary
+ private val alternateBouncerResId = com.android.internal.R.attr.materialColorOnPrimaryFixed
+ private val lockscreenColor = Utils.getColorAttrDefaultColor(context, lockscreenColorResId)
+ private val alternateBouncerColor =
+ Utils.getColorAttrDefaultColor(context, alternateBouncerResId)
+
+ private lateinit var underTest: UdfpsLockscreenViewModel
+ private lateinit var testScope: TestScope
+ private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testScope = TestScope()
+ transitionRepository = FakeKeyguardTransitionRepository()
+ val transitionInteractor =
+ KeyguardTransitionInteractor(
+ transitionRepository,
+ testScope.backgroundScope,
+ )
+ underTest =
+ UdfpsLockscreenViewModel(
+ context,
+ lockscreenColorResId,
+ alternateBouncerResId,
+ transitionInteractor,
+ )
+ }
+
+ @Test
+ fun goneToAodTransition() =
+ testScope.runTest {
+ val transition by collectLastValue(underTest.transition)
+ val visible by collectLastValue(underTest.visible)
+
+ // TransitionState.STARTED: gone -> AOD
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ ownerName = "goneToAodTransition",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(0f)
+ assertThat(visible).isFalse()
+
+ // TransitionState.RUNNING: gone -> AOD
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ value = .6f,
+ transitionState = TransitionState.RUNNING,
+ ownerName = "goneToAodTransition",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(0f)
+ assertThat(visible).isFalse()
+
+ // TransitionState.FINISHED: gone -> AOD
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ value = 1f,
+ transitionState = TransitionState.FINISHED,
+ ownerName = "goneToAodTransition",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(0f)
+ assertThat(visible).isFalse()
+ }
+
+ @Test
+ fun lockscreenToAod() =
+ testScope.runTest {
+ val transition by collectLastValue(underTest.transition)
+ val visible by collectLastValue(underTest.visible)
+
+ // TransitionState.STARTED: lockscreen -> AOD
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ ownerName = "lockscreenToAod",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(1f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(lockscreenColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.RUNNING: lockscreen -> AOD
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = .6f,
+ transitionState = TransitionState.RUNNING,
+ ownerName = "lockscreenToAod",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f))
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(lockscreenColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.FINISHED: lockscreen -> AOD
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 1f,
+ transitionState = TransitionState.FINISHED,
+ ownerName = "lockscreenToAod",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(0f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(lockscreenColor)
+ assertThat(visible).isFalse()
+ }
+
+ @Test
+ fun aodToLockscreen() =
+ testScope.runTest {
+ val transition by collectLastValue(underTest.transition)
+ val visible by collectLastValue(underTest.visible)
+
+ // TransitionState.STARTED: AOD -> lockscreen
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ ownerName = "aodToLockscreen",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(0f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(lockscreenColor)
+ assertThat(visible).isFalse()
+
+ // TransitionState.RUNNING: AOD -> lockscreen
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ value = .6f,
+ transitionState = TransitionState.RUNNING,
+ ownerName = "aodToLockscreen",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isIn(Range.closed(.59f, .61f))
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(lockscreenColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.FINISHED: AOD -> lockscreen
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ value = 1f,
+ transitionState = TransitionState.FINISHED,
+ ownerName = "aodToLockscreen",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(1f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(lockscreenColor)
+ assertThat(visible).isTrue()
+ }
+
+ @Test
+ fun lockscreenToAlternateBouncer() =
+ testScope.runTest {
+ val transition by collectLastValue(underTest.transition)
+ val visible by collectLastValue(underTest.visible)
+
+ // TransitionState.STARTED: lockscreen -> alternate bouncer
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.ALTERNATE_BOUNCER,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ ownerName = "lockscreenToAlternateBouncer",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(1f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.RUNNING: lockscreen -> alternate bouncer
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.ALTERNATE_BOUNCER,
+ value = .6f,
+ transitionState = TransitionState.RUNNING,
+ ownerName = "lockscreenToAlternateBouncer",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(1f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.FINISHED: lockscreen -> alternate bouncer
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.ALTERNATE_BOUNCER,
+ value = 1f,
+ transitionState = TransitionState.FINISHED,
+ ownerName = "lockscreenToAlternateBouncer",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(1f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+ assertThat(visible).isTrue()
+ }
+
+ fun alternateBouncerToPrimaryBouncer() =
+ testScope.runTest {
+ val transition by collectLastValue(underTest.transition)
+ val visible by collectLastValue(underTest.visible)
+
+ // TransitionState.STARTED: alternate bouncer -> primary bouncer
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ ownerName = "alternateBouncerToPrimaryBouncer",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(1f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.RUNNING: alternate bouncer -> primary bouncer
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ value = .6f,
+ transitionState = TransitionState.RUNNING,
+ ownerName = "alternateBouncerToPrimaryBouncer",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isIn(Range.closed(.59f, .61f))
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.FINISHED: alternate bouncer -> primary bouncer
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ value = 1f,
+ transitionState = TransitionState.FINISHED,
+ ownerName = "alternateBouncerToPrimaryBouncer",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(0f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+ assertThat(visible).isFalse()
+ }
+
+ fun alternateBouncerToAod() =
+ testScope.runTest {
+ val transition by collectLastValue(underTest.transition)
+ val visible by collectLastValue(underTest.visible)
+
+ // TransitionState.STARTED: alternate bouncer -> aod
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.AOD,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ ownerName = "alternateBouncerToAod",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(1f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.RUNNING: alternate bouncer -> aod
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.AOD,
+ value = .6f,
+ transitionState = TransitionState.RUNNING,
+ ownerName = "alternateBouncerToAod",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f))
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.FINISHED: alternate bouncer -> aod
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.AOD,
+ value = 1f,
+ transitionState = TransitionState.FINISHED,
+ ownerName = "alternateBouncerToAod",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(0f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+ assertThat(visible).isFalse()
+ }
+
+ @Test
+ fun lockscreenToOccluded() =
+ testScope.runTest {
+ val transition by collectLastValue(underTest.transition)
+ val visible by collectLastValue(underTest.visible)
+
+ // TransitionState.STARTED: lockscreen -> occluded
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ ownerName = "lockscreenToOccluded",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(1f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(lockscreenColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.RUNNING: lockscreen -> occluded
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ value = .6f,
+ transitionState = TransitionState.RUNNING,
+ ownerName = "lockscreenToOccluded",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f))
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(lockscreenColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.FINISHED: lockscreen -> occluded
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ value = 1f,
+ transitionState = TransitionState.FINISHED,
+ ownerName = "lockscreenToOccluded",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(0f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(lockscreenColor)
+ assertThat(visible).isFalse()
+ }
+
+ @Test
+ fun occludedToLockscreen() =
+ testScope.runTest {
+ val transition by collectLastValue(underTest.transition)
+ val visible by collectLastValue(underTest.visible)
+
+ // TransitionState.STARTED: occluded -> lockscreen
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.LOCKSCREEN,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ ownerName = "occludedToLockscreen",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(1f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(lockscreenColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.RUNNING: occluded -> lockscreen
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.LOCKSCREEN,
+ value = .6f,
+ transitionState = TransitionState.RUNNING,
+ ownerName = "occludedToLockscreen",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(1f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(lockscreenColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.FINISHED: occluded -> lockscreen
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.LOCKSCREEN,
+ value = 1f,
+ transitionState = TransitionState.FINISHED,
+ ownerName = "occludedToLockscreen",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(1f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(lockscreenColor)
+ assertThat(visible).isTrue()
+ }
+}