Refactor SysUI's interface with ATMS#setLockScreenShown/keyguardGoingAway.

Bug: 278086361
Test: atest SystemUITests
Merged-In: Iaacccacbbc408bf5d39b32016b981199aa36dcc0
Change-Id: Iaacccacbbc408bf5d39b32016b981199aa36dcc0
This commit is contained in:
Josh Tsuji
2023-07-31 16:53:14 -04:00
parent 00c31ff2ed
commit b1b85467e8
48 changed files with 2985 additions and 159 deletions

View File

@@ -78,6 +78,7 @@ import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
@@ -126,6 +127,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
private final BouncerMessageInteractor mBouncerMessageInteractor;
private int mTranslationY;
private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
// Whether the volume keys should be handled by keyguard. If true, then
// they will be handled here for specific media types such as music, otherwise
// the audio service will bring up the volume dialog.
@@ -299,6 +301,10 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mViewMediatorCallback.keyguardDone(fromPrimaryAuth, targetUserId);
}
}
if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
mKeyguardTransitionInteractor.startDismissKeyguardTransition();
}
}
@Override
@@ -424,6 +430,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
Provider<JavaAdapter> javaAdapter,
UserInteractor userInteractor,
FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
KeyguardTransitionInteractor keyguardTransitionInteractor,
Provider<AuthenticationInteractor> authenticationInteractor
) {
super(view);
@@ -455,6 +462,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mUserInteractor = userInteractor;
mAuthenticationInteractor = authenticationInteractor;
mJavaAdapter = javaAdapter;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
}
@Override

View File

@@ -302,6 +302,19 @@ object Flags {
R.bool.flag_stop_pulsing_face_scanning_animation,
"stop_pulsing_face_scanning_animation")
/**
* TODO(b/278086361): Tracking bug
* Complete rewrite of the interactions between System UI and Window Manager involving keyguard
* state. When enabled, calls to ActivityTaskManagerService from System UI will exclusively
* occur from [WmLockscreenVisibilityManager] rather than the legacy KeyguardViewMediator.
*
* This flag is under development; some types of unlock may not animate properly if you enable
* it.
*/
@JvmField
val KEYGUARD_WM_STATE_REFACTOR: UnreleasedFlag =
unreleasedFlag("keyguard_wm_state_refactor")
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag("power_menu_lite")

View File

@@ -73,6 +73,14 @@ import com.android.internal.policy.IKeyguardService;
import com.android.internal.policy.IKeyguardStateCallback;
import com.android.keyguard.mediator.ScreenOnCoordinator;
import com.android.systemui.SystemUIApplication;
import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier;
import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindViewBinder;
import com.android.systemui.keyguard.ui.binder.WindowManagerLockscreenVisibilityViewBinder;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSurfaceBehindViewModel;
import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibilityViewModel;
import com.android.systemui.settings.DisplayTracker;
import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
@@ -85,10 +93,13 @@ import java.util.WeakHashMap;
import javax.inject.Inject;
import kotlinx.coroutines.CoroutineScope;
public class KeyguardService extends Service {
static final String TAG = "KeyguardService";
static final String PERMISSION = android.Manifest.permission.CONTROL_KEYGUARD;
private final FeatureFlags mFlags;
private final KeyguardViewMediator mKeyguardViewMediator;
private final KeyguardLifecyclesDispatcher mKeyguardLifecyclesDispatcher;
private final ScreenOnCoordinator mScreenOnCoordinator;
@@ -291,13 +302,33 @@ public class KeyguardService extends Service {
KeyguardLifecyclesDispatcher keyguardLifecyclesDispatcher,
ScreenOnCoordinator screenOnCoordinator,
ShellTransitions shellTransitions,
DisplayTracker displayTracker) {
DisplayTracker displayTracker,
WindowManagerLockscreenVisibilityViewModel
wmLockscreenVisibilityViewModel,
WindowManagerLockscreenVisibilityManager wmLockscreenVisibilityManager,
KeyguardSurfaceBehindViewModel keyguardSurfaceBehindViewModel,
KeyguardSurfaceBehindParamsApplier keyguardSurfaceBehindAnimator,
@Application CoroutineScope scope,
FeatureFlags featureFlags) {
super();
mKeyguardViewMediator = keyguardViewMediator;
mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
mScreenOnCoordinator = screenOnCoordinator;
mShellTransitions = shellTransitions;
mDisplayTracker = displayTracker;
mFlags = featureFlags;
if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
WindowManagerLockscreenVisibilityViewBinder.bind(
wmLockscreenVisibilityViewModel,
wmLockscreenVisibilityManager,
scope);
KeyguardSurfaceBehindViewBinder.bind(
keyguardSurfaceBehindViewModel,
keyguardSurfaceBehindAnimator,
scope);
}
}
@Override

View File

