Use Launcher smartspace instead of SysUI's for the shared element transition.

See justification/explanation in linked bug.

I re-ordered some methods in the unlock controller so they'd make more sense,
they're generally called from top to bottom now during the unlock sequence.

Bug: 229890190
Fixes: 227713319
Test: atest SystemUITests
Test: manual
Change-Id: I58fa437eea183691ff29548143664698498c2105
This commit is contained in:
Josh Tsuji
2022-04-25 14:56:24 -04:00
parent 4d41feb829
commit 53781df704
9 changed files with 275 additions and 527 deletions

View File

@@ -16,17 +16,20 @@
package com.android.systemui.shared.system.smartspace;
import android.graphics.Rect;
import com.android.systemui.shared.system.smartspace.SmartspaceState;
// Methods for System UI to interface with Launcher to perform the unlock animation.
interface ILauncherUnlockAnimationController {
// Prepares Launcher for the unlock animation by setting scale/alpha/etc. to their starting
// values.
void prepareForUnlock(boolean willAnimateSmartspace, int selectedPage);
void prepareForUnlock(boolean animateSmartspace, in Rect lockscreenSmartspaceBounds,
int selectedPage);
// Set the unlock percentage. This is used when System UI is controlling each frame of the
// unlock animation, such as during a swipe to unlock touch gesture.
oneway void setUnlockAmount(float amount);
// unlock animation, such as during a swipe to unlock touch gesture. Will not apply this change
// if the unlock amount is animating unless forceIfAnimating is true.
oneway void setUnlockAmount(float amount, boolean forceIfAnimating);
// Play a full unlock animation from 0f to 1f. This is used when System UI is unlocking from a
// single action, such as biometric auth, and doesn't need to control individual frames.

View File

@@ -58,9 +58,7 @@ import com.android.systemui.util.ViewController;
import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.Executor;
@@ -133,14 +131,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
private final KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener
mKeyguardUnlockAnimationListener =
new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
@Override
public void onSmartspaceSharedElementTransitionStarted() {
// The smartspace needs to be able to translate out of bounds in order to
// end up where the launcher's smartspace is, while its container is being
// swiped off the top of the screen.
setClipChildrenForUnlock(false);
}
@Override
public void onUnlockAnimationFinished() {
// For performance reasons, reset this once the unlock animation ends.
@@ -390,41 +380,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
if (mStatusArea != null) {
PropertyAnimator.setProperty(mStatusArea, AnimatableProperty.TRANSLATION_X,
x, props, animate);
// If we're unlocking with the SmartSpace shared element transition, let the controller
// know that it should re-position our SmartSpace.
if (mKeyguardUnlockAnimationController.isUnlockingWithSmartSpaceTransition()) {
mKeyguardUnlockAnimationController.updateLockscreenSmartSpacePosition();
}
}
}
/** Sets an alpha value on every child view except for the smartspace. */
public void setChildrenAlphaExcludingSmartspace(float alpha) {
final Set<View> excludedViews = new HashSet<>();
if (mSmartspaceView != null) {
excludedViews.add(mStatusArea);
}
// Don't change the alpha of the invisible clock.
if (mCurrentClockSize == LARGE) {
excludedViews.add(mClockFrame);
} else {
excludedViews.add(mLargeClockFrame);
}
setChildrenAlphaExcluding(alpha, excludedViews);
}
/** Sets an alpha value on every child view except for the views in the provided set. */
public void setChildrenAlphaExcluding(float alpha, Set<View> excludedViews) {
for (int i = 0; i < mView.getChildCount(); i++) {
final View child = mView.getChildAt(i);
if (!excludedViews.contains(child)) {
child.setAlpha(alpha);
}
}
}

View File

@@ -47,7 +47,6 @@ public class KeyguardStatusView extends GridLayout {
private float mDarkAmount = 0;
private int mTextColor;
private float mChildrenAlphaExcludingSmartSpace = 1f;
public KeyguardStatusView(Context context) {
this(context, null, 0);
@@ -95,23 +94,6 @@ public class KeyguardStatusView extends GridLayout {
mClockView.setTextColor(blendedTextColor);
}
public void setChildrenAlphaExcludingClockView(float alpha) {
setChildrenAlphaExcluding(alpha, Set.of(mClockView));
}
/** Sets an alpha value on every view except for the views in the provided set. */
public void setChildrenAlphaExcluding(float alpha, Set<View> excludedViews) {
mChildrenAlphaExcludingSmartSpace = alpha;
for (int i = 0; i < mStatusViewContainer.getChildCount(); i++) {
final View child = mStatusViewContainer.getChildAt(i);
if (!excludedViews.contains(child)) {
child.setAlpha(alpha);
}
}
}
/** Sets a translationY value on every child view except for the media view. */
public void setChildrenTranslationYExcludingMediaView(float translationY) {
setChildrenTranslationYExcluding(translationY, Set.of(mMediaHostContainer));
@@ -128,10 +110,6 @@ public class KeyguardStatusView extends GridLayout {
}
}
public float getChildrenAlphaExcludingSmartSpace() {
return mChildrenAlphaExcludingSmartSpace;
}
public void dump(PrintWriter pw, String[] args) {
pw.println("KeyguardStatusView:");
pw.println(" mDarkAmount: " + mDarkAmount);

View File

@@ -20,7 +20,6 @@ import android.graphics.Rect;
import android.util.Slog;
import com.android.keyguard.KeyguardClockSwitch.ClockSize;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -49,10 +48,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
private final KeyguardClockSwitchController mKeyguardClockSwitchController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final ConfigurationController mConfigurationController;
private final DozeParameters mDozeParameters;
private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final KeyguardStateController mKeyguardStateController;
private final Rect mClipBounds = new Rect();
@Inject
@@ -64,18 +60,14 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
KeyguardUpdateMonitor keyguardUpdateMonitor,
ConfigurationController configurationController,
DozeParameters dozeParameters,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
ScreenOffAnimationController screenOffAnimationController) {
super(keyguardStatusView);
mKeyguardSliceViewController = keyguardSliceViewController;
mKeyguardClockSwitchController = keyguardClockSwitchController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mConfigurationController = configurationController;
mDozeParameters = dozeParameters;
mKeyguardStateController = keyguardStateController;
mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
dozeParameters, screenOffAnimationController, /* animateYPos= */ true);
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
}
@Override
@@ -87,14 +79,12 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
protected void onViewAttached() {
mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
mConfigurationController.addCallback(mConfigurationListener);
mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
}
@Override
protected void onViewDetached() {
mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
mConfigurationController.removeCallback(mConfigurationListener);
mKeyguardStateController.removeCallback(mKeyguardStateControllerCallback);
}
/**
@@ -148,24 +138,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
*/
public void setAlpha(float alpha) {
if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
// If we're capable of performing the SmartSpace shared element transition, and we are
// going to (we're swiping to dismiss vs. bringing up the PIN screen), then fade out
// everything except for the SmartSpace.
if (mKeyguardUnlockAnimationController.isUnlockingWithSmartSpaceTransition()) {
mView.setChildrenAlphaExcludingClockView(alpha);
mKeyguardClockSwitchController.setChildrenAlphaExcludingSmartspace(alpha);
} else if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
// Otherwise, we can just set the alpha for the entire container.
mView.setAlpha(alpha);
// If we previously unlocked with the shared element transition, some child views
// might still have alpha = 0f. Set them back to 1f since we're just using the
// parent container's alpha.
if (mView.getChildrenAlphaExcludingSmartSpace() < 1f) {
mView.setChildrenAlphaExcludingClockView(1f);
mKeyguardClockSwitchController.setChildrenAlphaExcludingSmartspace(1f);
}
}
mView.setAlpha(alpha);
}
}
@@ -289,19 +262,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
}
};
private KeyguardStateController.Callback mKeyguardStateControllerCallback =
new KeyguardStateController.Callback() {
@Override
public void onKeyguardShowingChanged() {
// If we explicitly re-show the keyguard, make sure that all the child views are
// visible. They might have been animating out as part of the SmartSpace shared
// element transition.
if (mKeyguardStateController.isShowing()) {
mView.setChildrenAlphaExcludingClockView(1f);
}
}
};
/**
* Rect that specifies how KSV should be clipped, on its parent's coordinates.
*/

