diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java index 015c4e44185bd..ecc8c0074e181 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java @@ -145,6 +145,13 @@ public interface KeyguardViewController { */ void startPreHideAnimation(Runnable finishRunnable); + /** + * Blocks the current touch gesture from affecting the expansion amount of the notification + * panel. This is used after a completed unlock gesture to ignore further dragging before an + * ACTION_UP. + */ + void blockPanelExpansionFromCurrentTouch(); + /** * @return the ViewRootImpl of the View where the Keyguard is mounted. */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 97803c1cf2fd5..666afed41c351 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -70,7 +70,7 @@ public class KeyguardService extends Service { /** * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY */ - private static boolean sEnableRemoteKeyguardAnimation = + static boolean sEnableRemoteKeyguardAnimation = SystemProperties.getBoolean(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, false); private final KeyguardViewMediator mKeyguardViewMediator; @@ -138,6 +138,7 @@ public class KeyguardService extends Service { @Override // Binder interface public void onAnimationCancelled() { + mKeyguardViewMediator.cancelKeyguardExitAnimation(); } }; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt new file mode 100644 index 0000000000000..411c328cd3101 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2021 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.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator +import android.content.Context +import android.graphics.Matrix +import android.view.RemoteAnimationTarget +import android.view.SyncRtSurfaceTransactionApplier +import androidx.core.math.MathUtils +import com.android.internal.R +import com.android.keyguard.KeyguardViewController +import com.android.systemui.animation.Interpolators +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.policy.KeyguardStateController +import dagger.Lazy +import javax.inject.Inject + +/** + * Starting scale factor for the app/launcher surface behind the keyguard, when it's animating + * in during keyguard exit. + */ +const val SURFACE_BEHIND_START_SCALE_FACTOR = 0.95f + +/** + * How much to translate the surface behind the keyguard at the beginning of the exit animation, + * in terms of percentage of the surface's height. + */ +const val SURFACE_BEHIND_START_TRANSLATION_Y = 0.05f + +/** + * Y coordinate of the pivot point for the scale effect on the surface behind the keyguard. This + * is expressed as percentage of the surface's height, so 0.66f means the surface will scale up + * from the point at (width / 2, height * 0.66). + */ +const val SURFACE_BEHIND_SCALE_PIVOT_Y = 0.66f + +/** + * Dismiss amount at which to fade in the surface behind the keyguard. The surface will then animate + * along with the dismiss amount until [DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD] is reached. + * + * The dismiss amount is the inverse of the notification panel expansion, which decreases as the + * lock screen is swiped away. + */ +const val DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD = 0.1f + +/** + * Dismiss amount at which to complete the keyguard exit animation and hide the keyguard. + * + * The dismiss amount is the inverse of the notification panel expansion, which decreases as the + * lock screen is swiped away. + */ +const val DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD = 0.3f + +/** + * Initiates, controls, and ends the keyguard unlock animation. + * + * The unlock animation transitions between the keyguard (lock screen) and the app/launcher surface + * behind the keyguard. If the user is swiping away the keyguard, this controller will decide when + * to animate in the surface, and synchronize its appearance with the swipe gesture. If the keyguard + * is animating away via a canned animation (due to biometric unlock, tapping a notification, etc.) + * this controller will play a canned animation on the surface as well. + * + * The surface behind the keyguard is manipulated via a RemoteAnimation passed to + * [notifyStartKeyguardExitAnimation] by [KeyguardViewMediator]. + */ +@SysUISingleton +class KeyguardUnlockAnimationController @Inject constructor( + context: Context, + private val keyguardStateController: KeyguardStateController, + private val keyguardViewMediator: Lazy, + private val keyguardViewController: KeyguardViewController +) : KeyguardStateController.Callback { + + /** + * Information used to start, run, and finish a RemoteAnimation on the app or launcher surface + * behind the keyguard. + * + * If we're swiping to unlock, the "animation" is controlled via the gesture, tied to the + * dismiss amounts received in [onKeyguardDismissAmountChanged]. It does not have a fixed + * duration, and it ends when the gesture reaches a certain threshold or is cancelled. + * + * If we're unlocking via biometrics, PIN entry, or from clicking a notification, a canned + * animation is started in [notifyStartKeyguardExitAnimation]. + */ + private var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier? = null + private var surfaceBehindRemoteAnimationTarget: RemoteAnimationTarget? = null + private var surfaceBehindRemoteAnimationStartTime: Long = 0 + + /** + * Alpha value applied to [surfaceBehindRemoteAnimationTarget], which is the surface of the + * app/launcher behind the keyguard. + * + * If we're doing a swipe gesture, we fade in the surface when the swipe passes a certain + * threshold. If we're doing a canned animation, it'll be faded in while a translate/scale + * animation plays. + */ + private var surfaceBehindAlpha = 1f + private var surfaceBehindAlphaAnimator = ValueAnimator.ofFloat(0f, 1f) + + /** + * Matrix applied to [surfaceBehindRemoteAnimationTarget], which is the surface of the + * app/launcher behind the keyguard. + * + * This is used during the unlock animation/swipe gesture to scale and translate the surface. + */ + private val surfaceBehindMatrix = Matrix() + + /** + * Animator that animates in the surface behind the keyguard. This is used to play a canned + * animation on the surface, if we're not doing a swipe gesture. + */ + private val surfaceBehindEntryAnimator = ValueAnimator.ofFloat(0f, 1f) + + /** Rounded corner radius to apply to the surface behind the keyguard. */ + private var roundedCornerRadius = 0f + + init { + surfaceBehindAlphaAnimator.duration = 150 + surfaceBehindAlphaAnimator.interpolator = Interpolators.ALPHA_IN + surfaceBehindAlphaAnimator.addUpdateListener { valueAnimator: ValueAnimator -> + surfaceBehindAlpha = valueAnimator.animatedValue as Float + updateSurfaceBehindAppearAmount() + } + surfaceBehindAlphaAnimator.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + // If the surface alpha is 0f, it's no longer visible so we can safely be done with + // the animation. + if (surfaceBehindAlpha == 0f) { + keyguardViewMediator.get().finishSurfaceBehindRemoteAnimation() + } + } + }) + + surfaceBehindEntryAnimator.duration = 450 + surfaceBehindEntryAnimator.interpolator = Interpolators.DECELERATE_QUINT + surfaceBehindEntryAnimator.addUpdateListener { valueAnimator: ValueAnimator -> + surfaceBehindAlpha = valueAnimator.animatedValue as Float + setSurfaceBehindAppearAmount(valueAnimator.animatedValue as Float) + } + surfaceBehindEntryAnimator.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished() + } + }) + + // Listen for changes in the dismiss amount. + keyguardStateController.addCallback(this) + + roundedCornerRadius = + context.resources.getDimensionPixelSize(R.dimen.rounded_corner_radius).toFloat() + } + + /** + * Called from [KeyguardViewMediator] to tell us that the RemoteAnimation on the surface behind + * the keyguard has started successfully. We can use these parameters to directly manipulate the + * surface for the unlock gesture/animation. + * + * When we're done with it, we'll call [KeyguardViewMediator.finishSurfaceBehindRemoteAnimation] + * to end the RemoteAnimation. + * + * [requestedShowSurfaceBehindKeyguard] denotes whether the exit animation started because of a + * call to [KeyguardViewMediator.showSurfaceBehindKeyguard], as happens during a swipe gesture, + * as opposed to the keyguard hiding. + */ + fun notifyStartKeyguardExitAnimation( + target: RemoteAnimationTarget, + startTime: Long, + requestedShowSurfaceBehindKeyguard: Boolean + ) { + + if (surfaceTransactionApplier == null) { + surfaceTransactionApplier = SyncRtSurfaceTransactionApplier( + keyguardViewController.viewRootImpl.view) + } + + surfaceBehindRemoteAnimationTarget = target + surfaceBehindRemoteAnimationStartTime = startTime + + // If the surface behind wasn't made visible during a swipe, we'll do a canned animation + // to animate it in. Otherwise, the swipe touch events will continue animating it. + if (!requestedShowSurfaceBehindKeyguard) { + keyguardViewController.hide(startTime, 350) + surfaceBehindEntryAnimator.start() + } + } + + fun notifyCancelKeyguardExitAnimation() { + surfaceBehindRemoteAnimationTarget = null + } + + fun notifyFinishedKeyguardExitAnimation() { + surfaceBehindRemoteAnimationTarget = null + } + + fun hideKeyguardViewAfterRemoteAnimation() { + keyguardViewController.hide(surfaceBehindRemoteAnimationStartTime, 350) + } + + /** + * Scales in and translates up the surface behind the keyguard. This is used during unlock + * animations and swipe gestures to animate the surface's entry (and exit, if the swipe is + * cancelled). + */ + private fun setSurfaceBehindAppearAmount(amount: Float) { + if (surfaceBehindRemoteAnimationTarget == null) { + return + } + + val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.height() + val scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR + + (1f - SURFACE_BEHIND_START_SCALE_FACTOR) * + MathUtils.clamp(amount, 0f, 1f)) + + // Scale up from a point at the center-bottom of the surface. + surfaceBehindMatrix.setScale( + scaleFactor, + scaleFactor, + surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.width() / 2f, + surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y) + + // Translate up from the bottom. + surfaceBehindMatrix.postTranslate(0f, + surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)) + + // If we're snapping the keyguard back, immediately begin fading it out. + val animationAlpha = + if (keyguardStateController.isSnappingKeyguardBackAfterSwipe) amount + else surfaceBehindAlpha + + val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder( + surfaceBehindRemoteAnimationTarget!!.leash) + .withMatrix(surfaceBehindMatrix) + .withCornerRadius(roundedCornerRadius) + .withAlpha(animationAlpha) + .build() + surfaceTransactionApplier!!.scheduleApply(params) + } + + /** + * Sets the appearance amount of the surface behind the keyguard, according to the current + * keyguard dismiss amount and the method of dismissal. + */ + private fun updateSurfaceBehindAppearAmount() { + if (surfaceBehindRemoteAnimationTarget == null) { + return + } + + // For fling animations, we want to animate the surface in over the full distance. If we're + // dismissing the keyguard via a swipe gesture (or cancelling the swipe gesture), we want to + // bring in the surface behind over a relatively short swipe distance (~15%), to keep the + // interaction tight. + if (keyguardStateController.isFlingingToDismissKeyguard) { + setSurfaceBehindAppearAmount(keyguardStateController.dismissAmount) + } else if (keyguardStateController.isDismissingFromSwipe || + keyguardStateController.isSnappingKeyguardBackAfterSwipe) { + val totalSwipeDistanceToDismiss = + (DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD) + val swipedDistanceSoFar: Float = + keyguardStateController.dismissAmount - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD + val progress = swipedDistanceSoFar / totalSwipeDistanceToDismiss + setSurfaceBehindAppearAmount(progress) + } + } + + override fun onKeyguardDismissAmountChanged() { + if (!KeyguardService.sEnableRemoteKeyguardAnimation) { + return + } + + val dismissAmount = keyguardStateController.dismissAmount + + // Hide the keyguard if we're fully dismissed, or if we're swiping to dismiss and have + // crossed the threshold to finish the dismissal. + val reachedHideKeyguardThreshold = (dismissAmount >= 1f || + (keyguardStateController.isDismissingFromSwipe && + // Don't hide if we're flinging during a swipe, since we need to finish + // animating it out. This will be called again after the fling ends. + !keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture && + dismissAmount >= DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD)) + + if (dismissAmount >= DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD && + !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) { + // We passed the threshold, and we're not yet showing the surface behind the keyguard. + // Animate it in. + keyguardViewMediator.get().showSurfaceBehindKeyguard() + fadeInSurfaceBehind() + } else if (dismissAmount < DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD && + keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) { + // We're no longer past the threshold but we are showing the surface. Animate it out. + keyguardViewMediator.get().hideSurfaceBehindKeyguard() + fadeOutSurfaceBehind() + } else if (keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe && + reachedHideKeyguardThreshold) { + keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished() + } + + if (keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() || + keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) { + updateSurfaceBehindAppearAmount() + } + } + + private fun fadeInSurfaceBehind() { + surfaceBehindAlphaAnimator.cancel() + surfaceBehindAlphaAnimator.start() + } + + private fun fadeOutSurfaceBehind() { + surfaceBehindAlphaAnimator.cancel() + surfaceBehindAlphaAnimator.reverse() + } +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 70459df3dafb2..48f9a58d7d1ac 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -17,6 +17,7 @@ package com.android.systemui.keyguard; import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; +import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; @@ -72,7 +73,6 @@ import android.util.SparseIntArray; import android.view.IRemoteAnimationFinishedCallback; import android.view.RemoteAnimationTarget; import android.view.SyncRtSurfaceTransactionApplier; -import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; @@ -109,6 +109,7 @@ import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.DeviceConfigProxy; import java.io.FileDescriptor; @@ -195,6 +196,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, private static final int NOTIFY_SCREEN_TURNED_OFF = 16; private static final int NOTIFY_STARTED_GOING_TO_SLEEP = 17; private static final int SYSTEM_READY = 18; + private static final int CANCEL_KEYGUARD_EXIT_ANIM = 19; /** * The default amount of time we stay awake (used for all key input) @@ -350,7 +352,6 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, private int mUnlockSoundId; private int mTrustedSoundId; private int mLockSoundStreamId; - /** * The animation used for hiding keyguard. This is used to fetch the animation timings if * WindowManager is not providing us with them. @@ -400,6 +401,29 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, private IKeyguardDrawnCallback mDrawnCallback; private CharSequence mCustomMessage; + /** + * Whether the RemoteAnimation on the app/launcher surface behind the keyguard is 'running'. + * Note that this does not necessarily mean the surface is currently in motion - we may be + * 'animating' it along with the user's finger during a swipe to unlock gesture, a gesture that + * can be paused or reversed. + */ + private boolean mSurfaceBehindRemoteAnimationRunning; + + /** + * Whether we've asked to make the app/launcher surface behind the keyguard visible, via a call + * to {@link android.app.IActivityTaskManager#keyguardGoingAway(int)}. + * + * Since that's an IPC, this doesn't necessarily mean the remote animation has started yet. + * {@link #mSurfaceBehindRemoteAnimationRunning} will be true if the call completed and the + * animation is now running. + */ + private boolean mSurfaceBehindRemoteAnimationRequested = false; + + /** + * Callback to run to end the RemoteAnimation on the app/launcher surface behind the keyguard. + */ + private IRemoteAnimationFinishedCallback mSurfaceBehindRemoteAnimationFinishedCallback; + private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener = new DeviceConfig.OnPropertiesChangedListener() { @Override @@ -735,6 +759,9 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, private DeviceConfigProxy mDeviceConfig; private DozeParameters mDozeParameters; + private final KeyguardStateController mKeyguardStateController; + private final Lazy mKeyguardUnlockAnimationControllerLazy; + /** * Injected constructor. See {@link KeyguardModule}. */ @@ -752,7 +779,9 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, NavigationModeController navigationModeController, KeyguardDisplayManager keyguardDisplayManager, DozeParameters dozeParameters, - StatusBarStateController statusBarStateController) { + StatusBarStateController statusBarStateController, + KeyguardStateController keyguardStateController, + Lazy keyguardUnlockAnimationControllerLazy) { super(context); mFalsingCollector = falsingCollector; mLockPatternUtils = lockPatternUtils; @@ -780,6 +809,9 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, })); mDozeParameters = dozeParameters; statusBarStateController.addCallback(this); + + mKeyguardStateController = keyguardStateController; + mKeyguardUnlockAnimationControllerLazy = keyguardUnlockAnimationControllerLazy; } public void userActivity() { @@ -1732,6 +1764,12 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, mFalsingCollector.onSuccessfulUnlock(); Trace.endSection(); break; + case CANCEL_KEYGUARD_EXIT_ANIM: + Trace.beginSection( + "KeyguardViewMediator#handleMessage CANCEL_KEYGUARD_EXIT_ANIM"); + handleCancelKeyguardExitAnimation(); + Trace.endSection(); + break; case KEYGUARD_DONE_PENDING_TIMEOUT: Trace.beginSection("KeyguardViewMediator#handleMessage" + " KEYGUARD_DONE_PENDING_TIMEOUT"); @@ -1943,7 +1981,8 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, int flags = 0; if (mKeyguardViewControllerLazy.get().shouldDisableWindowAnimationsForUnlock() - || (mWakeAndUnlocking && !mPulsing)) { + || (mWakeAndUnlocking && !mPulsing) + || isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe()) { flags |= WindowManagerPolicyConstants .KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS; } @@ -1952,7 +1991,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE; } if (mKeyguardViewControllerLazy.get().isUnlockWithWallpaper()) { - flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER; + flags |= KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER; } if (mKeyguardViewControllerLazy.get().shouldSubtleWindowAnimationsForUnlock()) { flags |= WindowManagerPolicyConstants @@ -2033,8 +2072,13 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, + " fadeoutDuration=" + fadeoutDuration); synchronized (KeyguardViewMediator.this) { - if (!mHiding) { - // Tell ActivityManager that we canceled the keyguardExitAnimation. + // Tell ActivityManager that we canceled the keyguard animation if + // handleStartKeyguardExitAnimation was called but we're not hiding the keyguard, unless + // we're animating the surface behind the keyguard and will be hiding the keyguard + // shortly. + if (!mHiding + && !mSurfaceBehindRemoteAnimationRequested + && !mKeyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture()) { setShowingLocked(mShowing, true /* force */); return; } @@ -2056,61 +2100,184 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, playSounds(false); } - setShowingLocked(false); - mWakeAndUnlocking = false; - mDismissCallbackRegistry.notifyDismissSucceeded(); - mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration); + if (KeyguardService.sEnableRemoteKeyguardAnimation) { + mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback; + mSurfaceBehindRemoteAnimationRunning = true; - // 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 - // supported, so it's always null. - mContext.getMainExecutor().execute(() -> { - if (finishedCallback == null) { + if (apps != null && apps.length > 0) { + // Pass the surface and metadata to the unlock animation controller. + mKeyguardUnlockAnimationControllerLazy.get().notifyStartKeyguardExitAnimation( + apps[0], startTime, mSurfaceBehindRemoteAnimationRequested); + } else { + // We weren't given any surfaces to animate, so just finish. + onKeyguardExitRemoteAnimationFinished(); return; } + } else { + setShowingLocked(false); + mWakeAndUnlocking = false; + mDismissCallbackRegistry.notifyDismissSucceeded(); + mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration); - // TODO(bc-unlock): Sample animation, just to apply alpha animation on the app. - final SyncRtSurfaceTransactionApplier applier = new SyncRtSurfaceTransactionApplier( - mKeyguardViewControllerLazy.get().getViewRootImpl().getView()); - final RemoteAnimationTarget primary = apps[0]; - ValueAnimator anim = ValueAnimator.ofFloat(0, 1); - anim.setDuration(400 /* duration */); - anim.setInterpolator(Interpolators.LINEAR); - anim.addUpdateListener((ValueAnimator animation) -> { - SurfaceParams params = new SurfaceParams.Builder(primary.leash) - .withAlpha(animation.getAnimatedFraction()) - .build(); - applier.scheduleApply(params); - }); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - try { - finishedCallback.onAnimationFinished(); - } catch (RemoteException e) { - Slog.e(TAG, "RemoteException"); - } + // 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 + // supported, so it's always null. + mContext.getMainExecutor().execute(() -> { + if (finishedCallback == null) { + return; } - @Override - public void onAnimationCancel(Animator animation) { - try { - finishedCallback.onAnimationFinished(); - } catch (RemoteException e) { - Slog.e(TAG, "RemoteException"); + // TODO(bc-unlock): Sample animation, just to apply alpha animation on the app. + final SyncRtSurfaceTransactionApplier applier = + new SyncRtSurfaceTransactionApplier( + mKeyguardViewControllerLazy.get().getViewRootImpl().getView()); + final RemoteAnimationTarget primary = apps[0]; + ValueAnimator anim = ValueAnimator.ofFloat(0, 1); + anim.setDuration(400 /* duration */); + anim.setInterpolator(Interpolators.LINEAR); + anim.addUpdateListener((ValueAnimator animation) -> { + SyncRtSurfaceTransactionApplier.SurfaceParams params = + new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder( + primary.leash) + .withAlpha(animation.getAnimatedFraction()) + .build(); + applier.scheduleApply(params); + }); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + try { + finishedCallback.onAnimationFinished(); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException"); + } } - } + + @Override + public void onAnimationCancel(Animator animation) { + try { + finishedCallback.onAnimationFinished(); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException"); + } + } + }); + anim.start(); }); - anim.start(); - }); - resetKeyguardDonePendingLocked(); - mHideAnimationRun = false; - adjustStatusBarLocked(); - sendUserPresentBroadcast(); + resetKeyguardDonePendingLocked(); + mHideAnimationRun = false; + adjustStatusBarLocked(); + sendUserPresentBroadcast(); + } } + Trace.endSection(); } + /** + * Whether we're currently animating between the keyguard and the app/launcher surface behind + * it, or will be shortly (which happens if we started a fling to dismiss the keyguard). + */ + public boolean isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe() { + return mSurfaceBehindRemoteAnimationRunning + || mKeyguardStateController.isFlingingToDismissKeyguard(); + } + + /** + * Called if the keyguard exit animation has been cancelled and we should return to the + * keyguard. + * + * This can happen due to the system cancelling the RemoteAnimation (due to a timeout), or the + * user cancelling the unlock swipe gesture. + */ + private void handleCancelKeyguardExitAnimation() { + hideSurfaceBehindKeyguard(); + mKeyguardUnlockAnimationControllerLazy.get().notifyCancelKeyguardExitAnimation(); + } + + /** + * Called when we're done running the keyguard exit animation. + * + * This will call {@link #mSurfaceBehindRemoteAnimationFinishedCallback} to let WM know that + * we're done with the RemoteAnimation, actually hide the keyguard, and clean up state related + * to the keyguard exit animation. + */ + public void onKeyguardExitRemoteAnimationFinished() { + if (!mSurfaceBehindRemoteAnimationRunning && !mSurfaceBehindRemoteAnimationRequested) { + return; + } + + // Block the panel from expanding, in case we were doing a swipe to dismiss gesture. + mKeyguardViewControllerLazy.get().blockPanelExpansionFromCurrentTouch(); + final boolean wasShowing = mShowing; + setShowingLocked(false); + + mWakeAndUnlocking = false; + mDismissCallbackRegistry.notifyDismissSucceeded(); + + if (mKeyguardStateController.isDismissingFromSwipe() || !wasShowing) { + mKeyguardUnlockAnimationControllerLazy.get().hideKeyguardViewAfterRemoteAnimation(); + } + + finishSurfaceBehindRemoteAnimation(); + + resetKeyguardDonePendingLocked(); + mHideAnimationRun = false; + adjustStatusBarLocked(); + sendUserPresentBroadcast(); + mSurfaceBehindRemoteAnimationRequested = false; + mKeyguardUnlockAnimationControllerLazy.get().notifyFinishedKeyguardExitAnimation(); + } + + /** + * Tells the ActivityTaskManager that the keyguard is planning to go away, so that it makes the + * surface behind the keyguard visible and calls {@link #handleStartKeyguardExitAnimation} with + * the parameters needed to animate the surface. + */ + public void showSurfaceBehindKeyguard() { + mSurfaceBehindRemoteAnimationRequested = true; + + try { + ActivityTaskManager.getService().keyguardGoingAway( + WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS + | WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER); + } catch (RemoteException e) { + mSurfaceBehindRemoteAnimationRequested = false; + e.printStackTrace(); + } + } + + /** Hides the surface behind the keyguard by re-showing the keyguard/activity lock screen. */ + public void hideSurfaceBehindKeyguard() { + mSurfaceBehindRemoteAnimationRequested = false; + + if (mShowing) { + setShowingLocked(true, true); + } + } + + /** + * Whether we have requested to show the surface behind the keyguard, even if it's not yet + * visible due to IPC delay. + */ + public boolean requestedShowSurfaceBehindKeyguard() { + return mSurfaceBehindRemoteAnimationRequested; + } + + /** If it's running, finishes the RemoteAnimation on the surface behind the keyguard. */ + public void finishSurfaceBehindRemoteAnimation() { + mSurfaceBehindRemoteAnimationRunning = false; + + if (mSurfaceBehindRemoteAnimationFinishedCallback != null) { + try { + mSurfaceBehindRemoteAnimationFinishedCallback.onAnimationFinished(); + mSurfaceBehindRemoteAnimationFinishedCallback = null; + } catch (RemoteException e) { + e.printStackTrace(); + } + } + } + private void adjustStatusBarLocked() { adjustStatusBarLocked(false /* forceHideHomeRecentsButtons */, false /* forceClearFlags */); @@ -2346,6 +2513,19 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, Trace.endSection(); } + /** + * Cancel the keyguard exit animation, usually because we were swiping to unlock and the swipe + * gesture was cancelled. + * + * This will re-show the keyguard and animate out the app/launcher surface behind the keyguard. + */ + public void cancelKeyguardExitAnimation() { + Trace.beginSection("KeyguardViewMediator#cancelKeyguardExitAnimation"); + Message msg = mHandler.obtainMessage(CANCEL_KEYGUARD_EXIT_ANIM); + mHandler.sendMessage(msg); + Trace.endSection(); + } + public void onShortPowerPressedGoHome() { // do nothing } 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 a747edd0580a7..ecee1b508ce01 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -42,6 +42,7 @@ import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.keyguard.FaceAuthScreenBrightnessController; +import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -49,6 +50,7 @@ import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardLiftController; import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.util.settings.GlobalSettings; @@ -92,7 +94,9 @@ public class KeyguardModule { NavigationModeController navigationModeController, KeyguardDisplayManager keyguardDisplayManager, DozeParameters dozeParameters, - StatusBarStateController statusBarStateController) { + StatusBarStateController statusBarStateController, + KeyguardStateController keyguardStateController, + Lazy keyguardUnlockAnimationController) { return new KeyguardViewMediator( context, falsingCollector, @@ -109,7 +113,9 @@ public class KeyguardModule { navigationModeController, keyguardDisplayManager, dozeParameters, - statusBarStateController + statusBarStateController, + keyguardStateController, + keyguardUnlockAnimationController ); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 40680dc493b58..aac4ada37eb70 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -1374,6 +1374,7 @@ public class NotificationPanelViewController extends PanelViewController { protected void flingToHeight(float vel, boolean expand, float target, float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) { mHeadsUpTouchHelper.notifyFling(!expand); + mKeyguardStateController.notifyPanelFlingStart(!expand /* flingingToDismiss */); setClosingWithAlphaFadeout(!expand && !isOnKeyguard() && getFadeoutAlpha() == 1.0f); super.flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java index 0c8122cc9afb6..388d72da808d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java @@ -256,7 +256,12 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW state.mScrimsVisibility == ScrimController.OPAQUE; final boolean keyguardOrAod = state.mKeyguardShowing || (state.mDozing && mDozeParameters.getAlwaysOn()); - if (keyguardOrAod && !state.mBackdropShowing && !scrimsOccludingWallpaper) { + if ((keyguardOrAod && !state.mBackdropShowing && !scrimsOccludingWallpaper) + || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe()) { + // Show the wallpaper if we're on keyguard/AOD and the wallpaper is not occluded by a + // solid backdrop or scrim. Also, show it if we are currently animating between the + // keyguard and the surface behind the keyguard - we want to use the wallpaper as a + // backdrop for this animation. mLpChanged.flags |= LayoutParams.FLAG_SHOW_WALLPAPER; } else { mLpChanged.flags &= ~LayoutParams.FLAG_SHOW_WALLPAPER; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index 064086a828cb3..6977ec1088f9a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -594,6 +594,7 @@ public abstract class PanelViewController { float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) { if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) { InteractionJankMonitor.getInstance().end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); + mKeyguardStateController.notifyPanelFlingEnd(); notifyExpandingFinished(); return; } @@ -681,6 +682,7 @@ public abstract class PanelViewController { private void onFlingEnd(boolean cancelled) { setAnimator(null); + mKeyguardStateController.notifyPanelFlingEnd(); if (!cancelled) { InteractionJankMonitor.getInstance() .end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 026072bf0d6a9..93b7b4fa851cb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -21,7 +21,6 @@ import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.StatusBarManager.WindowType; import static android.app.StatusBarManager.WindowVisibleState; import static android.app.StatusBarManager.windowStateToString; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY; import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.InsetsState.containsType; @@ -163,6 +162,7 @@ import com.android.systemui.emergency.EmergencyGesture; import com.android.systemui.fragments.ExtensionFragmentListener; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.keyguard.DismissCallbackRegistry; +import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; @@ -801,7 +801,8 @@ public class StatusBar extends SystemUI implements DemoMode, SystemStatusAnimationScheduler animationScheduler, PrivacyDotViewController dotViewController, TunerService tunerService, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, + KeyguardUnlockAnimationController keyguardUnlockAnimationController) { super(context); mNotificationsController = notificationsController; mLightBarController = lightBarController; @@ -1123,6 +1124,8 @@ public class StatusBar extends SystemUI implements DemoMode, inflateShelf(); mNotificationIconAreaController.setupShelf(mNotificationShelfController); mNotificationPanelViewController.addExpansionListener(mWakeUpCoordinator); + mNotificationPanelViewController.addExpansionListener( + this::dispatchPanelExpansionForKeyguardDismiss); // Allow plugins to reference DarkIconDispatcher and StatusBarStateController mPluginDependencyProvider.allowPluginDependency(DarkIconDispatcher.class); @@ -1349,6 +1352,38 @@ public class StatusBar extends SystemUI implements DemoMode, ThreadedRenderer.overrideProperty("ambientRatio", String.valueOf(1.5f)); } + + /** + * When swiping up to dismiss the lock screen, the panel expansion goes from 1f to 0f. This + * results in the clock/notifications/other content disappearing off the top of the screen. + * + * We also use the expansion amount to animate in the app/launcher surface from the bottom of + * the screen, 'pushing' off the notifications and other content. To do this, we dispatch the + * expansion amount to the KeyguardViewMediator if we're in the process of dismissing the + * keyguard. + */ + private void dispatchPanelExpansionForKeyguardDismiss(float expansion, boolean trackingTouch) { + // Things that mean we're not dismissing the keyguard, and should ignore this expansion: + // - Keyguard isn't even visible. + // - Keyguard is visible, but can't be dismissed (swiping up will show PIN/password prompt). + // - QS is expanded and we're swiping - swiping up now will hide QS, not dismiss the + // keyguard. + if (!isKeyguardShowing() + || !mKeyguardStateController.canDismissLockScreen() + || (mNotificationPanelViewController.isQsExpanded() && trackingTouch)) { + return; + } + + // Otherwise, we should let the keyguard know about this if we're tracking touch, or if we + // are already animating the keyguard dismiss (since we will need to either finish or cancel + // the animation). + if (trackingTouch + || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe()) { + mKeyguardStateController.notifyKeyguardDismissAmountChanged( + 1f - expansion, trackingTouch); + } + } + @NonNull @Override public Lifecycle getLifecycle() { 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 1980d9791e47e..161b91385e6af 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -563,6 +563,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mNotificationPanelViewController.blockExpansionForCurrentTouch(); } + @Override + public void blockPanelExpansionFromCurrentTouch() { + mNotificationPanelViewController.blockExpansionForCurrentTouch(); + } + @Override public void hide(long startTime, long fadeoutDuration) { mShowing = false; @@ -576,7 +581,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb long uptimeMillis = SystemClock.uptimeMillis(); long delay = Math.max(0, startTime + HIDE_TIMING_CORRECTION_MS - uptimeMillis); - if (mStatusBar.isInLaunchTransition() ) { + if (mStatusBar.isInLaunchTransition() + || mKeyguardStateController.isFlingingToDismissKeyguard()) { mStatusBar.fadeKeyguardAfterLaunchTransition(new Runnable() { @Override public void run() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index 24e6db818ef65..4795e8a3d35a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -38,6 +38,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.keyguard.DismissCallbackRegistry; +import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; @@ -214,7 +215,8 @@ public interface StatusBarPhoneModule { SystemStatusAnimationScheduler animationScheduler, PrivacyDotViewController dotViewController, TunerService tunerService, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, + KeyguardUnlockAnimationController keyguardUnlockAnimationController) { return new StatusBar( context, notificationsController, @@ -300,6 +302,7 @@ public interface StatusBarPhoneModule { animationScheduler, dotViewController, tunerService, - featureFlags); + featureFlags, + keyguardUnlockAnimationController); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java index 692c34c62dd87..e7201f87b3205 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java @@ -139,6 +139,38 @@ public interface KeyguardStateController extends CallbackController { */ long calculateGoingToFullShadeDelay(); + /** + * How much (from 0f to 1f) the keyguard is dismissed, either via a swipe gesture or an + * animation. + */ + float getDismissAmount(); + + /** + * Whether the keyguard is being dismissed due to direct user input, rather than a canned + * animation. + */ + boolean isDismissingFromSwipe(); + + /** + * Whether a fling animation is currently playing on the keyguard, either to dismiss it or to + * cancel dismissing it. + */ + boolean isFlingingToDismissKeyguard(); + + /** + * Whether a fling animation is currently playing on the keyguard, either to dismiss it or to + * cancel dismissing it, and that animation started during a swipe gesture. Fling animations + * can also be started without a swipe (e.g. activity launch from lock screen notification), so + * this is a way to tell them apart for animation purposes. + */ + boolean isFlingingToDismissKeyguardDuringSwipeGesture(); + + /** + * Whether a fling animation is currently playing on the keyguard to cancel dismissing it, after + * the user released their finger during a swipe gesture. + */ + boolean isSnappingKeyguardBackAfterSwipe(); + /** **/ default void setLaunchTransitionFadingAway(boolean b) {} /** **/ @@ -148,6 +180,28 @@ public interface KeyguardStateController extends CallbackController { /** **/ default void notifyKeyguardState(boolean showing, boolean occluded) {} + /** + * Updates the keyguard state to reflect that it's in the process of being dismissed, either by + * a swipe gesture on the lock screen or by a canned animation. + * + * @param dismissAmount 0f means we're not dismissed at all, 1f means we have been completely + * swiped away. + * @param dismissingFromTouch True if this change was caused by direct user interaction, false + * if it's due to an animation. + */ + default void notifyKeyguardDismissAmountChanged( + float dismissAmount, boolean dismissingFromTouch) {} + + /** + * Updates the keyguard state to reflect that a dismiss fling gesture has started. + * + * @param dismiss Whether we're flinging to dismiss (upward) or to cancel a dismiss gesture. + */ + void notifyPanelFlingStart(boolean dismiss); + + /** Updates the keyguard state to reflect that a dismiss fling gesture has ended. */ + void notifyPanelFlingEnd(); + /** * Callback for authentication events. */ @@ -173,5 +227,11 @@ public interface KeyguardStateController extends CallbackController { * Triggered when the device was just unlocked and the lock screen is being dismissed. */ default void onKeyguardFadingAwayChanged() {} + + /** + * Triggered when the keyguard dismiss amount has changed, via either a swipe gesture or an + * animation. + */ + default void onKeyguardDismissAmountChanged() {} } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java index 7f4eec745690d..e69c1f2573b7b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java @@ -70,6 +70,28 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum private boolean mDebugUnlocked = false; private boolean mFaceAuthEnabled; + private float mDismissAmount = 0f; + private boolean mDismissingFromTouch = false; + + /** + * Whether the panel is currently flinging to a collapsed state, which means we're dismissing + * the keyguard. + */ + private boolean mFlingingToDismissKeyguard = false; + + /** + * Whether the panel is currently flinging to a collapsed state, which means we're dismissing + * the keyguard, and the fling started during a swipe gesture. This means that we need to take + * over the gesture and animate the rest of the way dismissed. + */ + private boolean mFlingingToDismissKeyguardDuringSwipeGesture = false; + + /** + * Whether the panel is currently flinging to an expanded state, which means we cancelled the + * dismiss gesture and are snapping back to the keyguard state. + */ + private boolean mSnappingKeyguardBackAfterSwipe = false; + /** */ @Inject @@ -240,11 +262,59 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum return mKeyguardFadingAwayDelay + mKeyguardFadingAwayDuration; } + @Override + public boolean isFlingingToDismissKeyguard() { + return mFlingingToDismissKeyguard; + } + + @Override + public boolean isFlingingToDismissKeyguardDuringSwipeGesture() { + return mFlingingToDismissKeyguardDuringSwipeGesture; + } + + @Override + public boolean isSnappingKeyguardBackAfterSwipe() { + return mSnappingKeyguardBackAfterSwipe; + } + + @Override + public float getDismissAmount() { + return mDismissAmount; + } + + @Override + public boolean isDismissingFromSwipe() { + return mDismissingFromTouch; + } + @Override public void notifyKeyguardGoingAway(boolean keyguardGoingAway) { mKeyguardGoingAway = keyguardGoingAway; } + @Override + public void notifyPanelFlingEnd() { + mFlingingToDismissKeyguard = false; + mFlingingToDismissKeyguardDuringSwipeGesture = false; + mSnappingKeyguardBackAfterSwipe = false; + } + + @Override + public void notifyPanelFlingStart(boolean flingToDismiss) { + mFlingingToDismissKeyguard = flingToDismiss; + mFlingingToDismissKeyguardDuringSwipeGesture = + flingToDismiss && mDismissingFromTouch; + mSnappingKeyguardBackAfterSwipe = !flingToDismiss; + } + + @Override + public void notifyKeyguardDismissAmountChanged(float dismissAmount, + boolean dismissingFromTouch) { + mDismissAmount = dismissAmount; + mDismissingFromTouch = dismissingFromTouch; + new ArrayList<>(mCallbacks).forEach(Callback::onKeyguardDismissAmountChanged); + } + @Override public void setLaunchTransitionFadingAway(boolean fadingAway) { mLaunchTransitionFadingAway = fadingAway; 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 b8c37fde2ce39..5c87741b88835 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -29,17 +29,13 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.app.ActivityManager; -import android.app.ActivityTaskManager; import android.app.admin.DevicePolicyManager; import android.app.trust.TrustManager; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; -import android.view.View; -import com.android.systemui.R; import androidx.test.filters.SmallTest; import com.android.internal.widget.LockPatternUtils; @@ -51,9 +47,9 @@ import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.dump.DumpManager; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.LightRevealScrim; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.DeviceConfigProxyFake; import com.android.systemui.util.concurrency.FakeExecutor; @@ -84,6 +80,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock KeyguardDisplayManager mKeyguardDisplayManager; private @Mock DozeParameters mDozeParameters; private @Mock StatusBarStateController mStatusBarStateController; + private @Mock KeyguardStateController mKeyguardStateController; + private @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake(); private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); @@ -102,7 +100,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { () -> mStatusBarKeyguardViewManager, mDismissCallbackRegistry, mUpdateMonitor, mDumpManager, mUiBgExecutor, mPowerManager, mTrustManager, mDeviceConfig, mNavigationModeController, - mKeyguardDisplayManager, mDozeParameters, mStatusBarStateController); + mKeyguardDisplayManager, mDozeParameters, mStatusBarStateController, + mKeyguardStateController, () -> mKeyguardUnlockAnimationController); mViewMediator.start(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java index cebf8be491504..2e4d253fad56d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java @@ -553,6 +553,36 @@ public class NotificationPanelViewTest extends SysuiTestCase { assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isFalse(); } + @Test + public void testSwipeWhileLocked_notifiesKeyguardState() { + mStatusBarStateController.setState(KEYGUARD); + + // Fling expanded (cancelling the keyguard exit swipe). We should notify keyguard state that + // the fling occurred and did not dismiss the keyguard. + mNotificationPanelViewController.flingToHeight( + 0f, true /* expand */, 1000f, 1f, false); + verify(mKeyguardStateController).notifyPanelFlingStart(false /* dismissKeyguard */); + + // Fling un-expanded, which is a keyguard exit fling when we're in KEYGUARD state. + mNotificationPanelViewController.flingToHeight( + 0f, false /* expand */, 1000f, 1f, false); + verify(mKeyguardStateController).notifyPanelFlingStart(true /* dismissKeyguard */); + } + + @Test + public void testCancelSwipeWhileLocked_notifiesKeyguardState() { + mStatusBarStateController.setState(KEYGUARD); + + mNotificationPanelViewController.setOverExpansion(100f, true); + + // Fling expanded (cancelling the keyguard exit swipe). We should notify keyguard state that + // the fling occurred and did not dismiss the keyguard. + mNotificationPanelViewController.flingToHeight( + 0f, true /* expand */, 1000f, 1f, false); + mNotificationPanelViewController.cancelHeightAnimator(); + verify(mKeyguardStateController).notifyPanelFlingEnd(); + } + private View newViewWithId(int id) { View view = new View(mContext); view.setId(id); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java index 4b8eec44ef4cf..323843098a1a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java @@ -133,6 +133,19 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { assertThat((mLayoutParameters.getValue().flags & FLAG_SHOW_WALLPAPER) != 0).isTrue(); } + @Test + public void attach_animatingKeyguardAndSurface_wallpaperVisible() { + clearInvocations(mWindowManager); + when(mKeyguardViewMediator.isShowingAndNotOccluded()).thenReturn(true); + when(mKeyguardViewMediator + .isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe()) + .thenReturn(true); + mNotificationShadeWindowController.attach(); + + verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture()); + assertThat((mLayoutParameters.getValue().flags & FLAG_SHOW_WALLPAPER) != 0).isTrue(); + } + @Test public void setBackgroundBlurRadius_expandedWithBlurs() { mNotificationShadeWindowController.setBackgroundBlurRadius(10); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 11f96c87c0e5a..8c8212cdb56c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -86,6 +86,7 @@ import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.keyguard.DismissCallbackRegistry; +import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; @@ -271,6 +272,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private TunerService mTunerService; @Mock private FeatureFlags mFeatureFlags; @Mock private IWallpaperManager mWallpaperManager; + @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private ShadeController mShadeController; private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); private InitController mInitController = new InitController(); @@ -439,7 +441,8 @@ public class StatusBarTest extends SysuiTestCase { mAnimationScheduler, mDotViewController, mTunerService, - mFeatureFlags); + mFeatureFlags, + mKeyguardUnlockAnimationController); when(mKeyguardViewMediator.registerStatusBar(any(StatusBar.class), any(ViewGroup.class), any(NotificationPanelViewController.class), any(BiometricUnlockController.class), any(ViewGroup.class), any(KeyguardBypassController.class))) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java index e57bbc1623f08..53a2efce9bb8f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java @@ -143,4 +143,14 @@ public class KeyguardStateControllerTest extends SysuiTestCase { verify(callback).onUnlockedChanged(); } + @Test + public void testKeyguardDismissAmountCallbackInvoked() { + KeyguardStateController.Callback callback = mock(KeyguardStateController.Callback.class); + mKeyguardStateController.addCallback(callback); + + mKeyguardStateController.notifyKeyguardDismissAmountChanged(100f, false); + + verify(callback).onKeyguardDismissAmountChanged(); + } + } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java index 26cac290c9b50..5fb779adc5be0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java @@ -92,4 +92,39 @@ public class FakeKeyguardStateController implements KeyguardStateController { public long calculateGoingToFullShadeDelay() { return 0; } + + @Override + public float getDismissAmount() { + return 0; + } + + @Override + public boolean isDismissingFromSwipe() { + return false; + } + + @Override + public boolean isFlingingToDismissKeyguard() { + return false; + } + + @Override + public boolean isFlingingToDismissKeyguardDuringSwipeGesture() { + return false; + } + + @Override + public boolean isSnappingKeyguardBackAfterSwipe() { + return false; + } + + @Override + public void notifyPanelFlingStart(boolean dismiss) { + + } + + @Override + public void notifyPanelFlingEnd() { + + } } diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index 67bc7af6dc287..1a429f88fe2e6 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -55,7 +55,7 @@ import java.util.ArrayList; class RemoteAnimationController implements DeathRecipient { private static final String TAG = TAG_WITH_CLASS_NAME ? "RemoteAnimationController" : TAG_WM; - private static final long TIMEOUT_MS = 2000; + private static final long TIMEOUT_MS = 10000; private final WindowManagerService mService; private final DisplayContent mDisplayContent; diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java index 37da529d43b79..b6cfa8e96a4c2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -177,7 +177,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { mFinishedCallback); mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); - mClock.fastForward(2500); + mClock.fastForward(10500); mHandler.timeAdvance(); verify(mMockRunner).onAnimationCancelled(); @@ -198,12 +198,12 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { mFinishedCallback); mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); - mClock.fastForward(2500); + mClock.fastForward(10500); mHandler.timeAdvance(); verify(mMockRunner, never()).onAnimationCancelled(); - mClock.fastForward(10000); + mClock.fastForward(52500); mHandler.timeAdvance(); verify(mMockRunner).onAnimationCancelled();