@@ -405,7 +405,9 @@ class KeyguardUnlockAnimationController @Inject constructor(
* the device.
*/
fun canPerformInWindowLauncherAnimations(): Boolean {
return isNexusLauncherUnderneath() &&
// TODO(b/278086361): Refactor in-window animations.
return !featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR) &&
isNexusLauncherUnderneath() &&
// If the launcher is underneath, but we're about to launch an activity, don't do
// the animations since they won't be visible.
!notificationShadeWindowController.isLaunchingActivity &&
@@ -849,54 +851,57 @@ class KeyguardUnlockAnimationController @Inject constructor(
}
surfaceBehindRemoteAnimationTargets?.forEach { surfaceBehindRemoteAnimationTarget ->
val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
val surfaceHeight: Int =
surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
(1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
MathUtils.clamp(amount, 0f, 1f))
var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
(1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
MathUtils.clamp(amount, 0f, 1f))
// If we're dismissing via swipe to the Launcher, we'll play in-window scale animations,
// so don't also scale the window.
if (keyguardStateController.isDismissingFromSwipe &&
willUnlockWithInWindowLauncherAnimations) {
scaleFactor = 1f
}
// Translate up from the bottom.
surfaceBehindMatrix.setTranslate(
surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
)
// Scale up from a point at the center-bottom of the surface.
surfaceBehindMatrix.postScale(
scaleFactor,
scaleFactor,
keyguardViewController.viewRootImpl.width / 2f,
surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
)
// SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
// unable to draw
val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash
if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
sc?.isValid == true) {
with(SurfaceControl.Transaction()) {
setMatrix(sc, surfaceBehindMatrix, tmpFloat)
setCornerRadius(sc, roundedCornerRadius)
setAlpha(sc, animationAlpha)
apply()
// If we're dismissing via swipe to the Launcher, we'll play in-window scale
// animations, so don't also scale the window.
if (keyguardStateController.isDismissingFromSwipe &&
willUnlockWithInWindowLauncherAnimations) {
scaleFactor = 1f
}
} else {
applyParamsToSurface(
SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
surfaceBehindRemoteAnimationTarget.leash)
.withMatrix(surfaceBehindMatrix)
.withCornerRadius(roundedCornerRadius)
.withAlpha(animationAlpha)
.build()
// Translate up from the bottom.
surfaceBehindMatrix.setTranslate(
surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
)
// Scale up from a point at the center-bottom of the surface.
surfaceBehindMatrix.postScale(
scaleFactor,
scaleFactor,
keyguardViewController.viewRootImpl.width / 2f,
surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
)
// SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
// unable to draw
val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash
if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
sc?.isValid == true) {
with(SurfaceControl.Transaction()) {
setMatrix(sc, surfaceBehindMatrix, tmpFloat)
setCornerRadius(sc, roundedCornerRadius)
setAlpha(sc, animationAlpha)
apply()
}
} else {
applyParamsToSurface(
SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
surfaceBehindRemoteAnimationTarget.leash)
.withMatrix(surfaceBehindMatrix)
.withCornerRadius(roundedCornerRadius)
.withAlpha(animationAlpha)
.build()
)
}
}
}
@@ -985,10 +990,12 @@ class KeyguardUnlockAnimationController @Inject constructor(
if (keyguardStateController.isShowing) {
// Hide the keyguard, with no fade out since we animated it away during the unlock.
keyguardViewController.hide(
surfaceBehindRemoteAnimationStartTime,
0 /* fadeOutDuration */
)
if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
keyguardViewController.hide(
surfaceBehindRemoteAnimationStartTime,
0 /* fadeOutDuration */
)
}
} else {
Log.i(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
"showing. Ignoring...")

View File

@@ -171,8 +171,6 @@ import com.android.systemui.util.time.SystemClock;
import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
import dagger.Lazy;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -182,6 +180,7 @@ import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import dagger.Lazy;
import kotlinx.coroutines.CoroutineDispatcher;
/**
@@ -1035,12 +1034,19 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
IRemoteAnimationFinishedCallback finishedCallback) {
Trace.beginSection("mExitAnimationRunner.onAnimationStart#startKeyguardExitAnimation");
startKeyguardExitAnimation(transit, apps, wallpapers, nonApps, finishedCallback);
if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationStart(
transit, apps, wallpapers, nonApps, finishedCallback);
}
Trace.endSection();
}
@Override // Binder interface
public void onAnimationCancelled() {
cancelKeyguardExitAnimation();
if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationCancelled();
}
}
};
@@ -1106,7 +1112,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mOccludeByDreamAnimator = ValueAnimator.ofFloat(0f, 1f);
mOccludeByDreamAnimator.setDuration(mDreamOpenAnimationDuration);
mOccludeByDreamAnimator.setInterpolator(Interpolators.LINEAR);
//mOccludeByDreamAnimator.setInterpolator(Interpolators.LINEAR);
mOccludeByDreamAnimator.addUpdateListener(
animation -> {
SyncRtSurfaceTransactionApplier.SurfaceParams.Builder
@@ -1335,6 +1341,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mDreamingToLockscreenTransitionViewModel;
private RemoteAnimationTarget mRemoteAnimationTarget;
private Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
/**
* Injected constructor. See {@link KeyguardModule}.
*/
@@ -1377,7 +1385,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
SystemClock systemClock,
@Main CoroutineDispatcher mainDispatcher,
Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
SystemPropertiesHelper systemPropertiesHelper) {
SystemPropertiesHelper systemPropertiesHelper,
Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager) {
mContext = context;
mUserTracker = userTracker;
mFalsingCollector = falsingCollector;
@@ -1443,8 +1452,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mUiEventLogger = uiEventLogger;
mSessionTracker = sessionTracker;
mMainDispatcher = mainDispatcher;
mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel;
mWmLockscreenVisibilityManager = wmLockscreenVisibilityManager;
mMainDispatcher = mainDispatcher;
}
public void userActivity() {
@@ -2677,6 +2687,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
if (DEBUG) {
Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ")");
}
if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
// Handled in WmLockscreenVisibilityManager if flag is enabled.
return;
}
try {
ActivityTaskManager.getService().setLockScreenShown(showing, aodShowing);
} catch (RemoteException e) {
@@ -2716,7 +2732,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
mHiding = false;
mKeyguardViewControllerLazy.get().show(options);
if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
// Handled directly in StatusBarKeyguardViewManager if enabled.
mKeyguardViewControllerLazy.get().show(options);
}
resetKeyguardDonePendingLocked();
mHideAnimationRun = false;
adjustStatusBarLocked();
@@ -2787,19 +2807,22 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mUpdateMonitor.setKeyguardGoingAway(true);
mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(true);
// Don't actually hide the Keyguard at the moment, wait for window
// manager until it tells us it's safe to do so with
// startKeyguardExitAnimation.
// Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager will be in
// order.
final int keyguardFlag = flags;
mUiBgExecutor.execute(() -> {
try {
ActivityTaskManager.getService().keyguardGoingAway(keyguardFlag);
} catch (RemoteException e) {
Log.e(TAG, "Error while calling WindowManager", e);
}
});
// Handled in WmLockscreenVisibilityManager if flag is enabled.
if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
// Don't actually hide the Keyguard at the moment, wait for window manager until it
// tells us it's safe to do so with startKeyguardExitAnimation.
// Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager will be
// in order.
final int keyguardFlag = flags;
mUiBgExecutor.execute(() -> {
try {
ActivityTaskManager.getService().keyguardGoingAway(keyguardFlag);
} catch (RemoteException e) {
Log.e(TAG, "Error while calling WindowManager", e);
}
});
}
Trace.endSection();
}
};
@@ -2913,7 +2936,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
if (!mHiding
&& !mSurfaceBehindRemoteAnimationRequested
&& !mKeyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture()) {
if (finishedCallback != null) {
// If the flag is enabled, remote animation state is handled in
// WmLockscreenVisibilityManager.
if (finishedCallback != null
&& !mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
// There will not execute animation, send a finish callback to ensure the remote
// animation won't hang there.
try {
@@ -2939,10 +2965,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
new IRemoteAnimationFinishedCallback() {
@Override
public void onAnimationFinished() throws RemoteException {
try {
finishedCallback.onAnimationFinished();
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call onAnimationFinished", e);
if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
try {
finishedCallback.onAnimationFinished();
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call onAnimationFinished", e);
}
}
onKeyguardExitFinished();
mKeyguardViewControllerLazy.get().hide(0 /* startTime */,
@@ -2969,7 +2997,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
// it will dismiss the panel in that case.
} else if (!mStatusBarStateController.leaveOpenOnKeyguardHide()
&& apps != null && apps.length > 0) {
mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
// Handled in WmLockscreenVisibilityManager. Other logic in this class will
// short circuit when this is null.
mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
}
mSurfaceBehindRemoteAnimationRunning = true;
mInteractionJankMonitor.begin(
@@ -2989,7 +3021,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
createInteractionJankMonitorConf(
CUJ_LOCKSCREEN_UNLOCK_ANIMATION, "RemoteAnimationDisabled"));
mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
// Handled directly in StatusBarKeyguardViewManager if enabled.
mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
}
// TODO(bc-animation): When remote animation is enabled for keyguard exit animation,
// apps, wallpapers and finishedCallback are set to non-null. nonApps is not yet
@@ -3003,13 +3038,17 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
if (apps == null || apps.length == 0) {
Slog.e(TAG, "Keyguard exit without a corresponding app to show.");
try {
finishedCallback.onAnimationFinished();
if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
finishedCallback.onAnimationFinished();
}
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException");
} finally {
mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
}
return;
}
@@ -3033,7 +3072,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
@Override
public void onAnimationEnd(Animator animation) {
try {
finishedCallback.onAnimationFinished();
if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
finishedCallback.onAnimationFinished();
}
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException");
} finally {
@@ -3044,7 +3085,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
@Override
public void onAnimationCancel(Animator animation) {
try {
finishedCallback.onAnimationFinished();
if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
finishedCallback.onAnimationFinished();
}
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException");
} finally {
@@ -3193,7 +3236,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
}
ActivityTaskManager.getService().keyguardGoingAway(flags);
if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
// Handled in WmLockscreenVisibilityManager.
ActivityTaskManager.getService().keyguardGoingAway(flags);
}
mKeyguardStateController.notifyKeyguardGoingAway(true);
} catch (RemoteException e) {
mSurfaceBehindRemoteAnimationRequested = false;

View File

@@ -0,0 +1,206 @@
/*
* 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
import android.app.IActivityTaskManager
import android.util.Log
import android.view.IRemoteAnimationFinishedCallback
import android.view.RemoteAnimationTarget
import android.view.WindowManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier
import com.android.systemui.statusbar.policy.KeyguardStateController
import java.util.concurrent.Executor
import javax.inject.Inject
/**
* Manages lockscreen and AOD visibility state via the [IActivityTaskManager], and keeps track of
* remote animations related to changes in lockscreen visibility.
*/
@SysUISingleton
class WindowManagerLockscreenVisibilityManager
@Inject
constructor(
@Main private val executor: Executor,
private val activityTaskManagerService: IActivityTaskManager,
private val keyguardStateController: KeyguardStateController,
private val keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier,
) {
/**
* Whether the lockscreen is showing, which we pass to [IActivityTaskManager.setLockScreenShown]
* in order to show the lockscreen and hide the surface behind the keyguard (or the inverse).
*/
private var isLockscreenShowing = true
/**
* Whether AOD is showing, which we pass to [IActivityTaskManager.setLockScreenShown] in order
* to show AOD when the lockscreen is visible.
*/
private var isAodVisible = false
/**
* Whether the keyguard is currently "going away", which we triggered via a call to
* [IActivityTaskManager.keyguardGoingAway]. When we tell WM that the keyguard is going away,
* the app/launcher surface behind the keyguard is made visible, and WM calls
* [onKeyguardGoingAwayRemoteAnimationStart] with a RemoteAnimationTarget so that we can animate
* it.
*
* Going away does not inherently result in [isLockscreenShowing] being set to false; we need to
* do that ourselves once we are done animating the surface.
*
* THIS IS THE ONLY PLACE 'GOING AWAY' TERMINOLOGY SHOULD BE USED. 'Going away' is a WM concept
* and we have gotten into trouble using it to mean various different things in the past. Unlock
* animations may still be visible when the keyguard is NOT 'going away', for example, when we
* play in-window animations, we set the surface to alpha=1f and end the animation immediately.
* The remainder of the animation occurs in-window, so while you might expect that the keyguard
* is still 'going away' because unlock animations are playing, it's actually not.
*
* If you want to know if the keyguard is 'going away', you probably want to check if we have
* STARTED but not FINISHED a transition to GONE.
*
* The going away animation will run until:
* - We manually call [endKeyguardGoingAwayAnimation] after we're done animating.
* - We call [setLockscreenShown] = true, which cancels the going away animation.
* - WM calls [onKeyguardGoingAwayRemoteAnimationCancelled] for another reason (such as the 10
* second timeout).
*/
private var isKeyguardGoingAway = false
private set(value) {
// TODO(b/278086361): Extricate the keyguard state controller.
keyguardStateController.notifyKeyguardGoingAway(value)
field = value
}
/** Callback provided by WM to call once we're done with the going away animation. */
private var goingAwayRemoteAnimationFinishedCallback: IRemoteAnimationFinishedCallback? = null
/**
* Set the visibility of the surface behind the keyguard, making the appropriate calls to Window
* Manager to effect the change.
*/
fun setSurfaceBehindVisibility(visible: Boolean) {
if (isKeyguardGoingAway == visible) {
Log.d(TAG, "WmLockscreenVisibilityManager#setVisibility -> already visible=$visible")
return
}
// The surface behind is always visible if the lockscreen is not showing, so we're already
// visible.
if (visible && !isLockscreenShowing) {
Log.d(TAG, "#setVisibility -> already visible since the lockscreen isn't showing")
return
}
if (visible) {
// Make the surface visible behind the keyguard by calling keyguardGoingAway. The
// lockscreen is still showing as well, allowing us to animate unlocked.
Log.d(TAG, "ActivityTaskManagerService#keyguardGoingAway()")
activityTaskManagerService.keyguardGoingAway(0)
isKeyguardGoingAway = true
} else {
// Hide the surface by setting the lockscreen showing.
setLockscreenShown(true)
}
}
fun setAodVisible(aodVisible: Boolean) {
setWmLockscreenState(aodVisible = aodVisible)
}
/** Sets the visibility of the lockscreen. */
fun setLockscreenShown(lockscreenShown: Boolean) {
setWmLockscreenState(lockscreenShowing = lockscreenShown)
}
fun onKeyguardGoingAwayRemoteAnimationStart(
@WindowManager.TransitionOldType transit: Int,
apps: Array<RemoteAnimationTarget>,
wallpapers: Array<RemoteAnimationTarget>,
nonApps: Array<RemoteAnimationTarget>,
finishedCallback: IRemoteAnimationFinishedCallback
) {
goingAwayRemoteAnimationFinishedCallback = finishedCallback
keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0])
}
fun onKeyguardGoingAwayRemoteAnimationCancelled() {
// If WM cancelled the animation, we need to end immediately even if we're still using the
// animation.
endKeyguardGoingAwayAnimation()
}
/**
* Whether the going away remote animation target is in-use, which means we're animating it or
* intend to animate it.
*
* Some unlock animations (such as the translation spring animation) are non-deterministic and
* might end after the transition to GONE ends. In that case, we want to keep the remote
* animation running until the spring ends.
*/
fun setUsingGoingAwayRemoteAnimation(usingTarget: Boolean) {
if (!usingTarget) {
endKeyguardGoingAwayAnimation()
}
}
private fun setWmLockscreenState(
lockscreenShowing: Boolean = this.isLockscreenShowing,
aodVisible: Boolean = this.isAodVisible
) {
Log.d(
TAG,
"#setWmLockscreenState(" +
"isLockscreenShowing=$lockscreenShowing, " +
"aodVisible=$aodVisible)."
)
if (this.isLockscreenShowing == lockscreenShowing && this.isAodVisible == aodVisible) {
return
}
activityTaskManagerService.setLockScreenShown(lockscreenShowing, aodVisible)
this.isLockscreenShowing = lockscreenShowing
this.isAodVisible = aodVisible
}
private fun endKeyguardGoingAwayAnimation() {
if (!isKeyguardGoingAway) {
Log.d(
TAG,
"#endKeyguardGoingAwayAnimation() called when isKeyguardGoingAway=false. " +
"Short-circuiting."
)
return
}
executor.execute {
Log.d(TAG, "Finishing remote animation.")
goingAwayRemoteAnimationFinishedCallback?.onAnimationFinished()
goingAwayRemoteAnimationFinishedCallback = null
isKeyguardGoingAway = false
keyguardSurfaceBehindAnimator.notifySurfaceReleased()
}
}
companion object {
private val TAG = this::class.java.simpleName
}
}

View File

@@ -46,6 +46,7 @@ import com.android.systemui.flags.SystemPropertiesHelper;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager;
import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule;
import com.android.systemui.keyguard.data.repository.KeyguardFaceAuthModule;
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
@@ -73,12 +74,11 @@ import com.android.systemui.util.time.SystemClock;
import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
import java.util.concurrent.Executor;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
import java.util.concurrent.Executor;
import kotlinx.coroutines.CoroutineDispatcher;
/**
@@ -144,7 +144,8 @@ public class KeyguardModule {
SystemClock systemClock,
@Main CoroutineDispatcher mainDispatcher,
Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
SystemPropertiesHelper systemPropertiesHelper) {
SystemPropertiesHelper systemPropertiesHelper,
Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager) {
return new KeyguardViewMediator(
context,
uiEventLogger,
@@ -186,7 +187,8 @@ public class KeyguardModule {
systemClock,
mainDispatcher,
dreamingToLockscreenTransitionViewModel,
systemPropertiesHelper);
systemPropertiesHelper,
wmLockscreenVisibilityManager);
}
/** */

View File

@@ -46,6 +46,7 @@ import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.log.FaceAuthenticationLogger
@@ -160,7 +161,7 @@ constructor(
@FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
@FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
featureFlags: FeatureFlags,
private val featureFlags: FeatureFlags,
facePropertyRepository: FacePropertyRepository,
dumpManager: DumpManager,
) : DeviceEntryFaceAuthRepository, Dumpable {
@@ -286,8 +287,12 @@ constructor(
// starts going to sleep.
merge(
keyguardRepository.wakefulness.map { it.isStartingToSleepOrAsleep() },
keyguardRepository.isKeyguardGoingAway,
userRepository.userSwitchingInProgress
if (featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE)
} else {
keyguardRepository.isKeyguardGoingAway
},
userRepository.userSwitchingInProgress,
)
.onEach { anyOfThemIsTrue ->
if (anyOfThemIsTrue) {

View File

@@ -99,7 +99,16 @@ interface KeyguardRepository {
/** Is an activity showing over the keyguard? */
val isKeyguardOccluded: Flow<Boolean>
/** Observable for the signal that keyguard is about to go away. */
/**
* Observable for the signal that keyguard is about to go away.
*
* TODO(b/278086361): Remove once KEYGUARD_WM_STATE_REFACTOR flag is removed.
*/
@Deprecated(
"Use KeyguardTransitionInteractor flows instead. The closest match for 'going " +
"away' is isInTransitionToState(GONE), but consider using more specific flows " +
"whenever possible."
)
val isKeyguardGoingAway: Flow<Boolean>
/** Is the always-on display available to be used? */
@@ -365,10 +374,11 @@ constructor(
awaitClose { keyguardStateController.removeCallback(callback) }
}
.distinctUntilChanged()
.stateIn(
scope = scope,
started = SharingStarted.WhileSubscribed(),
initialValue = keyguardStateController.isUnlocked,
scope,
SharingStarted.Eagerly,
initialValue = false,
)
override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow {

View File

@@ -31,6 +31,11 @@ import dagger.multibindings.IntoMap
interface KeyguardRepositoryModule {
@Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
@Binds
fun keyguardSurfaceBehindRepository(
impl: KeyguardSurfaceBehindRepositoryImpl
): KeyguardSurfaceBehindRepository
@Binds
fun keyguardTransitionRepository(
impl: KeyguardTransitionRepositoryImpl

View File

@@ -0,0 +1,46 @@
/*
* 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.data.repository
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
/**
* State related to SysUI's handling of the surface behind the keyguard (typically an app or the
* launcher). We manipulate this surface during unlock animations.
*/
interface KeyguardSurfaceBehindRepository {
/** Whether we're running animations on the surface. */
val isAnimatingSurface: Flow<Boolean>
/** Set whether we're running animations on the surface. */
fun setAnimatingSurface(animating: Boolean)
}
@SysUISingleton
class KeyguardSurfaceBehindRepositoryImpl @Inject constructor() : KeyguardSurfaceBehindRepository {
private val _isAnimatingSurface = MutableStateFlow(false)
override val isAnimatingSurface = _isAnimatingSurface.asStateFlow()
override fun setAnimatingSurface(animating: Boolean) {
_isAnimatingSurface.value = animating
}
}

View File

@@ -17,7 +17,6 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
@@ -26,6 +25,7 @@ import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.Utils.Companion.toQuint
import com.android.systemui.util.kotlin.sample
import com.android.wm.shell.animation.Interpolators
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay

View File

@@ -20,8 +20,11 @@ import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -34,7 +37,11 @@ import java.util.UUID
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
@SysUISingleton
@@ -45,6 +52,7 @@ constructor(
override val transitionInteractor: KeyguardTransitionInteractor,
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
private val flags: FeatureFlags,
private val shadeRepository: ShadeRepository,
) :
TransitionInteractor(
@@ -53,6 +61,7 @@ constructor(
override fun start() {
listenForLockscreenToGone()
listenForLockscreenToGoneDragging()
listenForLockscreenToOccluded()
listenForLockscreenToCamera()
listenForLockscreenToAodOrDozing()
@@ -62,6 +71,63 @@ constructor(
listenForLockscreenToAlternateBouncer()
}
/**
* Whether we want the surface behind the keyguard visible for the transition from LOCKSCREEN,
* or null if we don't care and should just use a reasonable default.
*
* [KeyguardSurfaceBehindInteractor] will switch to this flow whenever a transition from
* LOCKSCREEN is running.
*/
val surfaceBehindVisibility: Flow<Boolean?> =
transitionInteractor.startedKeyguardTransitionStep
.map { startedStep ->
if (startedStep.to != KeyguardState.GONE) {
// LOCKSCREEN to anything but GONE does not require any special surface
// visibility handling.
return@map null
}
true // TODO(b/278086361): Implement continuous swipe to unlock.
}
.onStart {
// Default to null ("don't care, use a reasonable default").
emit(null)
}
.distinctUntilChanged()
/**
* The surface behind view params to use for the transition from LOCKSCREEN, or null if we don't
* care and should use a reasonable default.
*/
val surfaceBehindModel: Flow<KeyguardSurfaceBehindModel?> =
combine(
transitionInteractor.startedKeyguardTransitionStep,
transitionInteractor.transitionStepsFromState(KeyguardState.LOCKSCREEN)
) { startedStep, fromLockscreenStep ->
if (startedStep.to != KeyguardState.GONE) {
// Only LOCKSCREEN -> GONE has specific surface params (for the unlock
// animation).
return@combine null
} else if (fromLockscreenStep.value > 0.5f) {
// Start the animation once we're 50% transitioned to GONE.
KeyguardSurfaceBehindModel(
animateFromAlpha = 0f,
alpha = 1f,
animateFromTranslationY = 500f,
translationY = 0f
)
} else {
KeyguardSurfaceBehindModel(
alpha = 0f,
)
}
}
.onStart {
// Default to null ("don't care, use a reasonable default").
emit(null)
}
.distinctUntilChanged()
private fun listenForLockscreenToDreaming() {
val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING)
scope.launch {
@@ -169,7 +235,8 @@ constructor(
}
// If canceled, just put the state back
// TODO: This logic should happen in FromPrimaryBouncerInteractor.
// TODO(b/278086361): This logic should happen in
// FromPrimaryBouncerInteractor.
if (nextState == TransitionState.CANCELED) {
transitionRepository.startTransition(
TransitionInfo(
@@ -201,7 +268,32 @@ constructor(
}
}
fun dismissKeyguard() {
startTransitionTo(KeyguardState.GONE)
}
private fun listenForLockscreenToGone() {
if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
return
}
scope.launch {
keyguardInteractor.isKeyguardGoingAway
.sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (isKeyguardGoingAway, lastStartedStep) = pair
if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
startTransitionTo(KeyguardState.GONE)
}
}
}
}
private fun listenForLockscreenToGoneDragging() {
if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
return
}
scope.launch {
keyguardInteractor.isKeyguardGoingAway
.sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
@@ -291,7 +383,7 @@ constructor(
}
companion object {
private val DEFAULT_DURATION = 500.milliseconds
private val DEFAULT_DURATION = 400.milliseconds
val TO_DREAMING_DURATION = 933.milliseconds
val TO_OCCLUDED_DURATION = 450.milliseconds
}

View File

@@ -17,23 +17,28 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Password
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.Utils.Companion.toQuint
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import com.android.wm.shell.animation.Interpolators
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
@SysUISingleton
@@ -44,6 +49,7 @@ constructor(
override val transitionInteractor: KeyguardTransitionInteractor,
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
private val flags: FeatureFlags,
private val keyguardSecurityModel: KeyguardSecurityModel,
) :
TransitionInteractor(
@@ -57,6 +63,57 @@ constructor(
listenForPrimaryBouncerToDreamingLockscreenHosted()
}
val surfaceBehindVisibility: Flow<Boolean?> =
combine(
transitionInteractor.startedKeyguardTransitionStep,
transitionInteractor.transitionStepsFromState(KeyguardState.PRIMARY_BOUNCER)
) { startedStep, fromBouncerStep ->
if (startedStep.to != KeyguardState.GONE) {
return@combine null
}
fromBouncerStep.value > 0.5f
}
.onStart {
// Default to null ("don't care, use a reasonable default").
emit(null)
}
.distinctUntilChanged()
val surfaceBehindModel: Flow<KeyguardSurfaceBehindModel?> =
combine(
transitionInteractor.startedKeyguardTransitionStep,
transitionInteractor.transitionStepsFromState(KeyguardState.PRIMARY_BOUNCER)
) { startedStep, fromBouncerStep ->
if (startedStep.to != KeyguardState.GONE) {
// BOUNCER to anything but GONE does not require any special surface
// visibility handling.
return@combine null
}
if (fromBouncerStep.value > 0.5f) {
KeyguardSurfaceBehindModel(
animateFromAlpha = 0f,
alpha = 1f,
animateFromTranslationY = 500f,
translationY = 0f,
)
} else {
KeyguardSurfaceBehindModel(
alpha = 0f,
)
}
}
.onStart {
// Default to null ("don't care, use a reasonable default").
emit(null)
}
.distinctUntilChanged()
fun dismissPrimaryBouncer() {
startTransitionTo(KeyguardState.GONE)
}
private fun listenForPrimaryBouncerToLockscreenOrOccluded() {
scope.launch {
keyguardInteractor.primaryBouncerShowing
@@ -124,28 +181,34 @@ constructor(
private fun listenForPrimaryBouncerToDreamingLockscreenHosted() {
scope.launch {
keyguardInteractor.primaryBouncerShowing
.sample(
combine(
keyguardInteractor.isActiveDreamLockscreenHosted,
transitionInteractor.startedKeyguardTransitionStep,
::Pair
),
::toTriple
)
.collect {
(isBouncerShowing, isActiveDreamLockscreenHosted, lastStartedTransitionStep) ->
if (
!isBouncerShowing &&
isActiveDreamLockscreenHosted &&
lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
) {
startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
.sample(
combine(
keyguardInteractor.isActiveDreamLockscreenHosted,
transitionInteractor.startedKeyguardTransitionStep,
::Pair
),
::toTriple
)
.collect { (isBouncerShowing, isActiveDreamLockscreenHosted, lastStartedTransitionStep) ->
if (
!isBouncerShowing &&
isActiveDreamLockscreenHosted &&
lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
) {
startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
}
}
}
}
}
private fun listenForPrimaryBouncerToGone() {
if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
// This is handled in KeyguardSecurityContainerController and
// StatusBarKeyguardViewManager, which calls the transition interactor to kick off a
// transition vs. listening to legacy state flags.
return
}
scope.launch {
keyguardInteractor.isKeyguardGoingAway
.sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
@@ -160,7 +223,7 @@ constructor(
)
// IME for password requires a slightly faster animation
val duration =
if (securityMode == Password) {
if (securityMode == KeyguardSecurityModel.SecurityMode.Password) {
TO_GONE_SHORT_DURATION
} else {
TO_GONE_DURATION
@@ -188,7 +251,7 @@ constructor(
companion object {
private val DEFAULT_DURATION = 300.milliseconds
val TO_GONE_DURATION = 250.milliseconds
val TO_GONE_DURATION = 500.milliseconds
val TO_GONE_SHORT_DURATION = 200.milliseconds
}
}

View File

@@ -34,12 +34,12 @@ import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.ScreenModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
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
import kotlinx.coroutines.flow.Flow
@@ -53,6 +53,7 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
import javax.inject.Inject
/**
* Encapsulates business-logic related to the keyguard but not to a more specific part within it.
@@ -264,7 +265,26 @@ constructor(
repository.setAnimateDozingTransitions(animate)
}
fun isKeyguardDismissable(): Boolean {
return repository.isKeyguardUnlocked.value
}
companion object {
private const val TAG = "KeyguardInteractor"
fun isKeyguardVisibleInState(state: KeyguardState): Boolean {
return when (state) {
KeyguardState.OFF -> true
KeyguardState.DOZING -> true
KeyguardState.DREAMING -> true
KeyguardState.AOD -> true
KeyguardState.ALTERNATE_BOUNCER -> true
KeyguardState.PRIMARY_BOUNCER -> true
KeyguardState.LOCKSCREEN -> true
KeyguardState.GONE -> false
KeyguardState.OCCLUDED -> true
KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> false
}
}
}
}

View File

@@ -0,0 +1,89 @@
/*
* 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 com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardSurfaceBehindRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import javax.inject.Inject
@SysUISingleton
class KeyguardSurfaceBehindInteractor
@Inject
constructor(
private val repository: KeyguardSurfaceBehindRepository,
private val fromLockscreenInteractor: FromLockscreenTransitionInteractor,
private val fromPrimaryBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
transitionInteractor: KeyguardTransitionInteractor,
) {
@OptIn(ExperimentalCoroutinesApi::class)
val viewParams: Flow<KeyguardSurfaceBehindModel> =
transitionInteractor.isInTransitionToAnyState
.flatMapLatest { isInTransition ->
if (!isInTransition) {
defaultParams
} else {
combine(
transitionSpecificViewParams,
defaultParams,
) { transitionParams, defaultParams ->
transitionParams ?: defaultParams
}
}
}
val isAnimatingSurface = repository.isAnimatingSurface
private val defaultParams =
transitionInteractor.finishedKeyguardState.map { state ->
KeyguardSurfaceBehindModel(
alpha =
if (WindowManagerLockscreenVisibilityInteractor.isSurfaceVisible(state)) 1f
else 0f
)
}
/**
* View params provided by the transition interactor for the most recently STARTED transition.
* This is used to run transition-specific animations on the surface.
*
* If null, there are no transition-specific view params needed for this transition and we will
* use a reasonable default.
*/
@OptIn(ExperimentalCoroutinesApi::class)
private val transitionSpecificViewParams: Flow<KeyguardSurfaceBehindModel?> =
transitionInteractor.startedKeyguardTransitionStep.flatMapLatest { startedStep ->
when (startedStep.from) {
KeyguardState.LOCKSCREEN -> fromLockscreenInteractor.surfaceBehindModel
KeyguardState.PRIMARY_BOUNCER -> fromPrimaryBouncerInteractor.surfaceBehindModel
// Return null for other states, where no transition specific params are needed.
else -> flowOf(null)
}
}
fun setAnimatingSurface(animating: Boolean) {
repository.setAnimatingSurface(animating)
}
}

View File

@@ -17,17 +17,20 @@
package com.android.systemui.keyguard.domain.interactor
import android.util.Log
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.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -36,6 +39,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
@@ -46,8 +50,12 @@ import kotlinx.coroutines.flow.stateIn
class KeyguardTransitionInteractor
@Inject
constructor(
private val repository: KeyguardTransitionRepository,
@Application val scope: CoroutineScope,
private val repository: KeyguardTransitionRepository,
private val keyguardInteractor: dagger.Lazy<KeyguardInteractor>,
private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
private val fromPrimaryBouncerTransitionInteractor:
dagger.Lazy<FromPrimaryBouncerTransitionInteractor>,
) {
private val TAG = this::class.simpleName
@@ -128,12 +136,11 @@ constructor(
repository.transition(PRIMARY_BOUNCER, GONE)
/** OFF->LOCKSCREEN transition information. */
val offToLockscreenTransition: Flow<TransitionStep> =
repository.transition(KeyguardState.OFF, LOCKSCREEN)
val offToLockscreenTransition: Flow<TransitionStep> = repository.transition(OFF, LOCKSCREEN)
/** DOZING->LOCKSCREEN transition information. */
val dozingToLockscreenTransition: Flow<TransitionStep> =
repository.transition(KeyguardState.DOZING, LOCKSCREEN)
repository.transition(DOZING, LOCKSCREEN)
/**
* AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
@@ -157,17 +164,30 @@ constructor(
val finishedKeyguardTransitionStep: Flow<TransitionStep> =
repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
/** The destination state of the last started transition */
/** The destination state of the last started transition. */
val startedKeyguardState: StateFlow<KeyguardState> =
startedKeyguardTransitionStep
.map { step -> step.to }
.stateIn(scope, SharingStarted.Eagerly, KeyguardState.OFF)
.stateIn(scope, SharingStarted.Eagerly, OFF)
/** The last completed [KeyguardState] transition */
val finishedKeyguardState: StateFlow<KeyguardState> =
finishedKeyguardTransitionStep
.map { step -> step.to }
.stateIn(scope, SharingStarted.Eagerly, LOCKSCREEN)
/**
* Whether we're currently in a transition to a new [KeyguardState] and haven't yet completed
* it.
*/
val isInTransitionToAnyState =
combine(
startedKeyguardTransitionStep,
finishedKeyguardState,
) { startedStep, finishedState ->
startedStep.to != finishedState
}
/**
* The amount of transition into or out of the given [KeyguardState].
*
@@ -187,4 +207,41 @@ constructor(
}
}
}
fun transitionStepsFromState(fromState: KeyguardState): Flow<TransitionStep> {
return repository.transitions.filter { step -> step.from == fromState }
}
fun transitionStepsToState(toState: KeyguardState): Flow<TransitionStep> {
return repository.transitions.filter { step -> step.to == toState }
}
/**
* Called to start a transition that will ultimately dismiss the keyguard from the current
* state.
*/
fun startDismissKeyguardTransition() {
when (startedKeyguardState.value) {
LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer()
else ->
Log.e(
"KeyguardTransitionInteractor",
"We don't know how to dismiss keyguard from state " +
"${startedKeyguardState.value}"
)
}
}
/** Whether we're in a transition to the given [KeyguardState], but haven't yet completed it. */
fun isInTransitionToState(
state: KeyguardState,
): Flow<Boolean> {
return combine(
startedKeyguardTransitionStep,
finishedKeyguardState,
) { startedStep, finishedState ->
startedStep.to == state && finishedState != state
}
}
}

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.domain.interactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import javax.inject.Inject
@SysUISingleton
class WindowManagerLockscreenVisibilityInteractor
@Inject
constructor(
keyguardInteractor: KeyguardInteractor,
transitionInteractor: KeyguardTransitionInteractor,
surfaceBehindInteractor: KeyguardSurfaceBehindInteractor,
fromLockscreenInteractor: FromLockscreenTransitionInteractor,
fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
) {
private val defaultSurfaceBehindVisibility =
transitionInteractor.finishedKeyguardState.map(::isSurfaceVisible)
/**
* Surface visibility provided by the From*TransitionInteractor responsible for the currently
* RUNNING transition, or null if the current transition does not require special surface
* visibility handling.
*
* An example of transition-specific visibility is swipe to unlock, where the surface should
* only be visible after swiping 20% of the way up the screen, and should become invisible again
* if the user swipes back down.
*/
@OptIn(ExperimentalCoroutinesApi::class)
private val transitionSpecificSurfaceBehindVisibility: Flow<Boolean?> =
transitionInteractor.startedKeyguardTransitionStep
.flatMapLatest { startedStep ->
when (startedStep.from) {
KeyguardState.LOCKSCREEN -> {
fromLockscreenInteractor.surfaceBehindVisibility
}
KeyguardState.PRIMARY_BOUNCER -> {
fromBouncerInteractor.surfaceBehindVisibility
}
else -> flowOf(null)
}
}
.distinctUntilChanged()
/**
* Surface visibility, which is either determined by the default visibility in the FINISHED
* KeyguardState, or the transition-specific visibility used during certain RUNNING transitions.
*/
@OptIn(ExperimentalCoroutinesApi::class)
val surfaceBehindVisibility: Flow<Boolean> =
transitionInteractor
.isInTransitionToAnyState
.flatMapLatest { isInTransition ->
if (!isInTransition) {
defaultSurfaceBehindVisibility
} else {
combine(
transitionSpecificSurfaceBehindVisibility,
defaultSurfaceBehindVisibility,
) { transitionVisibility, defaultVisibility ->
// Defer to the transition-specific visibility since we're RUNNING a
// transition, but fall back to the default visibility if the current
// transition's interactor did not specify a visibility.
transitionVisibility ?: defaultVisibility
}
}
}
.distinctUntilChanged()
/**
* Whether we're animating, or intend to animate, the surface behind the keyguard via remote
* animation. This is used to keep the RemoteAnimationTarget alive until we're done using it.
*/
val usingKeyguardGoingAwayAnimation: Flow<Boolean> =
combine(
transitionInteractor.isInTransitionToState(KeyguardState.GONE),
transitionInteractor.finishedKeyguardState,
surfaceBehindInteractor.isAnimatingSurface
) { isInTransitionToGone, finishedState, isAnimatingSurface ->
// We may still be animating the surface after the keyguard is fully GONE, since
// some animations (like the translation spring) are not tied directly to the
// transition step amount.
isInTransitionToGone || (finishedState == KeyguardState.GONE && isAnimatingSurface)
}
.distinctUntilChanged()
/**
* Whether the lockscreen is visible, from the Window Manager (WM) perspective.
*
* Note: This may briefly be true even if the lockscreen UI has animated out (alpha = 0f), as we
* only inform WM once we're done with the keyguard and we're fully GONE. Don't use this if you
* want to know if the AOD/clock/notifs/etc. are visible.
*/
val lockscreenVisibility: Flow<Boolean> =
combine(
transitionInteractor.startedKeyguardTransitionStep,
transitionInteractor.finishedKeyguardState,
) { startedStep, finishedState ->
// If we finished the transition, use the finished state. If we're running a
// transition, use the state we're transitioning FROM. This can be different from
// the last finished state if a transition is interrupted. For example, if we were
// transitioning from GONE to AOD and then started AOD -> LOCKSCREEN mid-transition,
// we want to immediately use the visibility for AOD (lockscreenVisibility=true)
// even though the lastFinishedState is still GONE (lockscreenVisibility=false).
if (finishedState == startedStep.to) finishedState else startedStep.from
}
.map(::isLockscreenVisible)
.distinctUntilChanged()
/**
* Whether always-on-display (AOD) is visible when the lockscreen is visible, from window
* manager's perspective.
*
* Note: This may be true even if AOD is not user-visible, such as when the light sensor
* indicates the device is in the user's pocket. Don't use this if you want to know if the AOD
* clock/smartspace/notif icons are visible.
*/
val aodVisibility: Flow<Boolean> =
combine(
keyguardInteractor.isDozing,
keyguardInteractor.biometricUnlockState,
) { isDozing, biometricUnlockState ->
// AOD is visible if we're dozing, unless we are wake and unlocking (where we go
// directly from AOD to unlocked while dozing).
isDozing && !BiometricUnlockModel.isWakeAndUnlock(biometricUnlockState)
}
.distinctUntilChanged()
companion object {
fun isSurfaceVisible(state: KeyguardState): Boolean {
return !isLockscreenVisible(state)
}
fun isLockscreenVisible(state: KeyguardState): Boolean {
return state != KeyguardState.GONE
}
}
}

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.keyguard.shared.model
/**
* Models the appearance of the surface behind the keyguard, and (optionally) how it should be
* animating.
*
* This is intended to be an atomic, high-level description of the surface's appearance and related
* animations, which we can derive from the STARTED/FINISHED transition states rather than the
* individual TransitionSteps.
*
* For example, if we're transitioning from LOCKSCREEN to GONE, that means we should be
* animatingFromAlpha 0f -> 1f and animatingFromTranslationY 500f -> 0f.
* KeyguardSurfaceBehindAnimator can decide how best to implement this, depending on previously
* running animations, spring momentum, and other state.
*/
data class KeyguardSurfaceBehindModel(
val alpha: Float = 1f,
/**
* If provided, animate from this value to [alpha] unless an animation is already running, in
* which case we'll animate from the current value to [alpha].
*/
val animateFromAlpha: Float = alpha,
val translationY: Float = 0f,
/**
* If provided, animate from this value to [translationY] unless an animation is already
* running, in which case we'll animate from the current value to [translationY].
*/
val animateFromTranslationY: Float = translationY,
) {
fun willAnimateAlpha(): Boolean {
return animateFromAlpha != alpha
}
fun willAnimateTranslationY(): Boolean {
return animateFromTranslationY != translationY
}
}

View File

@@ -0,0 +1,219 @@
/*
* 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.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.graphics.Matrix
import android.util.Log
import android.view.RemoteAnimationTarget
import android.view.SurfaceControl
import android.view.SyncRtSurfaceTransactionApplier
import android.view.View
import androidx.dynamicanimation.animation.FloatValueHolder
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.keyguard.KeyguardViewController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.TAG
import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
import com.android.wm.shell.animation.Interpolators
import java.util.concurrent.Executor
import javax.inject.Inject
/**
* Applies [KeyguardSurfaceBehindViewParams] to a RemoteAnimationTarget, starting and managing
* animations as needed.
*/
@SysUISingleton
class KeyguardSurfaceBehindParamsApplier
@Inject
constructor(
@Main private val executor: Executor,
private val keyguardViewController: KeyguardViewController,
private val interactor: KeyguardSurfaceBehindInteractor,
) {
private var surfaceBehind: RemoteAnimationTarget? = null
private val surfaceTransactionApplier: SyncRtSurfaceTransactionApplier
get() = SyncRtSurfaceTransactionApplier(keyguardViewController.viewRootImpl.view)
private val matrix = Matrix()
private val tmpFloat = FloatArray(9)
private var animatedTranslationY = FloatValueHolder()
private val translateYSpring =
SpringAnimation(animatedTranslationY).apply {
spring =
SpringForce().apply {
stiffness = 200f
dampingRatio = 1f
}
addUpdateListener { _, _, _ -> applyToSurfaceBehind() }
addEndListener { _, _, _, _ ->
try {
updateIsAnimatingSurface()
} catch (e: NullPointerException) {
// TODO(b/291645410): Remove when we can isolate DynamicAnimations.
e.printStackTrace()
}
}
}
private var animatedAlpha = 0f
private var alphaAnimator =
ValueAnimator.ofFloat(0f, 1f).apply {
duration = 500
interpolator = Interpolators.ALPHA_IN
addUpdateListener {
animatedAlpha = it.animatedValue as Float
applyToSurfaceBehind()
}
addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
updateIsAnimatingSurface()
}
}
)
}
/**
* ViewParams to apply to the surface provided to [applyParamsToSurface]. If the surface is null
* these will be applied once someone gives us a surface via [applyParamsToSurface].
*/
var viewParams: KeyguardSurfaceBehindModel = KeyguardSurfaceBehindModel()
set(newParams) {
field = newParams
startOrUpdateAnimators()
applyToSurfaceBehind()
}
/**
* Provides us with a surface to animate. We'll apply the [viewParams] to this surface and start
* any necessary animations.
*/
fun applyParamsToSurface(surface: RemoteAnimationTarget) {
this.surfaceBehind = surface
startOrUpdateAnimators()
}
/**
* Notifies us that the [RemoteAnimationTarget] has been released, one way or another.
* Attempting to animate a released target will cause a crash.
*
* This can be called either because we finished animating the surface naturally, or by WM
* because external factors cancelled the remote animation (timeout, re-lock, etc). If it's the
* latter, cancel any outstanding animations we have.
*/
fun notifySurfaceReleased() {
surfaceBehind = null
if (alphaAnimator.isRunning) {
alphaAnimator.cancel()
}
if (translateYSpring.isRunning) {
translateYSpring.cancel()
}
}
private fun startOrUpdateAnimators() {
if (surfaceBehind == null) {
return
}
if (viewParams.willAnimateAlpha()) {
var fromAlpha = viewParams.animateFromAlpha
if (alphaAnimator.isRunning) {
alphaAnimator.cancel()
fromAlpha = animatedAlpha
}
alphaAnimator.setFloatValues(fromAlpha, viewParams.alpha)
alphaAnimator.start()
}
if (viewParams.willAnimateTranslationY()) {
if (!translateYSpring.isRunning) {
// If the spring isn't running yet, set the start value. Otherwise, respect the
// current position.
animatedTranslationY.value = viewParams.animateFromTranslationY
}
translateYSpring.animateToFinalPosition(viewParams.translationY)
}
updateIsAnimatingSurface()
}
private fun updateIsAnimatingSurface() {
interactor.setAnimatingSurface(translateYSpring.isRunning || alphaAnimator.isRunning)
}
private fun applyToSurfaceBehind() {
surfaceBehind?.leash?.let { sc ->
executor.execute {
if (surfaceBehind == null) {
Log.d(
TAG,
"Attempting to modify params of surface that isn't " +
"animating. Ignoring."
)
matrix.set(Matrix.IDENTITY_MATRIX)
return@execute
}
val translationY =
if (translateYSpring.isRunning) animatedTranslationY.value
else viewParams.translationY
val alpha =
if (alphaAnimator.isRunning) {
animatedAlpha
} else {
viewParams.alpha
}
if (
keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
sc.isValid
) {
with(SurfaceControl.Transaction()) {
setMatrix(
sc,
matrix.apply { setTranslate(/* dx= */ 0f, translationY) },
tmpFloat
)
setAlpha(sc, alpha)
apply()
}
} else {
surfaceTransactionApplier.scheduleApply(
SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(sc)
.withMatrix(matrix.apply { setTranslate(/* dx= */ 0f, translationY) })
.withAlpha(alpha)
.build()
)
}
}
}
}
}

View File

@@ -0,0 +1,37 @@
/*
* 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 com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSurfaceBehindViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
/**
* Binds the [WindowManagerLockscreenVisibilityManager] "view", which manages the visibility of the
* surface behind the keyguard.
*/
object KeyguardSurfaceBehindViewBinder {
@JvmStatic
fun bind(
viewModel: KeyguardSurfaceBehindViewModel,
applier: KeyguardSurfaceBehindParamsApplier,
scope: CoroutineScope
) {
scope.launch { viewModel.surfaceBehindViewParams.collect { applier.viewParams = it } }
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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 com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibilityViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
/**
* Binds the [WindowManagerLockscreenVisibilityManager] "view", which manages the visibility of the
* surface behind the keyguard.
*/
object WindowManagerLockscreenVisibilityViewBinder {
@JvmStatic
fun bind(
viewModel: WindowManagerLockscreenVisibilityViewModel,
lockscreenVisibilityManager: WindowManagerLockscreenVisibilityManager,
scope: CoroutineScope
) {
scope.launch {
viewModel.surfaceBehindVisibility.collect {
lockscreenVisibilityManager.setSurfaceBehindVisibility(it)
}
}
scope.launch {
viewModel.lockscreenVisibility.collect {
lockscreenVisibilityManager.setLockscreenShown(it)
}
}
scope.launch {
viewModel.aodVisibility.collect { lockscreenVisibilityManager.setAodVisible(it) }
}
scope.launch {
viewModel.surfaceBehindAnimating.collect {
lockscreenVisibilityManager.setUsingGoingAwayRemoteAnimation(it)
}
}
}
}

View File

@@ -0,0 +1,28 @@
/*
* 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.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
import javax.inject.Inject
@SysUISingleton
class KeyguardSurfaceBehindViewModel
@Inject
constructor(interactor: KeyguardSurfaceBehindInteractor) {
val surfaceBehindViewParams = interactor.viewParams
}

View File

@@ -0,0 +1,31 @@
/*
* 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.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor
import javax.inject.Inject
@SysUISingleton
class WindowManagerLockscreenVisibilityViewModel
@Inject
constructor(interactor: WindowManagerLockscreenVisibilityInteractor) {
val surfaceBehindVisibility = interactor.surfaceBehindVisibility
val surfaceBehindAnimating = interactor.usingKeyguardGoingAwayAnimation
val lockscreenVisibility = interactor.lockscreenVisibility
val aodVisibility = interactor.aodVisibility
}

View File

@@ -634,7 +634,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private final ActivityIntentHelper mActivityIntentHelper;
private final NotificationStackScrollLayoutController mStackScrollerController;
public final NotificationStackScrollLayoutController mStackScrollerController;
private final ColorExtractor.OnColorsChangedListener mOnColorsChangedListener =
(extractor, which) -> updateTheme();

View File

@@ -22,6 +22,8 @@ import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConst
import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -60,10 +62,14 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInte
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.bouncer.ui.BouncerView;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.TaskbarDelegate;
@@ -86,8 +92,6 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.unfold.FoldAodAnimationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import dagger.Lazy;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
@@ -97,6 +101,9 @@ import java.util.Set;
import javax.inject.Inject;
import dagger.Lazy;
import kotlinx.coroutines.CoroutineDispatcher;
/**
* Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
* via {@link ViewMediatorCallback} to poke the wake lock and report that the keyguard is done,
@@ -281,6 +288,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private int mLastBiometricMode;
private boolean mLastScreenOffAnimationPlaying;
private float mQsExpansion;
private FeatureFlags mFlags;
final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
private boolean mIsBackAnimationEnabled;
private final boolean mUdfpsNewTouchDetectionEnabled;
@@ -326,6 +336,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
}
};
private Lazy<WindowManagerLockscreenVisibilityInteractor> mWmLockscreenVisibilityInteractor;
@Inject
public StatusBarKeyguardViewManager(
@@ -352,7 +363,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
BouncerView primaryBouncerView,
AlternateBouncerInteractor alternateBouncerInteractor,
UdfpsOverlayInteractor udfpsOverlayInteractor,
ActivityStarter activityStarter
ActivityStarter activityStarter,
KeyguardTransitionInteractor keyguardTransitionInteractor,
@Main CoroutineDispatcher mainDispatcher,
Lazy<WindowManagerLockscreenVisibilityInteractor> wmLockscreenVisibilityInteractor
) {
mContext = context;
mViewMediatorCallback = callback;
@@ -370,6 +384,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mShadeController = shadeController;
mLatencyTracker = latencyTracker;
mKeyguardSecurityModel = keyguardSecurityModel;
mFlags = featureFlags;
mPrimaryBouncerCallbackInteractor = primaryBouncerCallbackInteractor;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
mPrimaryBouncerView = primaryBouncerView;
@@ -381,8 +396,14 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mUdfpsNewTouchDetectionEnabled = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION);
mUdfpsOverlayInteractor = udfpsOverlayInteractor;
mActivityStarter = activityStarter;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mMainDispatcher = mainDispatcher;
mWmLockscreenVisibilityInteractor = wmLockscreenVisibilityInteractor;
}
KeyguardTransitionInteractor mKeyguardTransitionInteractor;
CoroutineDispatcher mMainDispatcher;
@Override
public void registerCentralSurfaces(CentralSurfaces centralSurfaces,
ShadeViewController shadeViewController,
@@ -429,6 +450,14 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
}
private KeyguardStateController.Callback mKeyguardStateControllerCallback =
new KeyguardStateController.Callback() {
@Override
public void onUnlockedChanged() {
updateAlternateBouncerShowing(mAlternateBouncerInteractor.maybeHide());
}
};
private void registerListeners() {
mKeyguardUpdateManager.registerCallback(mUpdateMonitorCallback);
mStatusBarStateController.addCallback(this);
@@ -442,6 +471,32 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mDockManager.addListener(mDockEventListener);
mIsDocked = mDockManager.isDocked();
}
mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
mShadeViewController.postToView(() ->
collectFlow(
getViewRootImpl().getView(),
combineFlows(
mKeyguardTransitionInteractor.getFinishedKeyguardState(),
mWmLockscreenVisibilityInteractor.get()
.getUsingKeyguardGoingAwayAnimation(),
(finishedState, animating) ->
KeyguardInteractor.Companion.isKeyguardVisibleInState(
finishedState)
|| animating),
this::consumeShowStatusBarKeyguardView));
}
}
private void consumeShowStatusBarKeyguardView(boolean show) {
if (show != mLastShowing) {
if (show) {
show(null);
} else {
hide(0, 0);
}
}
}
/** Register a callback, to be invoked by the Predictive Back system. */
@@ -1313,6 +1368,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
hideAlternateBouncer(false);
executeAfterKeyguardGoneAction();
}
if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
mKeyguardTransitionInteractor.startDismissKeyguardTransition();
}
}
/** Display security message to relevant KeyguardMessageArea. */

View File

@@ -29,7 +29,7 @@ import kotlin.coroutines.EmptyCoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
/** A class allowing Java classes to collect on Kotlin flows. */
@@ -75,3 +75,7 @@ fun <T> collectFlow(
repeatOnLifecycle(state) { flow.collect { consumer.accept(it) } }
}
}
fun <A, B, R> combineFlows(flow1: Flow<A>, flow2: Flow<B>, bifunction: (A, B) -> R): Flow<R> {
return combine(flow1, flow2, bifunction)
}

View File

@@ -47,6 +47,8 @@ import com.android.systemui.classifier.FalsingA11yDelegate
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.log.SessionTracker
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.plugins.FalsingManager
@@ -145,6 +147,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
private lateinit var testableResources: TestableResources
private lateinit var sceneTestUtils: SceneTestUtils
private lateinit var sceneInteractor: SceneInteractor
private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
private lateinit var authenticationInteractor: AuthenticationInteractor
private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState>
@@ -184,6 +187,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
featureFlags.set(Flags.SCENE_CONTAINER, false)
featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false)
featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
keyguardPasswordViewController =
KeyguardPasswordViewController(
@@ -206,6 +210,9 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
whenever(userInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID)
sceneTestUtils = SceneTestUtils(this)
sceneInteractor = sceneTestUtils.sceneInteractor()
keyguardTransitionInteractor =
KeyguardTransitionInteractorFactory.create(sceneTestUtils.testScope.backgroundScope)
.keyguardTransitionInteractor
sceneTransitionStateFlow =
MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen))
sceneInteractor.setTransitionState(sceneTransitionStateFlow)
@@ -243,9 +250,9 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
{ JavaAdapter(sceneTestUtils.testScope.backgroundScope) },
userInteractor,
faceAuthAccessibilityDelegate,
) {
authenticationInteractor
}
keyguardTransitionInteractor,
{ authenticationInteractor },
)
}
@Test

View File

@@ -49,7 +49,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
@@ -164,8 +164,9 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase {
mVibrator,
mAuthRippleController,
mResources,
new KeyguardTransitionInteractor(mTransitionRepository,
TestScopeProvider.getTestScope().getBackgroundScope()),
KeyguardTransitionInteractorFactory.create(
TestScopeProvider.getTestScope().getBackgroundScope(),
mTransitionRepository).getKeyguardTransitionInteractor(),
KeyguardInteractorFactory.create(mFeatureFlags).getKeyguardInteractor(),
mFeatureFlags,
mPrimaryBouncerInteractor

View File

@@ -224,7 +224,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager,
mShadeWindowLogger);
mFeatureFlags = new FakeFeatureFlags();
mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
DejankUtils.setImmediate(true);
@@ -957,7 +957,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
mSystemClock,
mDispatcher,
() -> mDreamingToLockscreenTransitionViewModel,
mSystemPropertiesHelper);
mSystemPropertiesHelper,
() -> mock(WindowManagerLockscreenVisibilityManager.class));
mViewMediator.start();
mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);

View File

@@ -52,6 +52,7 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.dump.logcatLogBuffer
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
import com.android.systemui.flags.Flags.KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
@@ -168,7 +169,11 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
biometricSettingsRepository = FakeBiometricSettingsRepository()
deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
trustRepository = FakeTrustRepository()
featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) }
featureFlags =
FakeFeatureFlags().apply {
set(FACE_AUTH_REFACTOR, true)
set(KEYGUARD_WM_STATE_REFACTOR, false)
}
val withDeps =
KeyguardInteractorFactory.create(
featureFlags = featureFlags,

View File

@@ -63,6 +63,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -193,7 +194,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
assertThat(underTest.isKeyguardShowing()).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
verify(keyguardStateController).addCallback(captor.capture())
verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
whenever(keyguardStateController.isShowing).thenReturn(true)
captor.value.onKeyguardShowingChanged()
@@ -255,7 +256,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
assertThat(latest).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
verify(keyguardStateController).addCallback(captor.capture())
verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
whenever(keyguardStateController.isOccluded).thenReturn(true)
captor.value.onKeyguardShowingChanged()
@@ -280,7 +281,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
assertThat(isKeyguardUnlocked).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
verify(keyguardStateController).addCallback(captor.capture())
verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
whenever(keyguardStateController.isUnlocked).thenReturn(true)
captor.value.onUnlockedChanged()
@@ -454,7 +455,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
assertThat(latest).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
verify(keyguardStateController).addCallback(captor.capture())
verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(true)
captor.value.onKeyguardGoingAwayChanged()

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.data.repository
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
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
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyguardSurfaceBehindRepositoryImplTest : SysuiTestCase() {
private val testScope = TestScope()
private lateinit var underTest: KeyguardSurfaceBehindRepositoryImpl
@Before
fun setUp() {
underTest = KeyguardSurfaceBehindRepositoryImpl()
}
@Test
fun testSetAnimatingSurface() {
testScope.runTest {
val values by collectValues(underTest.isAnimatingSurface)
runCurrent()
underTest.setAnimatingSurface(true)
runCurrent()
underTest.setAnimatingSurface(false)
runCurrent()
// Default (first) value should be false.
assertThat(values).isEqualTo(listOf(false, true, false))
}
}
}

View File

@@ -0,0 +1,190 @@
/*
* 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.coroutines.collectValues
import com.android.systemui.flags.FakeFeatureFlags
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.shade.data.repository.FakeShadeRepository
import dagger.Lazy
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertTrue
import junit.framework.Assert.fail
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class FromLockscreenTransitionInteractorTest : KeyguardTransitionInteractorTestCase() {
private lateinit var underTest: FromLockscreenTransitionInteractor
// Override the fromLockscreenTransitionInteractor provider from the superclass so our underTest
// interactor is provided to any classes that need it.
override var fromLockscreenTransitionInteractorLazy: Lazy<FromLockscreenTransitionInteractor>? =
Lazy {
underTest
}
@Before
override fun setUp() {
super.setUp()
underTest =
FromLockscreenTransitionInteractor(
transitionRepository = super.transitionRepository,
transitionInteractor = super.transitionInteractor,
scope = super.testScope.backgroundScope,
keyguardInteractor = super.keyguardInteractor,
flags = FakeFeatureFlags(),
shadeRepository = FakeShadeRepository(),
)
}
@Test
fun testSurfaceBehindVisibility_nonNullOnlyForRelevantTransitions() =
testScope.runTest {
val values by collectValues(underTest.surfaceBehindVisibility)
runCurrent()
// Transition-specific surface visibility should be null ("don't care") initially.
assertEquals(
listOf(
null,
),
values
)
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
)
)
runCurrent()
assertEquals(
listOf(
null, // LOCKSCREEN -> AOD does not have any specific surface visibility.
),
values
)
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
)
)
runCurrent()
assertEquals(
listOf(
null,
true, // Surface is made visible immediately during LOCKSCREEN -> GONE
),
values
)
}
@Test
fun testSurfaceBehindModel() =
testScope.runTest {
val values by collectValues(underTest.surfaceBehindModel)
runCurrent()
assertEquals(
values,
listOf(
null, // We should start null ("don't care").
)
)
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
)
)
runCurrent()
assertEquals(
listOf(
null, // LOCKSCREEN -> AOD does not have specific view params.
),
values
)
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
)
)
runCurrent()
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.RUNNING,
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
value = 0.01f,
)
)
runCurrent()
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.RUNNING,
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
value = 0.99f,
)
)
runCurrent()
assertEquals(3, values.size)
val model1percent = values[1]
val model99percent = values[2]
try {
// We should initially have an alpha of 0f when unlocking, so the surface is not
// visible
// while lockscreen UI animates out.
assertEquals(0f, model1percent!!.alpha)
// By the end it should probably be visible.
assertTrue(model99percent!!.alpha > 0f)
} catch (e: NullPointerException) {
fail("surfaceBehindModel was unexpectedly null.")
}
}
}

View File

@@ -0,0 +1,214 @@
/*
* 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.coroutines.collectValues
import com.android.systemui.flags.FakeFeatureFlags
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.util.mockito.mock
import dagger.Lazy
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertTrue
import junit.framework.Assert.fail
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class FromPrimaryBouncerTransitionInteractorTest : KeyguardTransitionInteractorTestCase() {
private lateinit var underTest: FromPrimaryBouncerTransitionInteractor
// Override the fromPrimaryBouncerTransitionInteractor provider from the superclass so our
// underTest interactor is provided to any classes that need it.
override var fromPrimaryBouncerTransitionInteractorLazy:
Lazy<FromPrimaryBouncerTransitionInteractor>? =
Lazy {
underTest
}
@Before
override fun setUp() {
super.setUp()
underTest =
FromPrimaryBouncerTransitionInteractor(
transitionRepository = super.transitionRepository,
transitionInteractor = super.transitionInteractor,
scope = super.testScope.backgroundScope,
keyguardInteractor = super.keyguardInteractor,
flags = FakeFeatureFlags(),
keyguardSecurityModel = mock(),
)
}
@Test
fun testSurfaceBehindVisibility() =
testScope.runTest {
val values by collectValues(underTest.surfaceBehindVisibility)
runCurrent()
// Transition-specific surface visibility should be null ("don't care") initially.
assertEquals(
listOf(
null,
),
values
)
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.LOCKSCREEN,
)
)
runCurrent()
assertEquals(
listOf(
null, // PRIMARY_BOUNCER -> LOCKSCREEN does not have any specific visibility.
),
values
)
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.GONE,
)
)
runCurrent()
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.RUNNING,
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.GONE,
value = 0.01f,
)
)
runCurrent()
assertEquals(
listOf(
null,
false, // Surface is only made visible once the bouncer UI animates out.
),
values
)
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.GONE,
value = 0.99f,
)
)
runCurrent()
assertEquals(
listOf(
null,
false,
true, // Surface should eventually be visible.
),
values
)
}
@Test
fun testSurfaceBehindModel() =
testScope.runTest {
val values by collectValues(underTest.surfaceBehindModel)
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.LOCKSCREEN,
)
)
runCurrent()
assertEquals(
listOf(
null, // PRIMARY_BOUNCER -> LOCKSCREEN does not have specific view params.
),
values
)
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.GONE,
)
)
runCurrent()
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.RUNNING,
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.GONE,
value = 0.01f,
)
)
runCurrent()
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.RUNNING,
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.GONE,
value = 0.99f,
)
)
runCurrent()
assertEquals(3, values.size)
val model1percent = values[1]
val model99percent = values[2]
try {
// We should initially have an alpha of 0f when unlocking, so the surface is not
// visible
// while lockscreen UI animates out.
assertEquals(0f, model1percent!!.alpha)
// By the end it should probably be visible.
assertTrue(model99percent!!.alpha > 0f)
} catch (e: NullPointerException) {
fail("surfaceBehindModel was unexpectedly null.")
}
}
}

View File

@@ -0,0 +1,169 @@
/*
* 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.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.util.mockito.whenever
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertTrue
import kotlinx.coroutines.flow.flowOf
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.initMocks
@SmallTest
@RoboPilotTest
@RunWith(AndroidJUnit4::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
class KeyguardSurfaceBehindInteractorTest : SysuiTestCase() {
private lateinit var underTest: KeyguardSurfaceBehindInteractor
private lateinit var repository: FakeKeyguardSurfaceBehindRepository
@Mock
private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
@Mock
private lateinit var fromPrimaryBouncerTransitionInteractor:
FromPrimaryBouncerTransitionInteractor
private val lockscreenSurfaceBehindModel = KeyguardSurfaceBehindModel(alpha = 0.33f)
private val primaryBouncerSurfaceBehindModel = KeyguardSurfaceBehindModel(alpha = 0.66f)
private val testScope = TestScope()
private lateinit var transitionRepository: FakeKeyguardTransitionRepository
private lateinit var transitionInteractor: KeyguardTransitionInteractor
@Before
fun setUp() {
initMocks(this)
whenever(fromLockscreenTransitionInteractor.surfaceBehindModel)
.thenReturn(flowOf(lockscreenSurfaceBehindModel))
whenever(fromPrimaryBouncerTransitionInteractor.surfaceBehindModel)
.thenReturn(flowOf(primaryBouncerSurfaceBehindModel))
transitionRepository = FakeKeyguardTransitionRepository()
transitionInteractor =
KeyguardTransitionInteractorFactory.create(
scope = testScope.backgroundScope,
repository = transitionRepository,
)
.keyguardTransitionInteractor
repository = FakeKeyguardSurfaceBehindRepository()
underTest =
KeyguardSurfaceBehindInteractor(
repository = repository,
fromLockscreenInteractor = fromLockscreenTransitionInteractor,
fromPrimaryBouncerInteractor = fromPrimaryBouncerTransitionInteractor,
transitionInteractor = transitionInteractor,
)
}
@Test
fun viewParamsSwitchToCorrectFlow() =
testScope.runTest {
val values by collectValues(underTest.viewParams)
// Start on the LOCKSCREEN.
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.AOD,
to = KeyguardState.LOCKSCREEN,
)
)
runCurrent()
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.FINISHED,
from = KeyguardState.AOD,
to = KeyguardState.LOCKSCREEN,
)
)
runCurrent()
// We're on LOCKSCREEN; we should be using the default params.
assertEquals(1, values.size)
assertTrue(values[0].alpha == 0f)
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
)
)
runCurrent()
// We're going from LOCKSCREEN -> GONE, we should be using the lockscreen interactor's
// surface behind model.
assertEquals(2, values.size)
assertEquals(values[1], lockscreenSurfaceBehindModel)
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.GONE,
)
)
runCurrent()
// We're going from PRIMARY_BOUNCER -> GONE, we should be using the bouncer interactor's
// surface behind model.
assertEquals(3, values.size)
assertEquals(values[2], primaryBouncerSurfaceBehindModel)
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.FINISHED,
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.GONE,
)
)
runCurrent()
// Once PRIMARY_BOUNCER -> GONE finishes, we should be using default params, which is
// alpha=1f when we're GONE.
assertEquals(4, values.size)
assertEquals(1f, values[3].alpha)
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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 com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.util.mockito.mock
import dagger.Lazy
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
open class KeyguardTransitionInteractorTestCase : SysuiTestCase() {
val testDispatcher = StandardTestDispatcher()
val testScope = TestScope(testDispatcher)
lateinit var keyguardRepository: FakeKeyguardRepository
lateinit var transitionRepository: FakeKeyguardTransitionRepository
lateinit var keyguardInteractor: KeyguardInteractor
lateinit var transitionInteractor: KeyguardTransitionInteractor
/**
* Replace these lazy providers with non-null ones if you want test dependencies to use a real
* instance of the interactor for the test.
*/
open var fromLockscreenTransitionInteractorLazy: Lazy<FromLockscreenTransitionInteractor>? =
null
open var fromPrimaryBouncerTransitionInteractorLazy:
Lazy<FromPrimaryBouncerTransitionInteractor>? =
null
open fun setUp() {
keyguardRepository = FakeKeyguardRepository()
transitionRepository = FakeKeyguardTransitionRepository()
keyguardInteractor =
KeyguardInteractorFactory.create(repository = keyguardRepository).keyguardInteractor
transitionInteractor =
KeyguardTransitionInteractorFactory.create(
repository = transitionRepository,
keyguardInteractor = keyguardInteractor,
scope = testScope.backgroundScope,
fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractorLazy
?: Lazy { mock() },
fromPrimaryBouncerTransitionInteractor =
fromPrimaryBouncerTransitionInteractorLazy ?: Lazy { mock() },
)
.also {
fromLockscreenTransitionInteractorLazy = it.fromLockscreenTransitionInteractor
fromPrimaryBouncerTransitionInteractorLazy =
it.fromPrimaryBouncerTransitionInteractor
}
.keyguardTransitionInteractor
}
}

View File

@@ -104,12 +104,21 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) }
featureFlags =
FakeFeatureFlags().apply {
set(Flags.FACE_AUTH_REFACTOR, true)
set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
}
transitionInteractor =
KeyguardTransitionInteractorFactory.create(
scope = testScope,
repository = transitionRepository,
keyguardInteractor = createKeyguardInteractor(),
fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
fromPrimaryBouncerTransitionInteractor = {
fromPrimaryBouncerTransitionInteractor
},
)
.keyguardTransitionInteractor
@@ -119,6 +128,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
keyguardInteractor = createKeyguardInteractor(),
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
flags = featureFlags,
shadeRepository = shadeRepository,
)
.apply { start() }
@@ -129,6 +139,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
keyguardInteractor = createKeyguardInteractor(),
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
flags = featureFlags,
keyguardSecurityModel = keyguardSecurityModel,
)
.apply { start() }

View File

@@ -0,0 +1,412 @@
/*
* 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.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
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.util.mockito.whenever
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.flow.MutableStateFlow
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.initMocks
@SmallTest
@RoboPilotTest
@RunWith(AndroidJUnit4::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
private lateinit var underTest: WindowManagerLockscreenVisibilityInteractor
@Mock private lateinit var surfaceBehindInteractor: KeyguardSurfaceBehindInteractor
@Mock
private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
@Mock
private lateinit var fromPrimaryBouncerTransitionInteractor:
FromPrimaryBouncerTransitionInteractor
private val lockscreenSurfaceVisibilityFlow = MutableStateFlow<Boolean?>(false)
private val primaryBouncerSurfaceVisibilityFlow = MutableStateFlow<Boolean?>(false)
private val surfaceBehindIsAnimatingFlow = MutableStateFlow(false)
private val testScope = TestScope()
private lateinit var keyguardInteractor: KeyguardInteractor
private lateinit var transitionRepository: FakeKeyguardTransitionRepository
private lateinit var transitionInteractor: KeyguardTransitionInteractor
@Before
fun setUp() {
initMocks(this)
whenever(fromLockscreenTransitionInteractor.surfaceBehindVisibility)
.thenReturn(lockscreenSurfaceVisibilityFlow)
whenever(fromPrimaryBouncerTransitionInteractor.surfaceBehindVisibility)
.thenReturn(primaryBouncerSurfaceVisibilityFlow)
whenever(surfaceBehindInteractor.isAnimatingSurface)
.thenReturn(surfaceBehindIsAnimatingFlow)
transitionRepository = FakeKeyguardTransitionRepository()
transitionInteractor =
KeyguardTransitionInteractorFactory.create(
scope = testScope.backgroundScope,
repository = transitionRepository,
)
.also { keyguardInteractor = it.keyguardInteractor }
.keyguardTransitionInteractor
underTest =
WindowManagerLockscreenVisibilityInteractor(
keyguardInteractor = keyguardInteractor,
transitionInteractor = transitionInteractor,
surfaceBehindInteractor = surfaceBehindInteractor,
fromLockscreenTransitionInteractor,
fromPrimaryBouncerTransitionInteractor,
)
}
@Test
fun surfaceBehindVisibility_switchesToCorrectFlow() =
testScope.runTest {
val values by collectValues(underTest.surfaceBehindVisibility)
// Start on LOCKSCREEN.
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.AOD,
to = KeyguardState.LOCKSCREEN,
)
)
runCurrent()
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.FINISHED,
from = KeyguardState.AOD,
to = KeyguardState.LOCKSCREEN,
)
)
runCurrent()
assertEquals(
listOf(
false, // We should start with the surface invisible on LOCKSCREEN.
),
values
)
val lockscreenSpecificSurfaceVisibility = true
lockscreenSurfaceVisibilityFlow.emit(lockscreenSpecificSurfaceVisibility)
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
)
)
runCurrent()
// We started a transition from LOCKSCREEN, we should be using the value emitted by the
// lockscreenSurfaceVisibilityFlow.
assertEquals(
listOf(
false,
lockscreenSpecificSurfaceVisibility,
),
values
)
// Go back to LOCKSCREEN, since we won't emit 'true' twice in a row.
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.GONE,
to = KeyguardState.LOCKSCREEN,
)
)
runCurrent()
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.FINISHED,
from = KeyguardState.GONE,
to = KeyguardState.LOCKSCREEN,
)
)
runCurrent()
assertEquals(
listOf(
false,
lockscreenSpecificSurfaceVisibility,
false, // FINISHED (LOCKSCREEN)
),
values
)
val bouncerSpecificVisibility = true
primaryBouncerSurfaceVisibilityFlow.emit(bouncerSpecificVisibility)
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.GONE,
)
)
runCurrent()
// We started a transition from PRIMARY_BOUNCER, we should be using the value emitted by
// the
// primaryBouncerSurfaceVisibilityFlow.
assertEquals(
listOf(
false,
lockscreenSpecificSurfaceVisibility,
false,
bouncerSpecificVisibility,
),
values
)
}
@Test
fun testUsingGoingAwayAnimation_duringTransitionToGone() =
testScope.runTest {
val values by collectValues(underTest.usingKeyguardGoingAwayAnimation)
// Start on LOCKSCREEN.
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.AOD,
to = KeyguardState.LOCKSCREEN,
)
)
runCurrent()
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.FINISHED,
from = KeyguardState.AOD,
to = KeyguardState.LOCKSCREEN,
)
)
runCurrent()
assertEquals(
listOf(
false, // Not using the animation when we're just sitting on LOCKSCREEN.
),
values
)
surfaceBehindIsAnimatingFlow.emit(true)
runCurrent()
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.FINISHED,
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
)
)
runCurrent()
assertEquals(
listOf(
false,
true, // Still true when we're FINISHED -> GONE, since we're still animating.
),
values
)
surfaceBehindIsAnimatingFlow.emit(false)
runCurrent()
assertEquals(
listOf(
false,
true,
false, // False once the animation ends.
),
values
)
}
@Test
fun testNotUsingGoingAwayAnimation_evenWhenAnimating_ifStateIsNotGone() =
testScope.runTest {
val values by collectValues(underTest.usingKeyguardGoingAwayAnimation)
// Start on LOCKSCREEN.
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.AOD,
to = KeyguardState.LOCKSCREEN,
)
)
runCurrent()
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.FINISHED,
from = KeyguardState.AOD,
to = KeyguardState.LOCKSCREEN,
)
)
runCurrent()
assertEquals(
listOf(
false, // Not using the animation when we're just sitting on LOCKSCREEN.
),
values
)
surfaceBehindIsAnimatingFlow.emit(true)
runCurrent()
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
)
)
runCurrent()
assertEquals(
listOf(
false,
true, // We're happily animating while transitioning to gone.
),
values
)
// Oh no, we're still surfaceBehindAnimating=true, but no longer transitioning to GONE.
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
)
)
runCurrent()
assertEquals(
listOf(
false,
true,
false, // Despite the animator still running, this should be false.
),
values
)
surfaceBehindIsAnimatingFlow.emit(false)
runCurrent()
assertEquals(
listOf(
false,
true,
false, // The animator ending should have no effect.
),
values
)
}
@Test
fun lockscreenVisibility_visibleWhenGone() =
testScope.runTest {
val values by collectValues(underTest.lockscreenVisibility)
// Start on LOCKSCREEN.
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.AOD,
to = KeyguardState.LOCKSCREEN,
)
)
runCurrent()
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.FINISHED,
from = KeyguardState.AOD,
to = KeyguardState.LOCKSCREEN,
)
)
runCurrent()
assertEquals(
listOf(
true, // Unsurprisingly, we should start with the lockscreen visible on
// LOCKSCREEN.
),
values
)
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
)
)
runCurrent()
assertEquals(
listOf(
true, // Lockscreen remains visible while we're transitioning to GONE.
),
values
)
transitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.FINISHED,
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
)
)
runCurrent()
assertEquals(
listOf(
true,
false, // Once we're fully GONE, the lockscreen should not be visible.
),
values
)
}
}

View File

@@ -0,0 +1,160 @@
/*
* 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.testing.TestableLooper.RunWithLooper
import android.view.RemoteAnimationTarget
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardViewController
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.AnimatorTestRule
import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertNull
import junit.framework.Assert.assertTrue
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.doAnswer
import org.mockito.MockitoAnnotations
@SmallTest
@RoboPilotTest
@RunWithLooper(setAsMainLooper = true)
@kotlinx.coroutines.ExperimentalCoroutinesApi
class KeyguardSurfaceBehindParamsApplierTest : SysuiTestCase() {
@get:Rule val animatorTestRule = AnimatorTestRule()
private lateinit var underTest: KeyguardSurfaceBehindParamsApplier
private lateinit var executor: FakeExecutor
@Mock private lateinit var keyguardViewController: KeyguardViewController
@Mock private lateinit var interactor: KeyguardSurfaceBehindInteractor
@Mock private lateinit var remoteAnimationTarget: RemoteAnimationTarget
private var isAnimatingSurface: Boolean? = null
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
executor = FakeExecutor(FakeSystemClock())
underTest =
KeyguardSurfaceBehindParamsApplier(
executor = executor,
keyguardViewController = keyguardViewController,
interactor = interactor,
)
doAnswer {
(it.arguments[0] as Boolean).let { animating -> isAnimatingSurface = animating }
}
.whenever(interactor)
.setAnimatingSurface(anyBoolean())
}
@After
fun tearDown() {
animatorTestRule.advanceTimeBy(1000.toLong())
}
@Test
fun testNotAnimating_setParamsWithNoAnimation() {
underTest.viewParams =
KeyguardSurfaceBehindModel(
alpha = 0.3f,
translationY = 300f,
)
// A surface has not yet been provided, so we shouldn't have set animating to false OR true
// just yet.
assertNull(isAnimatingSurface)
underTest.applyParamsToSurface(remoteAnimationTarget)
// We should now explicitly not be animating the surface.
assertFalse(checkNotNull(isAnimatingSurface))
}
@Test
fun testAnimating_paramsThenSurfaceProvided() {
underTest.viewParams =
KeyguardSurfaceBehindModel(
animateFromAlpha = 0f,
alpha = 0.3f,
animateFromTranslationY = 0f,
translationY = 300f,
)
// A surface has not yet been provided, so we shouldn't have set animating to false OR true
// just yet.
assertNull(isAnimatingSurface)
underTest.applyParamsToSurface(remoteAnimationTarget)
// We should now be animating the surface.
assertTrue(checkNotNull(isAnimatingSurface))
}
@Test
fun testAnimating_surfaceThenParamsProvided() {
underTest.applyParamsToSurface(remoteAnimationTarget)
// The default params (which do not animate) should have been applied, so we're explicitly
// NOT animating yet.
assertFalse(checkNotNull(isAnimatingSurface))
underTest.viewParams =
KeyguardSurfaceBehindModel(
animateFromAlpha = 0f,
alpha = 0.3f,
animateFromTranslationY = 0f,
translationY = 300f,
)
// We should now be animating the surface.
assertTrue(checkNotNull(isAnimatingSurface))
}
@Test
fun testAnimating_thenReleased_animatingIsFalse() {
underTest.viewParams =
KeyguardSurfaceBehindModel(
animateFromAlpha = 0f,
alpha = 0.3f,
animateFromTranslationY = 0f,
translationY = 300f,
)
underTest.applyParamsToSurface(remoteAnimationTarget)
assertTrue(checkNotNull(isAnimatingSurface))
underTest.notifySurfaceReleased()
// Releasing the surface should immediately cancel animators.
assertFalse(checkNotNull(isAnimatingSurface))
}
}

View File

@@ -0,0 +1,100 @@
/*
* 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.app.IActivityTaskManager
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.keyguard.WindowManagerLockscreenVisibilityManager
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
@SmallTest
@RoboPilotTest
@RunWith(AndroidJUnit4::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() {
private lateinit var underTest: WindowManagerLockscreenVisibilityManager
private lateinit var executor: FakeExecutor
@Mock private lateinit var activityTaskManagerService: IActivityTaskManager
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
executor = FakeExecutor(FakeSystemClock())
underTest =
WindowManagerLockscreenVisibilityManager(
executor = executor,
activityTaskManagerService = activityTaskManagerService,
keyguardStateController = keyguardStateController,
keyguardSurfaceBehindAnimator = keyguardSurfaceBehindAnimator,
)
}
@Test
fun testLockscreenVisible_andAodVisible() {
underTest.setLockscreenShown(true)
underTest.setAodVisible(true)
verify(activityTaskManagerService).setLockScreenShown(true, true)
verifyNoMoreInteractions(activityTaskManagerService)
}
@Test
fun testGoingAway_whenLockscreenVisible_thenSurfaceMadeVisible() {
underTest.setLockscreenShown(true)
underTest.setAodVisible(true)
verify(activityTaskManagerService).setLockScreenShown(true, true)
verifyNoMoreInteractions(activityTaskManagerService)
underTest.setSurfaceBehindVisibility(true)
verify(activityTaskManagerService).keyguardGoingAway(anyInt())
verifyNoMoreInteractions(activityTaskManagerService)
}
@Test
fun testSurfaceVisible_whenLockscreenNotShowing_doesNotTriggerGoingAway() {
underTest.setLockscreenShown(false)
underTest.setAodVisible(false)
verify(activityTaskManagerService).setLockScreenShown(false, false)
verifyNoMoreInteractions(activityTaskManagerService)
underTest.setSurfaceBehindVisibility(true)
verifyNoMoreInteractions(activityTaskManagerService)
}
}

View File

@@ -21,9 +21,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING

View File

@@ -31,7 +31,7 @@ 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.KeyguardInteractorFactory
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.statusbar.phone.SystemUIDialogManager
@@ -83,16 +83,21 @@ class UdfpsFingerprintViewModelTest : SysuiTestCase() {
bouncerRepository = FakeKeyguardBouncerRepository()
transitionRepository = FakeKeyguardTransitionRepository()
shadeRepository = FakeShadeRepository()
val transitionInteractor =
KeyguardTransitionInteractor(
transitionRepository,
testScope.backgroundScope,
)
val keyguardInteractor =
KeyguardInteractorFactory.create(
repository = keyguardRepository,
featureFlags = featureFlags,
)
.keyguardInteractor
val transitionInteractor =
KeyguardTransitionInteractorFactory.create(
scope = testScope.backgroundScope,
repository = transitionRepository,
keyguardInteractor = keyguardInteractor,
)
.keyguardTransitionInteractor
underTest =
FingerprintViewModel(
context,

View File

@@ -30,7 +30,7 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepos
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -98,15 +98,20 @@ class UdfpsLockscreenViewModelTest : SysuiTestCase() {
bouncerRepository = it.bouncerRepository
}
val transitionInteractor =
KeyguardTransitionInteractorFactory.create(
scope = testScope.backgroundScope,
repository = transitionRepository,
keyguardInteractor = keyguardInteractor,
)
.keyguardTransitionInteractor
underTest =
UdfpsLockscreenViewModel(
context,
lockscreenColorResId,
alternateBouncerResId,
KeyguardTransitionInteractor(
transitionRepository,
testScope.backgroundScope,
),
transitionInteractor,
UdfpsKeyguardInteractor(
configRepository,
BurnInInteractor(

View File

@@ -30,6 +30,7 @@ import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.keyguard.TestScopeProvider
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
@@ -37,6 +38,7 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -66,7 +68,6 @@ import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -152,7 +153,11 @@ class MediaCarouselControllerTest : SysuiTestCase() {
debugLogger,
mediaFlags,
keyguardUpdateMonitor,
KeyguardTransitionInteractor(transitionRepository, TestScope().backgroundScope),
KeyguardTransitionInteractorFactory.create(
scope = TestScopeProvider.getTestScope().backgroundScope,
repository = transitionRepository,
)
.keyguardTransitionInteractor,
globalSettings
)
verify(configurationController).addCallback(capture(configListener))

View File

@@ -35,6 +35,8 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
import android.service.trust.TrustAgentService;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -73,6 +75,8 @@ import com.android.systemui.dock.DockManager;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.TaskbarDelegate;
import com.android.systemui.plugins.ActivityStarter;
@@ -201,7 +205,10 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mBouncerView,
mAlternateBouncerInteractor,
mUdfpsOverlayInteractor,
mActivityStarter) {
mActivityStarter,
mock(KeyguardTransitionInteractor.class),
StandardTestDispatcher(null, null),
() -> mock(WindowManagerLockscreenVisibilityInteractor.class)) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
@@ -701,7 +708,10 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mBouncerView,
mAlternateBouncerInteractor,
mUdfpsOverlayInteractor,
mActivityStarter) {
mActivityStarter,
mock(KeyguardTransitionInteractor.class),
StandardTestDispatcher(null, null),
() -> mock(WindowManagerLockscreenVisibilityInteractor.class)) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;

View File

@@ -0,0 +1,29 @@
/*
* 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.data.repository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
class FakeKeyguardSurfaceBehindRepository : KeyguardSurfaceBehindRepository {
private val _isAnimatingSurface = MutableStateFlow(false)
override val isAnimatingSurface = _isAnimatingSurface.asStateFlow()
override fun setAnimatingSurface(animating: Boolean) {
_isAnimatingSurface.value = animating
}
}

View File

@@ -18,6 +18,8 @@ package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.util.mockito.mock
import dagger.Lazy
import kotlinx.coroutines.CoroutineScope
/**
@@ -30,18 +32,36 @@ object KeyguardTransitionInteractorFactory {
fun create(
scope: CoroutineScope,
repository: KeyguardTransitionRepository = FakeKeyguardTransitionRepository(),
keyguardInteractor: KeyguardInteractor =
KeyguardInteractorFactory.create().keyguardInteractor,
fromLockscreenTransitionInteractor: Lazy<FromLockscreenTransitionInteractor> = Lazy {
mock<FromLockscreenTransitionInteractor>()
},
fromPrimaryBouncerTransitionInteractor: Lazy<FromPrimaryBouncerTransitionInteractor> =
Lazy {
mock<FromPrimaryBouncerTransitionInteractor>()
},
): WithDependencies {
return WithDependencies(
repository = repository,
keyguardInteractor = keyguardInteractor,
fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor,
fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor,
KeyguardTransitionInteractor(
scope = scope,
repository = repository,
keyguardInteractor = { keyguardInteractor },
fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor,
fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor,
)
)
}
data class WithDependencies(
val repository: KeyguardTransitionRepository,
val keyguardInteractor: KeyguardInteractor,
val fromLockscreenTransitionInteractor: Lazy<FromLockscreenTransitionInteractor>,
val fromPrimaryBouncerTransitionInteractor: Lazy<FromPrimaryBouncerTransitionInteractor>,
val keyguardTransitionInteractor: KeyguardTransitionInteractor,
)
}