From b1b85467e81eb9799e0baa694229fe3c40faf949 Mon Sep 17 00:00:00 2001 From: Josh Tsuji Date: Mon, 31 Jul 2023 16:53:14 -0400 Subject: [PATCH] Refactor SysUI's interface with ATMS#setLockScreenShown/keyguardGoingAway. Bug: 278086361 Test: atest SystemUITests Merged-In: Iaacccacbbc408bf5d39b32016b981199aa36dcc0 Change-Id: Iaacccacbbc408bf5d39b32016b981199aa36dcc0 --- .../KeyguardSecurityContainerController.java | 8 + .../src/com/android/systemui/flags/Flags.kt | 13 + .../systemui/keyguard/KeyguardService.java | 33 +- .../KeyguardUnlockAnimationController.kt | 105 ++--- .../keyguard/KeyguardViewMediator.java | 106 +++-- ...indowManagerLockscreenVisibilityManager.kt | 206 +++++++++ .../keyguard/dagger/KeyguardModule.java | 12 +- .../DeviceEntryFaceAuthRepository.kt | 11 +- .../data/repository/KeyguardRepository.kt | 18 +- .../repository/KeyguardRepositoryModule.kt | 5 + .../KeyguardSurfaceBehindRepository.kt | 46 ++ ...romAlternateBouncerTransitionInteractor.kt | 2 +- .../FromLockscreenTransitionInteractor.kt | 96 +++- .../FromPrimaryBouncerTransitionInteractor.kt | 105 ++++- .../domain/interactor/KeyguardInteractor.kt | 22 +- .../KeyguardSurfaceBehindInteractor.kt | 89 ++++ .../KeyguardTransitionInteractor.kt | 69 ++- ...owManagerLockscreenVisibilityInteractor.kt | 162 +++++++ .../model/KeyguardSurfaceBehindModel.kt | 55 +++ .../KeyguardSurfaceBehindParamsApplier.kt | 219 ++++++++++ .../binder/KeyguardSurfaceBehindViewBinder.kt | 37 ++ ...owManagerLockscreenVisibilityViewBinder.kt | 57 +++ .../KeyguardSurfaceBehindViewModel.kt | 28 ++ ...dowManagerLockscreenVisibilityViewModel.kt | 31 ++ .../statusbar/phone/CentralSurfacesImpl.java | 2 +- .../phone/StatusBarKeyguardViewManager.java | 65 ++- .../systemui/util/kotlin/JavaAdapter.kt | 6 +- ...KeyguardSecurityContainerControllerTest.kt | 13 +- .../LockIconViewControllerBaseTest.java | 7 +- .../keyguard/KeyguardViewMediatorTest.java | 5 +- .../DeviceEntryFaceAuthRepositoryTest.kt | 7 +- .../repository/KeyguardRepositoryImplTest.kt | 9 +- ...KeyguardSurfaceBehindRepositoryImplTest.kt | 60 +++ .../FromLockscreenTransitionInteractorTest.kt | 190 ++++++++ ...mPrimaryBouncerTransitionInteractorTest.kt | 214 +++++++++ .../KeyguardSurfaceBehindInteractorTest.kt | 169 +++++++ .../KeyguardTransitionInteractorTestCase.kt | 71 +++ .../KeyguardTransitionScenariosTest.kt | 13 +- ...nagerLockscreenVisibilityInteractorTest.kt | 412 ++++++++++++++++++ .../KeyguardSurfaceBehindParamsApplierTest.kt | 160 +++++++ ...wManagerLockscreenVisibilityManagerTest.kt | 100 +++++ ...mingToLockscreenTransitionViewModelTest.kt | 2 - .../UdfpsFingerprintViewModelTest.kt | 17 +- .../viewmodel/UdfpsLockscreenViewModelTest.kt | 15 +- .../ui/MediaCarouselControllerTest.kt | 9 +- .../StatusBarKeyguardViewManagerTest.java | 14 +- .../FakeKeyguardSurfaceBehindRepository.kt | 29 ++ .../KeyguardTransitionInteractorFactory.kt | 20 + 48 files changed, 2985 insertions(+), 159 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepository.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSurfaceBehindViewModel.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/WindowManagerLockscreenVisibilityViewModel.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt create mode 100644 packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSurfaceBehindRepository.kt diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 99a741153ccc3..4553a56f599c7 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -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 javaAdapter, UserInteractor userInteractor, FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate, + KeyguardTransitionInteractor keyguardTransitionInteractor, Provider authenticationInteractor ) { super(view); @@ -455,6 +462,7 @@ public class KeyguardSecurityContainerController extends ViewController - 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...") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index d2047d981ffc8..a55cfa7c066d5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -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 mWmLockscreenVisibilityManager; + /** * Injected constructor. See {@link KeyguardModule}. */ @@ -1377,7 +1385,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, SystemClock systemClock, @Main CoroutineDispatcher mainDispatcher, Lazy dreamingToLockscreenTransitionViewModel, - SystemPropertiesHelper systemPropertiesHelper) { + SystemPropertiesHelper systemPropertiesHelper, + Lazy 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; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt new file mode 100644 index 0000000000000..75677f2d9e9a8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt @@ -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, + wallpapers: Array, + nonApps: Array, + 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 + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 4205ed22ff243..dc19ea2515469 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -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, - SystemPropertiesHelper systemPropertiesHelper) { + SystemPropertiesHelper systemPropertiesHelper, + Lazy wmLockscreenVisibilityManager) { return new KeyguardViewMediator( context, uiEventLogger, @@ -186,7 +187,8 @@ public class KeyguardModule { systemClock, mainDispatcher, dreamingToLockscreenTransitionViewModel, - systemPropertiesHelper); + systemPropertiesHelper, + wmLockscreenVisibilityManager); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt index ff5094e276959..689414711388d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt @@ -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) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index e35c3699aabf9..42cd3a576926f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -99,7 +99,16 @@ interface KeyguardRepository { /** Is an activity showing over the keyguard? */ val isKeyguardOccluded: Flow - /** 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 /** 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 = conflatedCallbackFlow { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt index 246ee33fa2e8d..2f8010620e5e8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepository.kt new file mode 100644 index 0000000000000..014b7fa0aeebb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepository.kt @@ -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 + + /** 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 + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt index 8f0b91b6d864b..271bc38899ec2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index 6b28b270032e9..aa771fd16af85 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -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 = + 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 = + 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 } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt index 9142d1faa90a1..c9f32dabe736a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt @@ -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 = + 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 = + 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 } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 53d3c076247e4..562c4db44be6b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -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 + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt new file mode 100644 index 0000000000000..bf04f8f824306 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt @@ -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 = + 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 = + 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) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index 8c4c7ae53ce66..93826183a1836 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -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, + private val fromLockscreenTransitionInteractor: dagger.Lazy, + private val fromPrimaryBouncerTransitionInteractor: + dagger.Lazy, ) { private val TAG = this::class.simpleName @@ -128,12 +136,11 @@ constructor( repository.transition(PRIMARY_BOUNCER, GONE) /** OFF->LOCKSCREEN transition information. */ - val offToLockscreenTransition: Flow = - repository.transition(KeyguardState.OFF, LOCKSCREEN) + val offToLockscreenTransition: Flow = repository.transition(OFF, LOCKSCREEN) /** DOZING->LOCKSCREEN transition information. */ val dozingToLockscreenTransition: Flow = - 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 = 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 = startedKeyguardTransitionStep .map { step -> step.to } - .stateIn(scope, SharingStarted.Eagerly, KeyguardState.OFF) + .stateIn(scope, SharingStarted.Eagerly, OFF) /** The last completed [KeyguardState] transition */ val finishedKeyguardState: StateFlow = 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 { + return repository.transitions.filter { step -> step.from == fromState } + } + + fun transitionStepsToState(toState: KeyguardState): Flow { + 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 { + return combine( + startedKeyguardTransitionStep, + finishedKeyguardState, + ) { startedStep, finishedState -> + startedStep.to == state && finishedState != state + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt new file mode 100644 index 0000000000000..96bfdc600a719 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.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 = + 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 = + 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 = + 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 = + 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 = + 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 + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt new file mode 100644 index 0000000000000..7fb5cfd8f9309 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.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 + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt new file mode 100644 index 0000000000000..fe2df21a35e1e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt @@ -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() + ) + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt new file mode 100644 index 0000000000000..599f69f4bc381 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt @@ -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 } } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt new file mode 100644 index 0000000000000..fc0c78a6f76b4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt @@ -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) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSurfaceBehindViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSurfaceBehindViewModel.kt new file mode 100644 index 0000000000000..4f52962025150 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSurfaceBehindViewModel.kt @@ -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 +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/WindowManagerLockscreenVisibilityViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/WindowManagerLockscreenVisibilityViewModel.kt new file mode 100644 index 0000000000000..f797640fc48cf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/WindowManagerLockscreenVisibilityViewModel.kt @@ -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 +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 632f241ddfffe..127569d179fe4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -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(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 5c1dfbe42cf98..ea57eb46bab4f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -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 mCallbacks = new HashSet<>(); private boolean mIsBackAnimationEnabled; private final boolean mUdfpsNewTouchDetectionEnabled; @@ -326,6 +336,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } } }; + private Lazy 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 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. */ diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt index 12d7b4d372e83..0d0a6469d6745 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt @@ -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 collectFlow( repeatOnLifecycle(state) { flow.collect { consumer.accept(it) } } } } + +fun combineFlows(flow1: Flow, flow2: Flow, bifunction: (A, B) -> R): Flow { + return combine(flow1, flow2, bifunction) +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 8f27b59487f48..2aaeef81a6959 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -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 @@ -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 diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java index 956e0b81e6cef..b18137c47f5c5 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java @@ -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 diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 8ae9989570e9f..b05de48ae224d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -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); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index 85ee0e4d6ec36..04ebbf61a75ce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt @@ -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, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index 5e3376a45488e..5ead16bdc10f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -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() - 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() - 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() - 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() - verify(keyguardStateController).addCallback(captor.capture()) + verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture()) whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(true) captor.value.onKeyguardGoingAwayChanged() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt new file mode 100644 index 0000000000000..bed959f1a3f00 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.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)) + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt new file mode 100644 index 0000000000000..e2bf2f82b1c45 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt @@ -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? = + 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.") + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt new file mode 100644 index 0000000000000..85bc374651e00 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt @@ -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? = + 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.") + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt new file mode 100644 index 0000000000000..fdcc66b6c9740 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt @@ -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) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt new file mode 100644 index 0000000000000..8db19aef1aa6d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt @@ -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? = + null + open var fromPrimaryBouncerTransitionInteractorLazy: + Lazy? = + 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 + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index aa6bd4e85e242..4b221a0e81bf7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -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() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt new file mode 100644 index 0000000000000..73ecae5af9b3c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt @@ -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(false) + private val primaryBouncerSurfaceVisibilityFlow = MutableStateFlow(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 + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt new file mode 100644 index 0000000000000..a22f603ae92ae --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt @@ -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)) + } +} \ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt new file mode 100644 index 0000000000000..623c8771b3d21 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt @@ -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) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt index c67f535199577..bfc6f31c57db5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt @@ -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 diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt index 80ab418fbd303..0ad14d029e400 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt @@ -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, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt index 0456824abfbc6..edcaa1d65f49f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt @@ -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( diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt index 5b8272b04bfb7..ae0a3349fdfc4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt @@ -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)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index ed9cf3f2f158f..0da736003e22a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -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; diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSurfaceBehindRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSurfaceBehindRepository.kt new file mode 100644 index 0000000000000..823f29a910d13 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSurfaceBehindRepository.kt @@ -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 + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt index 312ade510784e..23faaf3ef9578 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt @@ -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 = Lazy { + mock() + }, + fromPrimaryBouncerTransitionInteractor: Lazy = + Lazy { + mock() + }, ): 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, + val fromPrimaryBouncerTransitionInteractor: Lazy, val keyguardTransitionInteractor: KeyguardTransitionInteractor, ) }