Merge "Modernize UDFPS views for maintainability" into udc-qpr-dev

This commit is contained in:
Beverly Tai
2023-06-30 03:00:44 +00:00
committed by Android (Google) Code Review
27 changed files with 1985 additions and 37 deletions

View File

@@ -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>

View File

@@ -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">

View File

@@ -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);

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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"
}
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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
)

View File

@@ -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

View File

@@ -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)
}
}
}
}
}
}

View File

@@ -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)
}
}
}
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -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
)
}
}

View File

@@ -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]
)
}
}

View File

@@ -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()
}
}

View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -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
)
}
}

View File

@@ -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,
)

View File

@@ -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()
}

View File

@@ -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());

View File

@@ -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,
)
)
}
}

View File

@@ -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())
}
}

View File

@@ -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())
}
}

View File

@@ -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()
}
}