Transitions - Smooth out bouncer -> gone

If the device unlocks while the bouncer is in transit, the bouncer
will remain visible over the launcher for half a second while the
appear animation completes while the scrim immediately
disappears (while also undergoing numerous bad requests to transition
to invalid states).

To fix, control this animation using the transition repo instead and
ignore all other incoming requests while it is running.

Also, support a new mode for canceling transitions. Some transitions
may want to continue from the canceled point, others may want to run
the starting transition in full.

Fixes: 268596047
Test: ScrimControllerTest KeyguardRepositoryImplTest
NotificationShadeWindowViewControllerTest
KeyguardTransitionScenariosTest KeyguardTransitionRepositoryTest
Test: manual - Tested all bouncer variations, with and without face
unlock

Change-Id: I6fdd0b796e8833539d06c8534182f1298c7e3a58
This commit is contained in:
Matt Pietal
2023-02-17 14:05:34 +00:00
parent a8db72ad6f
commit 0684a78d3a
23 changed files with 445 additions and 230 deletions

View File

@@ -82,7 +82,7 @@
<!-- The vertical margin between the date and the owner info. -->
<!-- The translation for disappearing security views after having solved them. -->
<dimen name="disappear_y_translation">-32dp</dimen>
<dimen name="disappear_y_translation">-50dp</dimen>
<!-- Dimens for animation for the Bouncer PIN view -->
<dimen name="pin_view_trans_y_entry">120dp</dimen>

View File