View File

@@ -23,7 +23,7 @@ import android.content.Context
import android.graphics.Matrix
import android.graphics.Rect
import android.os.Handler
import android.provider.Settings
import android.os.RemoteException
import android.util.Log
import android.view.RemoteAnimationTarget
import android.view.SyncRtSurfaceTransactionApplier
@@ -47,7 +47,6 @@ import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.policy.KeyguardStateController
import dagger.Lazy
import javax.inject.Inject
import kotlin.math.min
const val TAG = "KeyguardUnlock"
@@ -77,7 +76,7 @@ const val SURFACE_BEHIND_SCALE_PIVOT_Y = 0.66f
* 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.25f
const val DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD = 0.15f
/**
* Dismiss amount at which to complete the keyguard exit animation and hide the keyguard.
@@ -85,7 +84,7 @@ const val DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD = 0.25f
* 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.4f
const val DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD = 0.3f
/**
* How long the canned unlock animation takes. This is used if we are unlocking from biometric auth,
@@ -112,7 +111,7 @@ const val CANNED_UNLOCK_START_DELAY = 100L
* Duration for the alpha animation on the surface behind. This plays to fade in the surface during
* a swipe to unlock (and to fade it back out if the swipe is cancelled).
*/
const val SURFACE_BEHIND_SWIPE_FADE_DURATION_MS = 150L
const val SURFACE_BEHIND_SWIPE_FADE_DURATION_MS = 175L
/**
* Start delay for the surface behind animation, used so that the lockscreen can get out of the way
@@ -151,12 +150,21 @@ class KeyguardUnlockAnimationController @Inject constructor(
* [playingCannedAnimation] indicates whether we are playing a canned animation to show the
* app/launcher behind the keyguard, vs. this being a swipe to unlock where the dismiss
* amount drives the animation.
*
* [fromWakeAndUnlock] tells us whether we are unlocking directly from AOD - in this case,
* the lockscreen is dismissed instantly, so we shouldn't run any animations that rely on it
* being visible.
*
* [unlockAnimationStartDelay] and [unlockAnimationDuration] provide the timing parameters
* for the canned animation (if applicable) so interested parties can sync with it. If no
* canned animation is playing, these are both 0.
*/
@JvmDefault
fun onUnlockAnimationStarted(playingCannedAnimation: Boolean, fromWakeAndUnlock: Boolean) {}
fun onUnlockAnimationStarted(
playingCannedAnimation: Boolean,
fromWakeAndUnlock: Boolean,
unlockAnimationStartDelay: Long,
unlockAnimationDuration: Long) {}
/**
* Called when the remote unlock animation ends, in all cases, canned or swipe-to-unlock.
@@ -165,19 +173,6 @@ class KeyguardUnlockAnimationController @Inject constructor(
*/
@JvmDefault
fun onUnlockAnimationFinished() {}
/**
* Called when we begin the smartspace shared element transition, either due to an unlock
* action (biometric, etc.) or a swipe to unlock.
*
* This transition can begin BEFORE [onUnlockAnimationStarted] is called, if we are swiping
* to unlock and the surface behind the keyguard has not yet been made visible. This is
* because the lockscreen smartspace immediately begins moving towards the launcher
* smartspace location when a swipe begins, even before we start the keyguard exit remote
* animation and show the launcher itself.
*/
@JvmDefault
fun onSmartspaceSharedElementTransitionStarted() {}
}
/** The SmartSpace view on the lockscreen, provided by [KeyguardClockSwitchController]. */
@@ -259,8 +254,9 @@ class KeyguardUnlockAnimationController @Inject constructor(
* animation plays.
*/
private var surfaceBehindAlpha = 1f
private var surfaceBehindAlphaAnimator = ValueAnimator.ofFloat(0f, 1f)
private var smartspaceAnimator = ValueAnimator.ofFloat(0f, 1f)
@VisibleForTesting
var surfaceBehindAlphaAnimator = ValueAnimator.ofFloat(0f, 1f)
/**
* Matrix applied to [surfaceBehindRemoteAnimationTarget], which is the surface of the
@@ -281,59 +277,38 @@ class KeyguardUnlockAnimationController @Inject constructor(
private var roundedCornerRadius = 0f
/**
* Whether we tried to start the SmartSpace shared element transition for this unlock swipe.
* It's possible we were unable to do so (if the Launcher SmartSpace is not available), and we
* need to keep track of that so that we don't start doing it halfway through the swipe if
* Launcher becomes available suddenly.
* Whether we decided in [prepareForInWindowLauncherAnimations] that we are able to and want to
* play the in-window launcher unlock animations rather than simply animating the Launcher
* window like any other app. This can be true while [willUnlockWithSmartspaceTransition] is
* false, if the smartspace is not available or was not ready in time.
*/
private var attemptedSmartSpaceTransitionForThisSwipe = false
private var willUnlockWithInWindowLauncherAnimations: Boolean = false
/**
* The original location of the lockscreen smartspace on the screen.
* Whether we decided in [prepareForInWindowLauncherAnimations] that we are able to and want to
* play the smartspace shared element animation. If true,
* [willUnlockWithInWindowLauncherAnimations] will also always be true since in-window
* animations are a prerequisite for the smartspace transition.
*/
private val smartspaceOriginBounds = Rect()
/**
* The bounds to which the lockscreen smartspace is moving. This is set to the bounds of the
* launcher's smartspace prior to the transition starting.
*/
private val smartspaceDestBounds = Rect()
/**
* From 0f to 1f, the progress of the smartspace shared element animation. 0f means the
* smartspace is at its normal position within the lock screen hierarchy, and 1f means it has
* fully animated to the location of the Launcher's smartspace.
*/
private var smartspaceUnlockProgress = 0f
/**
* Whether we're currently unlocking, and we're talking to Launcher to perform in-window
* animations rather than simply animating the Launcher window like any other app. This can be
* true while [unlockingWithSmartspaceTransition] is false, if the smartspace is not available
* or was not ready in time.
*/
private var unlockingToLauncherWithInWindowAnimations: Boolean = false
/**
* Whether we are currently unlocking, and the smartspace shared element transition is in
* progress. If true, we're also [unlockingToLauncherWithInWindowAnimations].
*/
private var unlockingWithSmartspaceTransition: Boolean = false
private var willUnlockWithSmartspaceTransition: Boolean = false
private val handler = Handler()
init {
with(surfaceBehindAlphaAnimator) {
duration = SURFACE_BEHIND_SWIPE_FADE_DURATION_MS
interpolator = Interpolators.TOUCH_RESPONSE
interpolator = Interpolators.LINEAR
addUpdateListener { valueAnimator: ValueAnimator ->
surfaceBehindAlpha = valueAnimator.animatedValue as Float
updateSurfaceBehindAppearAmount()
}
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 even if other properties are still animating.
// If we animated the surface alpha to 0f, it means we cancelled a swipe to
// dismiss. In this case, we should ask the KeyguardViewMediator to end the
// remote animation to hide the surface behind the keyguard, but should *not*
// call onKeyguardExitRemoteAnimationFinished since that will hide the keyguard
// and unlock the device as well as hiding the surface.
if (surfaceBehindAlpha == 0f) {
keyguardViewMediator.get().finishSurfaceBehindRemoteAnimation(
false /* cancelled */)
@@ -360,21 +335,6 @@ class KeyguardUnlockAnimationController @Inject constructor(
})
}
with(smartspaceAnimator) {
duration = UNLOCK_ANIMATION_DURATION_MS
interpolator = Interpolators.TOUCH_RESPONSE
addUpdateListener {
smartspaceUnlockProgress = it.animatedValue as Float
}
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
launcherUnlockController?.setSmartspaceVisibility(View.VISIBLE)
keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(
false /* cancelled */)
}
})
}
// Listen for changes in the dismiss amount.
keyguardStateController.addCallback(this)
@@ -393,6 +353,74 @@ class KeyguardUnlockAnimationController @Inject constructor(
listeners.remove(listener)
}
/**
* Whether we should be able to do the in-window launcher animations given the current state of
* the device.
*/
fun canPerformInWindowLauncherAnimations(): Boolean {
return isNexusLauncherUnderneath() &&
launcherUnlockController != null &&
!keyguardStateController.isDismissingFromSwipe &&
// Temporarily disable for foldables since foldable launcher has two first pages,
// which breaks the in-window animation.
!isFoldable(context)
}
/**
* Called from [KeyguardStateController] to let us know that the keyguard going away state has
* changed.
*/
override fun onKeyguardGoingAwayChanged() {
if (keyguardStateController.isKeyguardGoingAway) {
prepareForInWindowLauncherAnimations()
}
}
/**
* Prepare for in-window Launcher unlock animations, if we're able to do so.
*
* The in-window animations consist of the staggered ring icon unlock animation, and optionally
* the shared element smartspace transition.
*/
fun prepareForInWindowLauncherAnimations() {
willUnlockWithInWindowLauncherAnimations = canPerformInWindowLauncherAnimations()
if (!willUnlockWithInWindowLauncherAnimations) {
return
}
// There are additional conditions under which we should not perform the smartspace
// transition specifically, so check those.
willUnlockWithSmartspaceTransition = shouldPerformSmartspaceTransition()
var lockscreenSmartspaceBounds = Rect()
// Grab the bounds of our lockscreen smartspace and send them to launcher so they can
// position their smartspace there initially, then animate it to its resting position.
if (willUnlockWithSmartspaceTransition) {
lockscreenSmartspaceBounds = Rect().apply {
lockscreenSmartspace!!.getBoundsOnScreen(this)
offset(lockscreenSmartspace!!.paddingLeft, lockscreenSmartspace!!.paddingTop)
}
}
// Currently selected lockscreen smartspace page, or -1 if it's not available.
val selectedPage =
(lockscreenSmartspace as BcSmartspaceDataPlugin.SmartspaceView?)?.selectedPage ?: -1
try {
// Let the launcher know to prepare for this animation.
launcherUnlockController?.prepareForUnlock(
willUnlockWithSmartspaceTransition, /* willAnimateSmartspace */
lockscreenSmartspaceBounds, /* lockscreenSmartspaceBounds */
selectedPage, /* selectedPage */
)
} catch (e: RemoteException) {
Log.e(TAG, "Remote exception in prepareForInWindowUnlockAnimations.", e)
}
}
/**
* 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
@@ -404,7 +432,8 @@ class KeyguardUnlockAnimationController @Inject constructor(
*
* [requestedShowSurfaceBehindKeyguard] indicates whether the animation started because of a
* call to [KeyguardViewMediator.showSurfaceBehindKeyguard], as happens during a swipe gesture,
* as opposed to being called because the device was unlocked and the keyguard is going away.
* as opposed to being called because the device was unlocked instantly by some other means
* (fingerprint, tap, etc.) and the keyguard is going away.
*/
fun notifyStartSurfaceBehindRemoteAnimation(
target: RemoteAnimationTarget,
@@ -434,7 +463,9 @@ class KeyguardUnlockAnimationController @Inject constructor(
listeners.forEach {
it.onUnlockAnimationStarted(
playingCannedUnlockAnimation /* playingCannedAnimation */,
biometricUnlockControllerLazy.get().isWakeAndUnlock /* isWakeAndUnlock */) }
biometricUnlockControllerLazy.get().isWakeAndUnlock /* isWakeAndUnlock */,
CANNED_UNLOCK_START_DELAY /* unlockStartDelay */,
LAUNCHER_ICONS_ANIMATION_DURATION_MS /* unlockAnimationDuration */) }
// Finish the keyguard remote animation if the dismiss amount has crossed the threshold.
// Check it here in case there is no more change to the dismiss amount after the last change
@@ -442,58 +473,33 @@ class KeyguardUnlockAnimationController @Inject constructor(
finishKeyguardExitRemoteAnimationIfReachThreshold()
}
/**
* Called by [KeyguardViewMediator] to let us know that the remote animation has finished, and
* we should clean up all of our state.
*/
fun notifyFinishedKeyguardExitAnimation(cancelled: Boolean) {
// Cancel any pending actions.
handler.removeCallbacksAndMessages(null)
// Make sure we made the surface behind fully visible, just in case. It should already be
// fully visible.
setSurfaceBehindAppearAmount(1f)
launcherUnlockController?.setUnlockAmount(1f)
smartspaceDestBounds.setEmpty()
// That target is no longer valid since the animation finished, null it out.
surfaceBehindRemoteAnimationTarget = null
surfaceBehindParams = null
playingCannedUnlockAnimation = false
unlockingToLauncherWithInWindowAnimations = false
unlockingWithSmartspaceTransition = false
resetSmartspaceTransition()
listeners.forEach { it.onUnlockAnimationFinished() }
}
/**
* Play a canned unlock animation to unlock the device. This is used when we were *not* swiping
* to unlock using a touch gesture. If we were swiping to unlock, the animation will be driven
* by the dismiss amount via [onKeyguardDismissAmountChanged].
*/
fun playCannedUnlockAnimation() {
private fun playCannedUnlockAnimation() {
playingCannedUnlockAnimation = true
if (canPerformInWindowLauncherAnimations()) {
// If possible, use the neat in-window animations to unlock to the launcher.
unlockToLauncherWithInWindowAnimations()
} else if (!biometricUnlockControllerLazy.get().isWakeAndUnlock) {
// If the launcher isn't behind the keyguard, or the launcher unlock controller is not
// available, animate in the entire window.
surfaceBehindEntryAnimator.start()
} else {
setSurfaceBehindAppearAmount(1f)
keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(false)
}
// If this is a wake and unlock, hide the lockscreen immediately. In the future, we should
// animate it out nicely instead, but to the current state of wake and unlock, not hiding it
// causes a lot of issues.
// TODO(b/210016643): Not this, it looks not-ideal!
if (biometricUnlockControllerLazy.get().isWakeAndUnlock) {
keyguardViewController.hide(surfaceBehindRemoteAnimationStartTime, 350)
when {
// If we're set up for in-window launcher animations, ask Launcher to play its in-window
// canned animation.
willUnlockWithInWindowLauncherAnimations -> unlockToLauncherWithInWindowAnimations()
// If we're waking and unlocking to a non-Launcher app surface (or Launcher in-window
// animations are not available), show it immediately and end the remote animation. The
// circular light reveal will show the app surface, and it looks weird if it's moving
// around behind that.
biometricUnlockControllerLazy.get().isWakeAndUnlock -> {
setSurfaceBehindAppearAmount(1f)
keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(
false /* cancelled */)
}
// Otherwise, we're doing a normal full-window unlock. Start this animator, which will
// scale/translate the window underneath the lockscreen.
else -> surfaceBehindEntryAnimator.start()
}
}
@@ -502,205 +508,31 @@ class KeyguardUnlockAnimationController @Inject constructor(
* transition if possible.
*/
private fun unlockToLauncherWithInWindowAnimations() {
// See if we can do the smartspace transition, and if so, do it!
if (prepareForSmartspaceTransition()) {
animateSmartspaceToDestination()
listeners.forEach { it.onSmartspaceSharedElementTransitionStarted() }
}
val startDelay = Settings.Secure.getLong(
context.contentResolver, "unlock_start_delay", CANNED_UNLOCK_START_DELAY)
val duration = Settings.Secure.getLong(
context.contentResolver, "unlock_duration", LAUNCHER_ICONS_ANIMATION_DURATION_MS)
unlockingToLauncherWithInWindowAnimations = true
prepareLauncherWorkspaceForUnlockAnimation()
setSurfaceBehindAppearAmount(1f)
// Begin the animation, waiting for the shade to animate out.
launcherUnlockController?.playUnlockAnimation(
true /* unlocked */,
duration /* duration */,
startDelay /* startDelay */)
LAUNCHER_ICONS_ANIMATION_DURATION_MS /* duration */,
CANNED_UNLOCK_START_DELAY /* startDelay */)
// Now that the Launcher surface (with its smartspace positioned identically to ours) is
// visible, hide our smartspace.
lockscreenSmartspace!!.visibility = View.INVISIBLE
// As soon as the shade has animated out of the way, finish the keyguard exit animation. The
// in-window animations in the Launcher window will end on their own.
handler.postDelayed({
applyParamsToSurface(
SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
surfaceBehindRemoteAnimationTarget!!.leash)
.withAlpha(1f)
.build())
}, startDelay)
if (!unlockingWithSmartspaceTransition) {
// If we are not unlocking with the smartspace transition, wait for the unlock animation
// to end and then finish the remote animation. If we are using the smartspace
// transition, it will finish the remote animation once it ends.
handler.postDelayed({
keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(
false /* cancelled */)
}, UNLOCK_ANIMATION_DURATION_MS)
}
}
/**
* Asks Launcher to prepare the workspace to be unlocked. This sets up the animation and makes
* the page invisible.
*/
private fun prepareLauncherWorkspaceForUnlockAnimation() {
// Tell the launcher to prepare for the animation by setting its views invisible and
// syncing the selected smartspace pages.
launcherUnlockController?.prepareForUnlock(
unlockingWithSmartspaceTransition /* willAnimateSmartspace */,
(lockscreenSmartspace as BcSmartspaceDataPlugin.SmartspaceView?)?.selectedPage ?: -1)
}
/**
* Animates the lockscreen smartspace all the way to the launcher's smartspace location, then
* makes the launcher smartspace visible and ends the remote animation.
*/
private fun animateSmartspaceToDestination() {
smartspaceAnimator.start()
}
/**
* Reset the lockscreen smartspace's position, and reset all state involving the smartspace
* transition.
*/
public fun resetSmartspaceTransition() {
unlockingWithSmartspaceTransition = false
smartspaceUnlockProgress = 0f
lockscreenSmartspace?.post {
lockscreenSmartspace!!.translationX = 0f
lockscreenSmartspace!!.translationY = 0f
}
}
/**
* Moves the lockscreen smartspace towards the launcher smartspace's position.
*/
private fun setSmartspaceProgressToDestinationBounds(progress: Float) {
if (smartspaceDestBounds.isEmpty) {
return
}
val progressClamped = min(1f, progress)
// Calculate the distance (relative to the origin) that we need to be for the current
// progress value.
val progressX =
(smartspaceDestBounds.left - smartspaceOriginBounds.left) * progressClamped
val progressY =
(smartspaceDestBounds.top - smartspaceOriginBounds.top) * progressClamped
val lockscreenSmartspaceCurrentBounds = Rect().also {
lockscreenSmartspace!!.getBoundsOnScreen(it)
}
// Figure out how far that is from our present location on the screen. This approach
// compensates for the fact that our parent container is also translating to animate out.
val dx = smartspaceOriginBounds.left + progressX -
lockscreenSmartspaceCurrentBounds.left
val dy = smartspaceOriginBounds.top + progressY -
lockscreenSmartspaceCurrentBounds.top
with(lockscreenSmartspace!!) {
translationX += dx
translationY += dy
}
}
/**
* Update the lockscreen SmartSpace to be positioned according to the current dismiss amount. As
* the dismiss amount increases, we will increase our SmartSpace's progress to the destination
* bounds (the location of the Launcher SmartSpace).
*
* This is used by [KeyguardClockSwitchController] to keep the smartspace position updated as
* the clock is swiped away.
*/
fun updateLockscreenSmartSpacePosition() {
setSmartspaceProgressToDestinationBounds(smartspaceUnlockProgress)
}
/**
* Asks the keyguard view to hide, using the start time from the beginning of the remote
* animation.
*/
fun hideKeyguardViewAfterRemoteAnimation() {
if (keyguardViewController.isShowing) {
// Hide the keyguard, with no fade out since we animated it away during the unlock.
keyguardViewController.hide(
surfaceBehindRemoteAnimationStartTime,
0 /* fadeOutDuration */
)
} else {
Log.e(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
"showing. Ignoring...")
}
}
private fun applyParamsToSurface(params: SyncRtSurfaceTransactionApplier.SurfaceParams) {
surfaceTransactionApplier!!.scheduleApply(params)
surfaceBehindParams = params
}
/**
* 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).
*/
fun setSurfaceBehindAppearAmount(amount: Float) {
if (surfaceBehindRemoteAnimationTarget == null) {
return
}
if (unlockingToLauncherWithInWindowAnimations) {
// If we aren't using the canned unlock animation (which would be setting the unlock
// amount in its update listener), do it here.
if (!isPlayingCannedUnlockAnimation()) {
launcherUnlockController?.setUnlockAmount(amount)
if (surfaceBehindParams?.alpha?.let { it < 1f } != false) {
applyParamsToSurface(
SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
surfaceBehindRemoteAnimationTarget!!.leash)
.withAlpha(1f)
.build())
}
if (keyguardViewMediator.get().isShowingAndNotOccluded &&
!keyguardStateController.isKeyguardGoingAway) {
Log.e(TAG, "Finish keyguard exit animation delayed Runnable ran, but we are " +
"showing and not going away.")
return@postDelayed
}
} else {
// Otherwise, animate in the surface's scale/transltion.
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
applyParamsToSurface(
SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
surfaceBehindRemoteAnimationTarget!!.leash)
.withMatrix(surfaceBehindMatrix)
.withCornerRadius(roundedCornerRadius)
.withAlpha(animationAlpha)
.build())
}
keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(
false /* cancelled */)
}, CANNED_UNLOCK_START_DELAY)
}
/**
@@ -738,7 +570,7 @@ class KeyguardUnlockAnimationController @Inject constructor(
return
}
if (keyguardViewController.isShowing) {
if (keyguardViewController.isShowing && !playingCannedUnlockAnimation) {
showOrHideSurfaceIfDismissAmountThresholdsReached()
// If the surface is visible or it's about to be, start updating its appearance to
@@ -750,11 +582,6 @@ class KeyguardUnlockAnimationController @Inject constructor(
updateSurfaceBehindAppearAmount()
}
}
// The end of the SmartSpace transition can occur after the keyguard is hidden (when we tell
// Launcher's SmartSpace to become visible again), so update it even if the keyguard view is
// no longer showing.
applyDismissAmountToSmartspaceTransition()
}
/**
@@ -775,16 +602,15 @@ class KeyguardUnlockAnimationController @Inject constructor(
return
}
if (!keyguardStateController.isShowing) {
return
}
val dismissAmount = keyguardStateController.dismissAmount
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.
if (!unlockingToLauncherWithInWindowAnimations &&
canPerformInWindowLauncherAnimations()) {
unlockingToLauncherWithInWindowAnimations = true
prepareLauncherWorkspaceForUnlockAnimation()
}
!keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) {
keyguardViewMediator.get().showSurfaceBehindKeyguard()
fadeInSurfaceBehind()
} else if (dismissAmount < DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
@@ -828,58 +654,101 @@ class KeyguardUnlockAnimationController @Inject constructor(
}
/**
* Updates flags related to the SmartSpace transition in response to a change in keyguard
* dismiss amount, and also updates the SmartSpaceTransitionController, which will let Launcher
* know if it needs to do something as a result.
* 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 applyDismissAmountToSmartspaceTransition() {
if (!featureFlags.isEnabled(Flags.SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED)) {
fun setSurfaceBehindAppearAmount(amount: Float) {
if (surfaceBehindRemoteAnimationTarget == null) {
return
}
// If we are playing the canned animation, the smartspace is being animated directly between
// its original location and the location of the launcher smartspace by smartspaceAnimator.
// We can ignore the dismiss amount, which is caused by panel height changes as the panel is
// flung away.
if (playingCannedUnlockAnimation) {
return
// Otherwise, animate in the surface's scale/transltion.
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
applyParamsToSurface(
SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
surfaceBehindRemoteAnimationTarget!!.leash)
.withMatrix(surfaceBehindMatrix)
.withCornerRadius(roundedCornerRadius)
.withAlpha(animationAlpha)
.build())
}
/**
* Called by [KeyguardViewMediator] to let us know that the remote animation has finished, and
* we should clean up all of our state.
*
* This is generally triggered by us, calling
* [KeyguardViewMediator.finishSurfaceBehindRemoteAnimation].
*/
fun notifyFinishedKeyguardExitAnimation(cancelled: Boolean) {
// Cancel any pending actions.
handler.removeCallbacksAndMessages(null)
// Make sure we made the surface behind fully visible, just in case. It should already be
// fully visible. If the launcher is doing its own animation, let it continue without
// forcing it to 1f.
setSurfaceBehindAppearAmount(1f)
launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */)
// That target is no longer valid since the animation finished, null it out.
surfaceBehindRemoteAnimationTarget = null
surfaceBehindParams = null
playingCannedUnlockAnimation = false
willUnlockWithInWindowLauncherAnimations = false
willUnlockWithSmartspaceTransition = false
// The lockscreen surface is gone, so it is now safe to re-show the smartspace.
lockscreenSmartspace!!.visibility = View.VISIBLE
listeners.forEach { it.onUnlockAnimationFinished() }
}
/**
* Asks the keyguard view to hide, using the start time from the beginning of the remote
* animation.
*/
fun hideKeyguardViewAfterRemoteAnimation() {
if (keyguardViewController.isShowing) {
// Hide the keyguard, with no fade out since we animated it away during the unlock.
keyguardViewController.hide(
surfaceBehindRemoteAnimationStartTime,
0 /* fadeOutDuration */
)
} else {
Log.e(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
"showing. Ignoring...")
}
}
val dismissAmount = keyguardStateController.dismissAmount
// If we've begun a swipe, and haven't yet tried doing the SmartSpace transition, do that
// now.
if (!attemptedSmartSpaceTransitionForThisSwipe &&
keyguardViewController.isShowing &&
dismissAmount > 0f &&
dismissAmount < 1f) {
attemptedSmartSpaceTransitionForThisSwipe = true
if (prepareForSmartspaceTransition()) {
unlockingWithSmartspaceTransition = true
// Ensure that the smartspace is invisible if we're doing the transition, and
// visible if we aren't.
launcherUnlockController?.setSmartspaceVisibility(
if (unlockingWithSmartspaceTransition) View.INVISIBLE else View.VISIBLE)
if (unlockingWithSmartspaceTransition) {
listeners.forEach { it.onSmartspaceSharedElementTransitionStarted() }
}
}
} else if (attemptedSmartSpaceTransitionForThisSwipe &&
(dismissAmount == 0f || dismissAmount == 1f)) {
attemptedSmartSpaceTransitionForThisSwipe = false
unlockingWithSmartspaceTransition = false
launcherUnlockController?.setSmartspaceVisibility(View.VISIBLE)
}
if (unlockingWithSmartspaceTransition) {
val swipedFraction: Float = keyguardStateController.dismissAmount
val progress = swipedFraction / DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD
smartspaceUnlockProgress = progress
setSmartspaceProgressToDestinationBounds(smartspaceUnlockProgress)
}
private fun applyParamsToSurface(params: SyncRtSurfaceTransactionApplier.SurfaceParams) {
surfaceTransactionApplier!!.scheduleApply(params)
surfaceBehindParams = params
}
private fun fadeInSurfaceBehind() {
@@ -892,14 +761,8 @@ class KeyguardUnlockAnimationController @Inject constructor(
surfaceBehindAlphaAnimator.reverse()
}
/**
* Prepare for the smartspace shared element transition, if possible, by figuring out where we
* are animating from/to.
*
* Return true if we'll be able to do the smartspace transition, or false if conditions are not
* right to do it right now.
*/
private fun prepareForSmartspaceTransition(): Boolean {
private fun shouldPerformSmartspaceTransition(): Boolean {
// Feature is disabled, so we don't want to.
if (!featureFlags.isEnabled(Flags.SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED)) {
return false
@@ -940,45 +803,22 @@ class KeyguardUnlockAnimationController @Inject constructor(
return false
}
unlockingWithSmartspaceTransition = true
smartspaceDestBounds.setEmpty()
// Assuming we were able to retrieve the launcher's state, start the lockscreen
// smartspace at 0, 0, and save its starting bounds.
with(lockscreenSmartspace!!) {
translationX = 0f
translationY = 0f
getBoundsOnScreen(smartspaceOriginBounds)
}
// Set the destination bounds to the launcher smartspace's bounds, offset by any
// padding on our smartspace.
with(smartspaceDestBounds) {
set(launcherSmartspaceState!!.boundsOnScreen)
offset(-lockscreenSmartspace!!.paddingLeft, -lockscreenSmartspace!!.paddingTop)
// We started to swipe to dismiss, but now we're doing a fling animation to complete the
// dismiss. In this case, the smartspace swiped away with the rest of the keyguard, so don't
// do the shared element transition.
if (keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture) {
return false
}
return true
}
/**
* Whether we should be able to do the in-window launcher animations given the current state of
* the device.
*/
fun canPerformInWindowLauncherAnimations(): Boolean {
return isNexusLauncherUnderneath() &&
launcherUnlockController != null &&
// Temporarily disable for foldables since foldable launcher has two first pages,
// which breaks the in-window animation.
!isFoldable(context)
}
/**
* Whether we are currently in the process of unlocking the keyguard, and we are performing the
* shared element SmartSpace transition.
*/
fun isUnlockingWithSmartSpaceTransition(): Boolean {
return unlockingWithSmartspaceTransition
return willUnlockWithSmartspaceTransition
}
/**

View File

@@ -2648,7 +2648,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
}
/** If it's running, finishes the RemoteAnimation on the surface behind the keyguard. */
public void finishSurfaceBehindRemoteAnimation(boolean cancelled) {
void finishSurfaceBehindRemoteAnimation(boolean cancelled) {
if (!mSurfaceBehindRemoteAnimationRunning) {
return;
}

View File

@@ -2937,8 +2937,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
@Override
public void showKeyguardImpl() {
Trace.beginSection("CentralSurfaces#showKeyguard");
// In case we're locking while a smartspace transition is in progress, reset it.
mKeyguardUnlockAnimationController.resetSmartspaceTransition();
if (mKeyguardStateController.isLaunchTransitionFadingAway()) {
mNotificationPanelViewController.cancelAnimation();
onLaunchTransitionFadingEnded();

View File

@@ -885,7 +885,10 @@ public class NotificationPanelViewController extends PanelViewController {
@Override
public void onUnlockAnimationStarted(
boolean playingCannedAnimation, boolean isWakeAndUnlock) {
boolean playingCannedAnimation,
boolean isWakeAndUnlock,
long unlockAnimationStartDelay,
long unlockAnimationDuration) {
// Disable blurs while we're unlocking so that panel expansion does not
// cause blurring. This will eventually be re-enabled by the panel view on
// ACTION_UP, since the user's finger might still be down after a swipe to
@@ -902,7 +905,22 @@ public class NotificationPanelViewController extends PanelViewController {
onTrackingStopped(false);
instantCollapse();
} else {
fling(0f, false, 1f, false);
mView.animate()
.alpha(0f)
.setStartDelay(0)
// Translate up by 4%.
.translationY(mView.getHeight() * -0.04f)
// This start delay is to give us time to animate out before
// the launcher icons animation starts, so use that as our
// duration.
.setDuration(unlockAnimationStartDelay)
.setInterpolator(EMPHASIZED_DECELERATE)
.withEndAction(() -> {
instantCollapse();
mView.setAlpha(1f);
mView.setTranslationY(0f);
})
.start();
}
}
}

View File

@@ -22,7 +22,6 @@ import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -55,8 +54,6 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase {
@Mock
DozeParameters mDozeParameters;
@Mock
KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@Mock
ScreenOffAnimationController mScreenOffAnimationController;
@Captor
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
@@ -75,7 +72,6 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase {
mKeyguardUpdateMonitor,
mConfigurationController,
mDozeParameters,
mKeyguardUnlockAnimationController,
mScreenOffAnimationController);
}