@@ -39,7 +39,6 @@ import static java.lang.Integer.max;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
@@ -1044,13 +1043,10 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
int yTranslation = mResources.getDimensionPixelSize(R.dimen.disappear_y_translation);
AnimatorSet anims = new AnimatorSet();
ObjectAnimator yAnim = ObjectAnimator.ofFloat(mView, View.TRANSLATION_Y, yTranslation);
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mView, View.ALPHA, 0f);
anims.setInterpolator(Interpolators.STANDARD_ACCELERATE);
anims.playTogether(alphaAnim, yAnim);
anims.start();
yAnim.setInterpolator(Interpolators.STANDARD_ACCELERATE);
yAnim.setDuration(500);
yAnim.start();
}
private void setupUserSwitcher() {

View File

@@ -637,12 +637,17 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
public void startAppearAnimation() {
if (mCurrentSecurityMode != SecurityMode.None) {
mView.setAlpha(1f);
setAlpha(1f);
mView.startAppearAnimation(mCurrentSecurityMode);
getCurrentSecurityController().startAppearAnimation();
}
}
/** Set the alpha of the security container view */
public void setAlpha(float alpha) {
mView.setAlpha(alpha);
}
public boolean startDisappearAnimation(Runnable onFinishRunnable) {
boolean didRunAnimation = false;

View File

@@ -80,6 +80,9 @@ interface KeyguardRepository {
*/
val isKeyguardShowing: Flow<Boolean>
/** Is the keyguard in a unlocked state? */
val isKeyguardUnlocked: Flow<Boolean>
/** Is an activity showing over the keyguard? */
val isKeyguardOccluded: Flow<Boolean>
@@ -278,6 +281,31 @@ constructor(
}
.distinctUntilChanged()
override val isKeyguardUnlocked: Flow<Boolean> =
conflatedCallbackFlow {
val callback =
object : KeyguardStateController.Callback {
override fun onUnlockedChanged() {
trySendWithFailureLogging(
keyguardStateController.isUnlocked,
TAG,
"updated isKeyguardUnlocked"
)
}
}
keyguardStateController.addCallback(callback)
// Adding the callback does not send an initial update.
trySendWithFailureLogging(
keyguardStateController.isUnlocked,
TAG,
"initial isKeyguardUnlocked"
)
awaitClose { keyguardStateController.removeCallback(callback) }
}
.distinctUntilChanged()
override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow {
val callback =
object : KeyguardStateController.Callback {

View File

@@ -68,8 +68,11 @@ interface KeyguardTransitionRepository {
/**
* Begin a transition from one state to another. Transitions are interruptible, and will issue a
* [TransitionStep] with state = [TransitionState.CANCELED] before beginning the next one.
*
* When canceled, there are two options: to continue from the current position of the prior
* transition, or to reset the position. When [resetIfCanceled] == true, it will do the latter.
*/
fun startTransition(info: TransitionInfo): UUID?
fun startTransition(info: TransitionInfo, resetIfCanceled: Boolean = false): UUID?
/**
* Allows manual control of a transition. When calling [startTransition], the consumer must pass
@@ -130,7 +133,10 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio
)
}
override fun startTransition(info: TransitionInfo): UUID? {
override fun startTransition(
info: TransitionInfo,
resetIfCanceled: Boolean,
): UUID? {
if (lastStep.from == info.from && lastStep.to == info.to) {
Log.i(TAG, "Duplicate call to start the transition, rejecting: $info")
return null
@@ -138,7 +144,11 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio
val startingValue =
if (lastStep.transitionState != TransitionState.FINISHED) {
Log.i(TAG, "Transition still active: $lastStep, canceling")
lastStep.value
if (resetIfCanceled) {
0f
} else {
lastStep.value
}
} else {
0f
}
@@ -227,10 +237,7 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio
}
private fun trace(step: TransitionStep, isManual: Boolean) {
if (
step.transitionState != TransitionState.STARTED &&
step.transitionState != TransitionState.FINISHED
) {
if (step.transitionState == TransitionState.RUNNING) {
return
}
val traceName =
@@ -243,7 +250,10 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio
val traceCookie = traceName.hashCode()
if (step.transitionState == TransitionState.STARTED) {
Trace.beginAsyncSection(traceName, traceCookie)
} else if (step.transitionState == TransitionState.FINISHED) {
} else if (
step.transitionState == TransitionState.FINISHED ||
step.transitionState == TransitionState.CANCELED
) {
Trace.endAsyncSection(traceName, traceCookie)
}
}

View File

@@ -34,7 +34,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@@ -57,14 +56,7 @@ constructor(
private fun listenForDreamingToLockscreen() {
scope.launch {
// Dependending on the dream, either dream state or occluded change will change first,
// so listen for both
combine(keyguardInteractor.isAbleToDream, keyguardInteractor.isKeyguardOccluded) {
isAbleToDream,
isKeyguardOccluded ->
isAbleToDream && isKeyguardOccluded
}
.distinctUntilChanged()
keyguardInteractor.isAbleToDream
.sample(
combine(
keyguardInteractor.dozeTransitionModel,

View File

@@ -130,55 +130,59 @@ constructor(
shadeRepository.shadeModel
.sample(
combine(
keyguardTransitionInteractor.finishedKeyguardState,
keyguardTransitionInteractor.startedKeyguardTransitionStep,
keyguardInteractor.statusBarState,
::Pair
keyguardInteractor.isKeyguardUnlocked,
::toTriple
),
::toTriple
::toQuad
)
.collect { (shadeModel, keyguardState, statusBarState) ->
.collect { (shadeModel, keyguardState, statusBarState, isKeyguardUnlocked) ->
val id = transitionId
if (id != null) {
// An existing `id` means a transition is started, and calls to
// `updateTransition` will control it until FINISHED or CANCELED
var nextState =
if (shadeModel.expansionAmount == 0f) {
TransitionState.FINISHED
} else if (shadeModel.expansionAmount == 1f) {
TransitionState.CANCELED
} else {
TransitionState.RUNNING
}
keyguardTransitionRepository.updateTransition(
id,
1f - shadeModel.expansionAmount,
nextState,
)
if (
nextState == TransitionState.CANCELED ||
nextState == TransitionState.FINISHED
) {
transitionId = null
}
// If canceled, just put the state back
if (nextState == TransitionState.CANCELED) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
ownerName = name,
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.LOCKSCREEN,
animator = getAnimator(0.milliseconds)
)
if (keyguardState.to == KeyguardState.PRIMARY_BOUNCER) {
// An existing `id` means a transition is started, and calls to
// `updateTransition` will control it until FINISHED or CANCELED
var nextState =
if (shadeModel.expansionAmount == 0f) {
TransitionState.FINISHED
} else if (shadeModel.expansionAmount == 1f) {
TransitionState.CANCELED
} else {
TransitionState.RUNNING
}
keyguardTransitionRepository.updateTransition(
id,
1f - shadeModel.expansionAmount,
nextState,
)
if (
nextState == TransitionState.CANCELED ||
nextState == TransitionState.FINISHED
) {
transitionId = null
}
// If canceled, just put the state back
if (nextState == TransitionState.CANCELED) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
ownerName = name,
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.LOCKSCREEN,
animator = getAnimator(0.milliseconds)
)
)
}
}
} else {
// TODO (b/251849525): Remove statusbarstate check when that state is
// integrated into KeyguardTransitionRepository
if (
keyguardState == KeyguardState.LOCKSCREEN &&
keyguardState.to == KeyguardState.LOCKSCREEN &&
shadeModel.isUserDragging &&
!isKeyguardUnlocked &&
statusBarState == KEYGUARD
) {
transitionId =

View File

@@ -17,6 +17,9 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Password
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -26,6 +29,8 @@ import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@@ -37,7 +42,8 @@ constructor(
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val keyguardSecurityModel: KeyguardSecurityModel,
) : TransitionInteractor(FromPrimaryBouncerTransitionInteractor::class.simpleName!!) {
override fun start() {
@@ -93,31 +99,47 @@ constructor(
private fun listenForPrimaryBouncerToGone() {
scope.launch {
keyguardInteractor.isKeyguardGoingAway
.sample(keyguardTransitionInteractor.finishedKeyguardState) { a, b -> Pair(a, b) }
.collect { pair ->
val (isKeyguardGoingAway, keyguardState) = pair
if (isKeyguardGoingAway && keyguardState == KeyguardState.PRIMARY_BOUNCER) {
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { (isKeyguardGoingAway, lastStartedTransitionStep) ->
if (
isKeyguardGoingAway &&
lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
) {
val securityMode =
keyguardSecurityModel.getSecurityMode(
KeyguardUpdateMonitor.getCurrentUser()
)
// IME for password requires a slightly faster animation
val duration =
if (securityMode == Password) {
TO_GONE_SHORT_DURATION
} else {
TO_GONE_DURATION
}
keyguardTransitionRepository.startTransition(
TransitionInfo(
ownerName = name,
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.GONE,
animator = getAnimator(),
)
animator = getAnimator(duration),
),
resetIfCanceled = true,
)
}
}
}
}
private fun getAnimator(): ValueAnimator {
private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
return ValueAnimator().apply {
setInterpolator(Interpolators.LINEAR)
setDuration(TRANSITION_DURATION_MS)
setDuration(duration.inWholeMilliseconds)
}
}
companion object {
private const val TRANSITION_DURATION_MS = 300L
private val DEFAULT_DURATION = 300.milliseconds
val TO_GONE_DURATION = 250.milliseconds
val TO_GONE_SHORT_DURATION = 200.milliseconds
}
}

View File

@@ -33,7 +33,9 @@ import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDoz
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
@@ -95,6 +97,9 @@ constructor(
awaitClose { commandQueue.removeCallback(callback) }
}
/** The device wake/sleep state */
val wakefulnessModel: Flow<WakefulnessModel> = repository.wakefulness
/**
* Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means
* that doze mode is not running and DREAMING is ok to commence.
@@ -109,6 +114,12 @@ constructor(
isDreaming && isDozeOff(dozeTransitionModel.to)
}
)
.sample(
wakefulnessModel,
{ isAbleToDream, wakefulnessModel ->
isAbleToDream && isWakingOrStartingToWake(wakefulnessModel)
}
)
.flatMapLatest { isAbleToDream ->
flow {
delay(50)
@@ -119,6 +130,8 @@ constructor(
/** Whether the keyguard is showing or not. */
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
/** Whether the keyguard is unlocked or not. */
val isKeyguardUnlocked: Flow<Boolean> = repository.isKeyguardUnlocked
/** Whether the keyguard is occluded (covered by an activity). */
val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded
/** Whether the keyguard is going away. */
@@ -127,8 +140,6 @@ constructor(
val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerVisible
/** Whether the alternate bouncer is showing or not. */
val alternateBouncerShowing: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
/** The device wake/sleep state */
val wakefulnessModel: Flow<WakefulnessModel> = repository.wakefulness
/** Observable for the [StatusBarState] */
val statusBarState: Flow<StatusBarState> = repository.statusBarState
/**

View File

@@ -61,7 +61,15 @@ constructor(
}
scope.launch {
keyguardInteractor.isDreaming.collect { logger.log(TAG, VERBOSE, "isDreaming", it) }
keyguardInteractor.isAbleToDream.collect {
logger.log(TAG, VERBOSE, "isAbleToDream", it)
}
}
scope.launch {
keyguardInteractor.isKeyguardOccluded.collect {
logger.log(TAG, VERBOSE, "isOccluded", it)
}
}
scope.launch {

View File

@@ -78,6 +78,10 @@ constructor(
val occludedToLockscreenTransition: Flow<TransitionStep> =
repository.transition(OCCLUDED, LOCKSCREEN)
/** PRIMARY_BOUNCER->GONE transition information. */
val primaryBouncerToGoneTransition: Flow<TransitionStep> =
repository.transition(PRIMARY_BOUNCER, GONE)
/**
* AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
* Lockscreen (0f).

View File

@@ -31,6 +31,7 @@ import com.android.settingslib.Utils
import com.android.systemui.keyguard.data.BouncerViewDelegate
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.ActivityStarter
import kotlinx.coroutines.awaitCancellation
@@ -44,6 +45,7 @@ object KeyguardBouncerViewBinder {
fun bind(
view: ViewGroup,
viewModel: KeyguardBouncerViewModel,
primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
componentFactory: KeyguardBouncerComponent.Factory
) {
// Builds the KeyguardSecurityContainerController from bouncer view group.
@@ -144,6 +146,12 @@ object KeyguardBouncerViewBinder {
}
}
launch {
primaryBouncerToGoneTransitionViewModel.bouncerAlpha.collect { alpha ->
securityContainerController.setAlpha(alpha)
}
}
launch {
viewModel.bouncerExpansionAmount
.filter { it == EXPANSION_VISIBLE }

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.keyguard.ui.viewmodel
import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
/**
* Breaks down PRIMARY_BOUNCER->GONE transition into discrete steps for corresponding views to
* consume.
*/
@SysUISingleton
class PrimaryBouncerToGoneTransitionViewModel
@Inject
constructor(
private val interactor: KeyguardTransitionInteractor,
) {
private val transitionAnimation =
KeyguardTransitionAnimationFlow(
transitionDuration = TO_GONE_DURATION,
transitionFlow = interactor.primaryBouncerToGoneTransition,
)
/** Bouncer container alpha */
val bouncerAlpha: Flow<Float> =
transitionAnimation.createFlow(
duration = 200.milliseconds,
onStep = { 1f - it },
)
/** Scrim alpha */
val scrimAlpha: Flow<Float> =
transitionAnimation.createFlow(
duration = TO_GONE_DURATION,
interpolator = EMPHASIZED_ACCELERATE,
onStep = { 1f - it },
)
}

View File

@@ -45,6 +45,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.keyguard.ui.binder.KeyguardBouncerViewBinder;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationInsetsController;
@@ -133,7 +134,8 @@ public class NotificationShadeWindowViewController {
KeyguardBouncerViewModel keyguardBouncerViewModel,
KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory,
AlternateBouncerInteractor alternateBouncerInteractor,
KeyguardTransitionInteractor keyguardTransitionInteractor
KeyguardTransitionInteractor keyguardTransitionInteractor,
PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel
) {
mLockscreenShadeTransitionController = transitionController;
mFalsingCollector = falsingCollector;
@@ -160,6 +162,7 @@ public class NotificationShadeWindowViewController {
KeyguardBouncerViewBinder.bind(
mView.findViewById(R.id.keyguard_bouncer_container),
keyguardBouncerViewModel,
primaryBouncerToGoneTransitionViewModel,
keyguardBouncerComponentFactory);
collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),

View File

@@ -3747,6 +3747,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
@Override
public void notifyBiometricAuthModeChanged() {
mDozeServiceHost.updateDozing();
if (mBiometricUnlockController.getMode()
== BiometricUnlockController.MODE_DISMISS_BOUNCER) {
// Don't update the scrim controller at this time, in favor of the transition repository
// updating the scrim
return;
}
updateScrimController();
}

View File

@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import static java.lang.Float.isNaN;
import android.animation.Animator;
@@ -53,7 +55,11 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -71,6 +77,8 @@ import java.util.function.Consumer;
import javax.inject.Inject;
import kotlinx.coroutines.CoroutineDispatcher;
/**
* Controls both the scrim behind the notifications and in front of the notifications (when a
* security method gets shown).
@@ -251,6 +259,28 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
private boolean mWakeLockHeld;
private boolean mKeyguardOccluded;
private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
private CoroutineDispatcher mMainDispatcher;
private boolean mIsBouncerToGoneTransitionStarted = false;
private boolean mIsBouncerToGoneTransitionRunning = false;
private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
private final Consumer<Float> mScrimAlphaConsumer =
(Float alpha) -> {
mScrimInFront.setViewAlpha(0f);
mNotificationsScrim.setViewAlpha(0f);
mScrimBehind.setViewAlpha(alpha);
};
final Consumer<TransitionStep> mPrimaryBouncerToGoneTransition =
(TransitionStep step) -> {
mIsBouncerToGoneTransitionRunning =
step.getTransitionState() == TransitionState.RUNNING;
mIsBouncerToGoneTransitionStarted =
step.getTransitionState() == TransitionState.STARTED;
if (mIsBouncerToGoneTransitionStarted) {
transitionTo(ScrimState.UNLOCKED);
}
};
@Inject
public ScrimController(
LightBarController lightBarController,
@@ -265,7 +295,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
@Main Executor mainExecutor,
ScreenOffAnimationController screenOffAnimationController,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
KeyguardTransitionInteractor keyguardTransitionInteractor,
@Main CoroutineDispatcher mainDispatcher) {
mScrimStateListener = lightBarController::setScrimState;
mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
@@ -304,6 +337,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
}
});
mColors = new GradientColors();
mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mMainDispatcher = mainDispatcher;
}
/**
@@ -343,6 +379,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
for (ScrimState state : ScrimState.values()) {
state.prepare(state);
}
collectFlow(behindScrim, mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition(),
mPrimaryBouncerToGoneTransition, mMainDispatcher);
collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha(),
mScrimAlphaConsumer, mMainDispatcher);
}
// TODO(b/270984686) recompute scrim height accurately, based on shade contents.
@@ -372,6 +413,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
}
public void transitionTo(ScrimState state, Callback callback) {
if (mIsBouncerToGoneTransitionRunning) {
Log.i(TAG, "Skipping transition to: " + state
+ " while mIsBouncerToGoneTransitionRunning");
return;
}
if (state == mState) {
// Call the callback anyway, unless it's already enqueued
if (callback != null && mCallback != callback) {
@@ -797,10 +843,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
mBehindAlpha = 0;
mNotificationsAlpha = 0;
} else {
// Behind scrim will finish fading in at 30% expansion.
float behindFraction = MathUtils
.constrainedMap(0f, 1f, 0f, 0.3f, mPanelExpansionFraction);
mBehindAlpha = behindFraction * mDefaultScrimAlpha;
if (!mIsBouncerToGoneTransitionStarted) {
mBehindAlpha = behindFraction * mDefaultScrimAlpha;
}
// Delay fade-in of notification scrim a bit further, to coincide with the
// behind scrim finishing fading in.
// Also to coincide with the view starting to fade in, otherwise the empty
@@ -1138,7 +1185,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_tint",
Color.alpha(tint));
scrimView.setTint(tint);
scrimView.setViewAlpha(alpha);
if (!mIsBouncerToGoneTransitionRunning) {
scrimView.setViewAlpha(alpha);
}
} else {
scrim.setAlpha(alpha);
}
@@ -1486,6 +1535,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
}
public void setKeyguardOccluded(boolean keyguardOccluded) {
if (mKeyguardOccluded == keyguardOccluded) {
return;
}
mKeyguardOccluded = keyguardOccluded;
updateScrims();
}

View File

@@ -218,6 +218,29 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
job.cancel()
}
@Test
fun isKeyguardUnlocked() =
runTest(UnconfinedTestDispatcher()) {
whenever(keyguardStateController.isUnlocked).thenReturn(false)
var latest: Boolean? = null
val job = underTest.isKeyguardUnlocked.onEach { latest = it }.launchIn(this)
assertThat(latest).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
verify(keyguardStateController).addCallback(captor.capture())
whenever(keyguardStateController.isUnlocked).thenReturn(true)
captor.value.onUnlockedChanged()
assertThat(latest).isTrue()
whenever(keyguardStateController.isUnlocked).thenReturn(false)
captor.value.onUnlockedChanged()
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun isDozing() =
runTest(UnconfinedTestDispatcher()) {

View File

@@ -19,6 +19,8 @@ package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Interpolators
import com.android.systemui.flags.FakeFeatureFlags
@@ -40,6 +42,7 @@ import com.android.systemui.keyguard.util.KeyguardTransitionRunner
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.cancelChildren
@@ -51,6 +54,8 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
@@ -77,6 +82,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
// Used to verify transition requests for test output
@Mock private lateinit var mockTransitionRepository: KeyguardTransitionRepository
@Mock private lateinit var commandQueue: CommandQueue
@Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
private lateinit var fromDreamingTransitionInteractor: FromDreamingTransitionInteractor
@@ -102,6 +108,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
transitionRepository = KeyguardTransitionRepositoryImpl()
runner = KeyguardTransitionRunner(transitionRepository)
whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
val featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) }
fromLockscreenTransitionInteractor =
FromLockscreenTransitionInteractor(
@@ -173,16 +181,17 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
keyguardInteractor = createKeyguardInteractor(featureFlags),
keyguardTransitionRepository = mockTransitionRepository,
keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
keyguardSecurityModel = keyguardSecurityModel,
)
fromPrimaryBouncerTransitionInteractor.start()
}
@Test
fun `DREAMING to LOCKSCREEN - dreaming state changes first`() =
fun `DREAMING to LOCKSCREEN`() =
testScope.runTest {
// GIVEN a device is dreaming and occluded
// GIVEN a device is dreaming
keyguardRepository.setDreamingWithOverlay(true)
keyguardRepository.setKeyguardOccluded(true)
keyguardRepository.setWakefulnessModel(startingToWake())
runCurrent()
// GIVEN a prior transition has run to DREAMING
@@ -215,56 +224,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
verify(mockTransitionRepository).startTransition(capture())
}
// THEN a transition to BOUNCER should occur
assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
assertThat(info.animator).isNotNull()
coroutineContext.cancelChildren()
}
@Test
fun `DREAMING to LOCKSCREEN - occluded state changes first`() =
testScope.runTest {
// GIVEN a device is dreaming and occluded
keyguardRepository.setDreamingWithOverlay(true)
keyguardRepository.setKeyguardOccluded(true)
runCurrent()
// GIVEN a prior transition has run to DREAMING
runner.startTransition(
testScope,
TransitionInfo(
ownerName = "",
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.DREAMING,
animator =
ValueAnimator().apply {
duration = 10
interpolator = Interpolators.LINEAR
},
)
)
runCurrent()
reset(mockTransitionRepository)
// WHEN doze is complete
keyguardRepository.setDozeTransitionModel(
DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
)
// AND occluded has stopped
keyguardRepository.setKeyguardOccluded(false)
advanceUntilIdle()
// AND then dreaming has stopped
keyguardRepository.setDreamingWithOverlay(false)
advanceUntilIdle()
val info =
withArgCaptor<TransitionInfo> {
verify(mockTransitionRepository).startTransition(capture())
verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to BOUNCER should occur
assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
@@ -304,7 +264,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
verify(mockTransitionRepository).startTransition(capture())
verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to PRIMARY_BOUNCER should occur
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -345,7 +305,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
verify(mockTransitionRepository).startTransition(capture())
verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -386,7 +346,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
verify(mockTransitionRepository).startTransition(capture())
verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -427,7 +387,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
verify(mockTransitionRepository).startTransition(capture())
verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -468,7 +428,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
verify(mockTransitionRepository).startTransition(capture())
verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -505,7 +465,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
verify(mockTransitionRepository).startTransition(capture())
verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -542,7 +502,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
verify(mockTransitionRepository).startTransition(capture())
verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -583,7 +543,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
verify(mockTransitionRepository).startTransition(capture())
verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -624,7 +584,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
verify(mockTransitionRepository).startTransition(capture())
verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to AOD should occur
assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -661,7 +621,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
verify(mockTransitionRepository).startTransition(capture())
verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to AOD should occur
assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -677,6 +637,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
testScope.runTest {
// GIVEN a device that is not dreaming or dozing
keyguardRepository.setDreamingWithOverlay(false)
keyguardRepository.setWakefulnessModel(startingToWake())
keyguardRepository.setDozeTransitionModel(
DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
)
@@ -704,7 +665,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
verify(mockTransitionRepository).startTransition(capture())
verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to DREAMING should occur
assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -741,7 +702,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
verify(mockTransitionRepository).startTransition(capture())
verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to PRIMARY_BOUNCER should occur
assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -784,7 +745,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
verify(mockTransitionRepository).startTransition(capture())
verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to AOD should occur
assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -828,7 +789,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
verify(mockTransitionRepository).startTransition(capture())
verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -870,7 +831,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
verify(mockTransitionRepository).startTransition(capture())
verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to LOCKSCREEN should occur
assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -912,7 +873,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
verify(mockTransitionRepository).startTransition(capture())
verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to AOD should occur
assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -954,7 +915,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
verify(mockTransitionRepository).startTransition(capture())
verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -995,7 +956,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
verify(mockTransitionRepository).startTransition(capture())
verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to LOCKSCREEN should occur
assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")

View File

@@ -33,6 +33,7 @@ import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteracto
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.NotificationInsetsController
@@ -65,48 +66,32 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
@Mock
private lateinit var view: NotificationShadeWindowView
@Mock
private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
@Mock
private lateinit var centralSurfaces: CentralSurfaces
@Mock
private lateinit var dockManager: DockManager
@Mock
private lateinit var notificationPanelViewController: NotificationPanelViewController
@Mock
private lateinit var notificationShadeDepthController: NotificationShadeDepthController
@Mock
private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@Mock
private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
@Mock
private lateinit var ambientState: AmbientState
@Mock
private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
@Mock
private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
@Mock
private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@Mock
private lateinit var statusBarWindowStateController: StatusBarWindowStateController
@Mock private lateinit var view: NotificationShadeWindowView
@Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
@Mock private lateinit var centralSurfaces: CentralSurfaces
@Mock private lateinit var dockManager: DockManager
@Mock private lateinit var notificationPanelViewController: NotificationPanelViewController
@Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
@Mock private lateinit var ambientState: AmbientState
@Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
@Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
@Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController
@Mock
private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
@Mock
private lateinit var lockIconViewController: LockIconViewController
@Mock
private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController
@Mock
private lateinit var pulsingGestureListener: PulsingGestureListener
@Mock
private lateinit var notificationInsetsController: NotificationInsetsController
@Mock
private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
@Mock private lateinit var lockIconViewController: LockIconViewController
@Mock private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController
@Mock private lateinit var pulsingGestureListener: PulsingGestureListener
@Mock private lateinit var notificationInsetsController: NotificationInsetsController
@Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
@Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
@Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController
@Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock
lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel
private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
private lateinit var interactionEventHandler: InteractionEventHandler
@@ -118,43 +103,44 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
whenever(view.bottom).thenReturn(VIEW_BOTTOM)
whenever(view.findViewById<ViewGroup>(R.id.keyguard_bouncer_container))
.thenReturn(mock(ViewGroup::class.java))
.thenReturn(mock(ViewGroup::class.java))
whenever(keyguardBouncerComponentFactory.create(any(ViewGroup::class.java)))
.thenReturn(keyguardBouncerComponent)
.thenReturn(keyguardBouncerComponent)
whenever(keyguardBouncerComponent.securityContainerController)
.thenReturn(keyguardSecurityContainerController)
.thenReturn(keyguardSecurityContainerController)
whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
.thenReturn(emptyFlow<TransitionStep>())
underTest = NotificationShadeWindowViewController(
lockscreenShadeTransitionController,
FalsingCollectorFake(),
sysuiStatusBarStateController,
dockManager,
notificationShadeDepthController,
view,
notificationPanelViewController,
ShadeExpansionStateManager(),
stackScrollLayoutController,
statusBarKeyguardViewManager,
statusBarWindowStateController,
lockIconViewController,
centralSurfaces,
notificationShadeWindowController,
keyguardUnlockAnimationController,
notificationInsetsController,
ambientState,
pulsingGestureListener,
keyguardBouncerViewModel,
keyguardBouncerComponentFactory,
alternateBouncerInteractor,
keyguardTransitionInteractor,
)
.thenReturn(emptyFlow<TransitionStep>())
underTest =
NotificationShadeWindowViewController(
lockscreenShadeTransitionController,
FalsingCollectorFake(),
sysuiStatusBarStateController,
dockManager,
notificationShadeDepthController,
view,
notificationPanelViewController,
ShadeExpansionStateManager(),
stackScrollLayoutController,
statusBarKeyguardViewManager,
statusBarWindowStateController,
lockIconViewController,
centralSurfaces,
notificationShadeWindowController,
keyguardUnlockAnimationController,
notificationInsetsController,
ambientState,
pulsingGestureListener,
keyguardBouncerViewModel,
keyguardBouncerComponentFactory,
alternateBouncerInteractor,
keyguardTransitionInteractor,
primaryBouncerToGoneTransitionViewModel,
)
underTest.setupExpandedStatusBar()
interactionEventHandlerCaptor =
ArgumentCaptor.forClass(InteractionEventHandler::class.java)
interactionEventHandlerCaptor = ArgumentCaptor.forClass(InteractionEventHandler::class.java)
verify(view).setInteractionEventHandler(interactionEventHandlerCaptor.capture())
interactionEventHandler = interactionEventHandlerCaptor.value
interactionEventHandler = interactionEventHandlerCaptor.value
}
// Note: So far, these tests only cover interactions with the status bar view controller. More
@@ -184,14 +170,11 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
@Test
fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() {
underTest.setStatusBarViewController(phoneStatusBarViewController)
val downEvBelow = MotionEvent.obtain(
0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0
)
val downEvBelow =
MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0)
interactionEventHandler.handleDispatchTouchEvent(downEvBelow)
val nextEvent = MotionEvent.obtain(
0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0
)
val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0)
whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true)
val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent)

View File

@@ -46,6 +46,7 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationInsetsController;
@@ -101,6 +102,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase {
@Mock private NotificationInsetsController mNotificationInsetsController;
@Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
@Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
@Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler>
mInteractionEventHandlerCaptor;
@@ -150,7 +152,8 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase {
mKeyguardBouncerViewModel,
mKeyguardBouncerComponentFactory,
mAlternateBouncerInteractor,
mKeyguardTransitionInteractor
mKeyguardTransitionInteractor,
mPrimaryBouncerToGoneTransitionViewModel
);
mController.setupExpandedStatusBar();
mController.setDragDownHelper(mDragDownHelper);

View File

@@ -24,6 +24,8 @@ import static com.android.systemui.statusbar.phone.ScrimState.SHADE_LOCKED;
import static com.google.common.truth.Truth.assertThat;
import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -58,7 +60,12 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
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.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.statusbar.policy.FakeConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -85,8 +92,10 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import kotlinx.coroutines.CoroutineDispatcher;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
public class ScrimControllerTest extends SysuiTestCase {
@@ -115,6 +124,10 @@ public class ScrimControllerTest extends SysuiTestCase {
@Mock private DockManager mDockManager;
@Mock private ScreenOffAnimationController mScreenOffAnimationController;
@Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
@Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@Mock private CoroutineDispatcher mMainDispatcher;
// TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The
// event-dispatch-on-registration pattern caused some of these unit tests to fail.)
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -225,13 +238,20 @@ public class ScrimControllerTest extends SysuiTestCase {
when(mDelayedWakeLockBuilder.build()).thenReturn(mWakeLock);
when(mDockManager.isDocked()).thenReturn(false);
when(mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition())
.thenReturn(emptyFlow());
when(mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha()).thenReturn(emptyFlow());
mScrimController = new ScrimController(mLightBarController,
mDozeParameters, mAlarmManager, mKeyguardStateController, mDelayedWakeLockBuilder,
new FakeHandler(mLooper.getLooper()), mKeyguardUpdateMonitor,
mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
mStatusBarKeyguardViewManager);
mStatusBarKeyguardViewManager,
mPrimaryBouncerToGoneTransitionViewModel,
mKeyguardTransitionInteractor,
mMainDispatcher);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
@@ -861,7 +881,10 @@ public class ScrimControllerTest extends SysuiTestCase {
mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
mStatusBarKeyguardViewManager);
mStatusBarKeyguardViewManager,
mPrimaryBouncerToGoneTransitionViewModel,
mKeyguardTransitionInteractor,
mMainDispatcher);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
@@ -1629,6 +1652,18 @@ public class ScrimControllerTest extends SysuiTestCase {
assertScrimAlpha(mScrimBehind, 0);
}
@Test
public void ignoreTransitionRequestWhileKeyguardTransitionRunning() {
mScrimController.transitionTo(ScrimState.UNLOCKED);
mScrimController.mPrimaryBouncerToGoneTransition.accept(
new TransitionStep(KeyguardState.PRIMARY_BOUNCER, KeyguardState.GONE, 0f,
TransitionState.RUNNING, "ScrimControllerTest"));
// This request should not happen
mScrimController.transitionTo(ScrimState.BOUNCER);
assertThat(mScrimController.getState()).isEqualTo(ScrimState.UNLOCKED);
}
private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) {
mScrimController.setRawPanelExpansionFraction(expansion);
finishAnimationsImmediately();

View File

@@ -47,6 +47,9 @@ class FakeKeyguardRepository : KeyguardRepository {
private val _isKeyguardShowing = MutableStateFlow(false)
override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
private val _isKeyguardUnlocked = MutableStateFlow(false)
override val isKeyguardUnlocked: Flow<Boolean> = _isKeyguardUnlocked
private val _isKeyguardOccluded = MutableStateFlow(false)
override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded

View File

@@ -37,7 +37,7 @@ class FakeKeyguardTransitionRepository : KeyguardTransitionRepository {
_transitions.emit(step)
}
override fun startTransition(info: TransitionInfo): UUID? {
override fun startTransition(info: TransitionInfo, resetIfCanceled: Boolean): UUID? {
return null
}