Merge "Animate app launches from lockscreen" into sc-dev am: 2c52d52f6d

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/14411116

Change-Id: Ib0271d0b8f2a8587517b5f019da32af318a6a2ff
This commit is contained in:
Jordan Demeulenaere
2021-05-31 09:17:11 +00:00
committed by Automerger Merge Worker
9 changed files with 251 additions and 76 deletions

View File

@@ -32,7 +32,10 @@ import kotlin.math.roundToInt
* A class that allows activities to be started in a seamless way from a view that is transforming * A class that allows activities to be started in a seamless way from a view that is transforming
* nicely into the starting window. * nicely into the starting window.
*/ */
class ActivityLaunchAnimator(context: Context) { class ActivityLaunchAnimator(
private val keyguardHandler: KeyguardHandler,
context: Context
) {
private val TAG = this::class.java.simpleName private val TAG = this::class.java.simpleName
companion object { companion object {
@@ -104,15 +107,22 @@ class ActivityLaunchAnimator(context: Context) {
Log.d(TAG, "Starting intent with a launch animation") Log.d(TAG, "Starting intent with a launch animation")
val runner = Runner(controller) val runner = Runner(controller)
val animationAdapter = RemoteAnimationAdapter( val isOnKeyguard = keyguardHandler.isOnKeyguard()
runner,
ANIMATION_DURATION, // Pass the RemoteAnimationAdapter to the intent starter only if we are not on the keyguard.
ANIMATION_DURATION - 150 /* statusBarTransitionDelay */ val animationAdapter = if (!isOnKeyguard) {
) RemoteAnimationAdapter(
runner,
ANIMATION_DURATION,
ANIMATION_DURATION - 150 /* statusBarTransitionDelay */
)
} else {
null
}
// Register the remote animation for the given package to also animate trampoline // Register the remote animation for the given package to also animate trampoline
// activity launches. // activity launches.
if (packageName != null) { if (packageName != null && animationAdapter != null) {
try { try {
ActivityTaskManager.getService().registerRemoteAnimationForNextActivityStart( ActivityTaskManager.getService().registerRemoteAnimationForNextActivityStart(
packageName, animationAdapter) packageName, animationAdapter)
@@ -122,20 +132,30 @@ class ActivityLaunchAnimator(context: Context) {
} }
val launchResult = intentStarter(animationAdapter) val launchResult = intentStarter(animationAdapter)
val willAnimate = launchResult == ActivityManager.START_TASK_TO_FRONT ||
launchResult == ActivityManager.START_SUCCESS
Log.d(TAG, "launchResult=$launchResult willAnimate=$willAnimate") // Only animate if the app is not already on top and will be opened, unless we are on the
// keyguard.
val willAnimate =
launchResult == ActivityManager.START_TASK_TO_FRONT ||
launchResult == ActivityManager.START_SUCCESS ||
(launchResult == ActivityManager.START_DELIVERED_TO_TOP && isOnKeyguard)
Log.d(TAG, "launchResult=$launchResult willAnimate=$willAnimate isOnKeyguard=$isOnKeyguard")
controller.callOnIntentStartedOnMainThread(willAnimate) controller.callOnIntentStartedOnMainThread(willAnimate)
// If we expect an animation, post a timeout to cancel it in case the remote animation is // If we expect an animation, post a timeout to cancel it in case the remote animation is
// never started. // never started.
if (willAnimate) { if (willAnimate) {
runner.postTimeout() runner.postTimeout()
// Hide the keyguard using the launch animation instead of the default unlock animation.
if (isOnKeyguard) {
keyguardHandler.hideKeyguardWithAnimation(runner)
}
} }
} }
internal fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) { private fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) {
if (Looper.myLooper() != Looper.getMainLooper()) { if (Looper.myLooper() != Looper.getMainLooper()) {
this.launchContainer.context.mainExecutor.execute { this.launchContainer.context.mainExecutor.execute {
this.onIntentStarted(willAnimate) this.onIntentStarted(willAnimate)
@@ -179,6 +199,14 @@ class ActivityLaunchAnimator(context: Context) {
fun startPendingIntent(animationAdapter: RemoteAnimationAdapter?): Int fun startPendingIntent(animationAdapter: RemoteAnimationAdapter?): Int
} }
interface KeyguardHandler {
/** Whether we are currently on the keyguard or not. */
fun isOnKeyguard(): Boolean
/** Hide the keyguard and animate using [runner]. */
fun hideKeyguardWithAnimation(runner: IRemoteAnimationRunner)
}
/** /**
* A controller that takes care of applying the animation to an expanding view. * A controller that takes care of applying the animation to an expanding view.
* *
@@ -337,17 +365,17 @@ class ActivityLaunchAnimator(context: Context) {
override fun onAnimationStart( override fun onAnimationStart(
@WindowManager.TransitionOldType transit: Int, @WindowManager.TransitionOldType transit: Int,
remoteAnimationTargets: Array<out RemoteAnimationTarget>, apps: Array<out RemoteAnimationTarget>?,
remoteAnimationWallpaperTargets: Array<out RemoteAnimationTarget>, wallpapers: Array<out RemoteAnimationTarget>?,
remoteAnimationNonAppTargets: Array<out RemoteAnimationTarget>, nonApps: Array<out RemoteAnimationTarget>?,
iRemoteAnimationFinishedCallback: IRemoteAnimationFinishedCallback iCallback: IRemoteAnimationFinishedCallback?
) { ) {
removeTimeout() removeTimeout()
// The animation was started too late and we already notified the controller that it // The animation was started too late and we already notified the controller that it
// timed out. // timed out.
if (timedOut) { if (timedOut) {
invokeCallback(iRemoteAnimationFinishedCallback) iCallback?.invoke()
return return
} }
@@ -358,30 +386,29 @@ class ActivityLaunchAnimator(context: Context) {
} }
context.mainExecutor.execute { context.mainExecutor.execute {
startAnimation(remoteAnimationTargets, remoteAnimationNonAppTargets, startAnimation(apps, nonApps, iCallback)
iRemoteAnimationFinishedCallback)
} }
} }
private fun startAnimation( private fun startAnimation(
remoteAnimationTargets: Array<out RemoteAnimationTarget>, apps: Array<out RemoteAnimationTarget>?,
remoteAnimationNonAppTargets: Array<out RemoteAnimationTarget>, nonApps: Array<out RemoteAnimationTarget>?,
iCallback: IRemoteAnimationFinishedCallback iCallback: IRemoteAnimationFinishedCallback?
) { ) {
Log.d(TAG, "Remote animation started") Log.d(TAG, "Remote animation started")
val window = remoteAnimationTargets.firstOrNull { val window = apps?.firstOrNull {
it.mode == RemoteAnimationTarget.MODE_OPENING it.mode == RemoteAnimationTarget.MODE_OPENING
} }
if (window == null) { if (window == null) {
Log.d(TAG, "Aborting the animation as no window is opening") Log.d(TAG, "Aborting the animation as no window is opening")
removeTimeout() removeTimeout()
invokeCallback(iCallback) iCallback?.invoke()
controller.onLaunchAnimationCancelled() controller.onLaunchAnimationCancelled()
return return
} }
val navigationBar = remoteAnimationNonAppTargets.firstOrNull { val navigationBar = nonApps?.firstOrNull {
it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
} }
@@ -439,7 +466,7 @@ class ActivityLaunchAnimator(context: Context) {
override fun onAnimationEnd(animation: Animator?) { override fun onAnimationEnd(animation: Animator?) {
Log.d(TAG, "Animation ended") Log.d(TAG, "Animation ended")
invokeCallback(iCallback) iCallback?.invoke()
controller.onLaunchAnimationEnd(isExpandingFullyAbove) controller.onLaunchAnimationEnd(isExpandingFullyAbove)
} }
}) })
@@ -519,8 +546,11 @@ class ActivityLaunchAnimator(context: Context) {
) )
// The scale will also be applied to the corner radius, so we divide by the scale to // The scale will also be applied to the corner radius, so we divide by the scale to
// keep the original radius. // keep the original radius. We use the max of (topCornerRadius, bottomCornerRadius) to
val cornerRadius = minOf(state.topCornerRadius, state.bottomCornerRadius) / scale // make sure that the window does not draw itself behind the expanding view. This is
// especially important for lock screen animations, where the window is not clipped by
// the shade.
val cornerRadius = maxOf(state.topCornerRadius, state.bottomCornerRadius) / scale
val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(window.leash) val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(window.leash)
.withAlpha(1f) .withAlpha(1f)
.withMatrix(matrix) .withMatrix(matrix)
@@ -585,9 +615,9 @@ class ActivityLaunchAnimator(context: Context) {
} }
} }
private fun invokeCallback(iCallback: IRemoteAnimationFinishedCallback) { private fun IRemoteAnimationFinishedCallback.invoke() {
try { try {
iCallback.onAnimationFinished() onAnimationFinished()
} catch (e: RemoteException) { } catch (e: RemoteException) {
e.printStackTrace() e.printStackTrace()
} }

View File

@@ -70,7 +70,7 @@ public class KeyguardService extends Service {
/** /**
* @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
*/ */
static boolean sEnableRemoteKeyguardAnimation = public static boolean sEnableRemoteKeyguardAnimation =
SystemProperties.getBoolean(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, false); SystemProperties.getBoolean(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, false);
private final KeyguardViewMediator mKeyguardViewMediator; private final KeyguardViewMediator mKeyguardViewMediator;

View File

@@ -52,6 +52,7 @@ import android.media.SoundPool;
import android.os.Bundle; import android.os.Bundle;
import android.os.DeadObjectException; import android.os.DeadObjectException;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
import android.os.PowerManager; import android.os.PowerManager;
@@ -71,6 +72,7 @@ import android.util.Slog;
import android.util.SparseBooleanArray; import android.util.SparseBooleanArray;
import android.util.SparseIntArray; import android.util.SparseIntArray;
import android.view.IRemoteAnimationFinishedCallback; import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget; import android.view.RemoteAnimationTarget;
import android.view.SyncRtSurfaceTransactionApplier; import android.view.SyncRtSurfaceTransactionApplier;
import android.view.View; import android.view.View;
@@ -426,6 +428,11 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
*/ */
private IRemoteAnimationFinishedCallback mSurfaceBehindRemoteAnimationFinishedCallback; private IRemoteAnimationFinishedCallback mSurfaceBehindRemoteAnimationFinishedCallback;
/**
* The animation runner to use for the next exit animation.
*/
private IRemoteAnimationRunner mKeyguardExitAnimationRunner;
private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener = private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
new DeviceConfig.OnPropertiesChangedListener() { new DeviceConfig.OnPropertiesChangedListener() {
@Override @Override
@@ -1605,6 +1612,16 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
Trace.endSection(); Trace.endSection();
} }
/** Hide the keyguard and let {@code runner} handle the animation. */
public void hideWithAnimation(IRemoteAnimationRunner runner) {
if (!mShowing) {
return;
}
mKeyguardExitAnimationRunner = runner;
hideLocked();
}
public boolean isSecure() { public boolean isSecure() {
return isSecure(KeyguardUpdateMonitor.getCurrentUser()); return isSecure(KeyguardUpdateMonitor.getCurrentUser());
} }
@@ -2050,6 +2067,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
// TODO: We should stop it early by disabling the swipe up flow. Right now swipe up // TODO: We should stop it early by disabling the swipe up flow. Right now swipe up
// still completes and makes the screen blank. // still completes and makes the screen blank.
if (DEBUG) Log.d(TAG, "Split system user, quit unlocking."); if (DEBUG) Log.d(TAG, "Split system user, quit unlocking.");
mKeyguardExitAnimationRunner = null;
return; return;
} }
mHiding = true; mHiding = true;
@@ -2103,9 +2121,37 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
playSounds(false); playSounds(false);
} }
IRemoteAnimationRunner runner = mKeyguardExitAnimationRunner;
mKeyguardExitAnimationRunner = null;
if (KeyguardService.sEnableRemoteKeyguardAnimation && runner != null
&& finishedCallback != null) {
// Wrap finishedCallback to clean up the keyguard state once the animation is done.
IRemoteAnimationFinishedCallback callback =
new IRemoteAnimationFinishedCallback() {
@Override
public void onAnimationFinished() throws RemoteException {
finishedCallback.onAnimationFinished();
onKeyguardExitFinished();
mKeyguardViewControllerLazy.get().hide(0 /* startTime */,
0 /* fadeoutDuration */);
}
@Override
public IBinder asBinder() {
return finishedCallback.asBinder();
}
};
try {
runner.onAnimationStart(WindowManager.TRANSIT_KEYGUARD_GOING_AWAY, apps,
wallpapers, nonApps, callback);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call onAnimationStart", e);
}
// When remaining on the shade, there's no need to do a fancy remote animation, // When remaining on the shade, there's no need to do a fancy remote animation,
// it will dismiss the panel in that case. // it will dismiss the panel in that case.
if (KeyguardService.sEnableRemoteKeyguardAnimation } else if (KeyguardService.sEnableRemoteKeyguardAnimation
&& !mStatusBarStateController.leaveOpenOnKeyguardHide() && !mStatusBarStateController.leaveOpenOnKeyguardHide()
&& apps != null && apps.length > 0) { && apps != null && apps.length > 0) {
mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback; mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
@@ -2115,9 +2161,6 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
mKeyguardUnlockAnimationControllerLazy.get().notifyStartKeyguardExitAnimation( mKeyguardUnlockAnimationControllerLazy.get().notifyStartKeyguardExitAnimation(
apps[0], startTime, mSurfaceBehindRemoteAnimationRequested); apps[0], startTime, mSurfaceBehindRemoteAnimationRequested);
} else { } else {
setShowingLocked(false);
mWakeAndUnlocking = false;
mDismissCallbackRegistry.notifyDismissSucceeded();
mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration); mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
// TODO(bc-animation): When remote animation is enabled for keyguard exit animation, // TODO(bc-animation): When remote animation is enabled for keyguard exit animation,
@@ -2140,8 +2183,8 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
SyncRtSurfaceTransactionApplier.SurfaceParams params = SyncRtSurfaceTransactionApplier.SurfaceParams params =
new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder( new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
primary.leash) primary.leash)
.withAlpha(animation.getAnimatedFraction()) .withAlpha(animation.getAnimatedFraction())
.build(); .build();
applier.scheduleApply(params); applier.scheduleApply(params);
}); });
anim.addListener(new AnimatorListenerAdapter() { anim.addListener(new AnimatorListenerAdapter() {
@@ -2165,16 +2208,24 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
}); });
anim.start(); anim.start();
}); });
resetKeyguardDonePendingLocked();
mHideAnimationRun = false; onKeyguardExitFinished();
adjustStatusBarLocked();
sendUserPresentBroadcast();
} }
} }
Trace.endSection(); Trace.endSection();
} }
private void onKeyguardExitFinished() {
setShowingLocked(false);
mWakeAndUnlocking = false;
mDismissCallbackRegistry.notifyDismissSucceeded();
resetKeyguardDonePendingLocked();
mHideAnimationRun = false;
adjustStatusBarLocked();
sendUserPresentBroadcast();
}
/** /**
* Whether we're currently animating between the keyguard and the app/launcher surface behind * 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). * it, or will be shortly (which happens if we started a fling to dismiss the keyguard).
@@ -2211,21 +2262,13 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
// Block the panel from expanding, in case we were doing a swipe to dismiss gesture. // Block the panel from expanding, in case we were doing a swipe to dismiss gesture.
mKeyguardViewControllerLazy.get().blockPanelExpansionFromCurrentTouch(); mKeyguardViewControllerLazy.get().blockPanelExpansionFromCurrentTouch();
final boolean wasShowing = mShowing; final boolean wasShowing = mShowing;
setShowingLocked(false); onKeyguardExitFinished();
mWakeAndUnlocking = false;
mDismissCallbackRegistry.notifyDismissSucceeded();
if (mKeyguardStateController.isDismissingFromSwipe() || !wasShowing) { if (mKeyguardStateController.isDismissingFromSwipe() || !wasShowing) {
mKeyguardUnlockAnimationControllerLazy.get().hideKeyguardViewAfterRemoteAnimation(); mKeyguardUnlockAnimationControllerLazy.get().hideKeyguardViewAfterRemoteAnimation();
} }
finishSurfaceBehindRemoteAnimation(); finishSurfaceBehindRemoteAnimation();
resetKeyguardDonePendingLocked();
mHideAnimationRun = false;
adjustStatusBarLocked();
sendUserPresentBroadcast();
mSurfaceBehindRemoteAnimationRequested = false; mSurfaceBehindRemoteAnimationRequested = false;
mKeyguardUnlockAnimationControllerLazy.get().notifyFinishedKeyguardExitAnimation(); mKeyguardUnlockAnimationControllerLazy.get().notifyFinishedKeyguardExitAnimation();
} }

View File

@@ -211,6 +211,12 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
return super.onInterceptTouchEvent(ev); return super.onInterceptTouchEvent(ev);
} }
/**
* Called by the TouchHandler when this view is tapped. This will be called for actual taps
* only, i.e. taps that have been filtered by the FalsingManager.
*/
public void onTap() {}
/** Sets the last action up time this view was touched. */ /** Sets the last action up time this view was touched. */
void setLastActionUpTime(long eventTime) { void setLastActionUpTime(long eventTime) {
mLastActionUpTime = eventTime; mLastActionUpTime = eventTime;

View File

@@ -118,7 +118,11 @@ public class ActivatableNotificationViewController
if (ev.getAction() == MotionEvent.ACTION_UP) { if (ev.getAction() == MotionEvent.ACTION_UP) {
// If this is a false tap, capture the even so it doesn't result in a click. // If this is a false tap, capture the even so it doesn't result in a click.
return mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY); boolean falseTap = mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY);
if (!falseTap && v instanceof ActivatableNotificationView) {
((ActivatableNotificationView) v).onTap();
}
return falseTap;
} }
return result; return result;
} }

View File

@@ -2290,6 +2290,23 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
setRippleAllowed(allowed); setRippleAllowed(allowed);
} }
@Override
public void onTap() {
// This notification will expand and animates into the content activity, so we disable the
// ripple. We will restore its value once the tap/click is actually performed.
if (mEntry.getSbn().getNotification().contentIntent != null) {
setRippleAllowed(false);
}
}
@Override
public boolean performClick() {
// We force-disabled the ripple in onTap. When this method is called, the code drawing the
// ripple will already have been called so we can restore its value now.
updateRippleAllowed();
return super.performClick();
}
@Override @Override
public int getIntrinsicHeight() { public int getIntrinsicHeight() {
if (isUserLocked()) { if (isUserLocked()) {

View File

@@ -105,6 +105,7 @@ import android.util.Log;
import android.util.MathUtils; import android.util.MathUtils;
import android.util.Slog; import android.util.Slog;
import android.view.Display; import android.view.Display;
import android.view.IRemoteAnimationRunner;
import android.view.IWindowManager; import android.view.IWindowManager;
import android.view.InsetsState.InternalInsetsType; import android.view.InsetsState.InternalInsetsType;
import android.view.KeyEvent; import android.view.KeyEvent;
@@ -164,6 +165,7 @@ import com.android.systemui.emergency.EmergencyGesture;
import com.android.systemui.fragments.ExtensionFragmentListener; import com.android.systemui.fragments.ExtensionFragmentListener;
import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.ScreenLifecycle;
@@ -267,7 +269,8 @@ public class StatusBar extends SystemUI implements DemoMode,
OnHeadsUpChangedListener, CommandQueue.Callbacks, OnHeadsUpChangedListener, CommandQueue.Callbacks,
ColorExtractor.OnColorsChangedListener, ConfigurationListener, ColorExtractor.OnColorsChangedListener, ConfigurationListener,
StatusBarStateController.StateListener, StatusBarStateController.StateListener,
LifecycleOwner, BatteryController.BatteryStateChangeCallback { LifecycleOwner, BatteryController.BatteryStateChangeCallback,
ActivityLaunchAnimator.KeyguardHandler {
public static final boolean MULTIUSER_DEBUG = false; public static final boolean MULTIUSER_DEBUG = false;
protected static final int MSG_HIDE_RECENT_APPS = 1020; protected static final int MSG_HIDE_RECENT_APPS = 1020;
@@ -1413,7 +1416,7 @@ public class StatusBar extends SystemUI implements DemoMode,
private void setUpPresenter() { private void setUpPresenter() {
// Set up the initial notification state. // Set up the initial notification state.
mActivityLaunchAnimator = new ActivityLaunchAnimator(mContext); mActivityLaunchAnimator = new ActivityLaunchAnimator(this, mContext);
mNotificationAnimationProvider = new NotificationLaunchAnimatorControllerProvider( mNotificationAnimationProvider = new NotificationLaunchAnimatorControllerProvider(
mNotificationShadeWindowViewController, mNotificationShadeWindowViewController,
mStackScrollerController.getNotificationListContainer(), mStackScrollerController.getNotificationListContainer(),
@@ -2072,10 +2075,45 @@ public class StatusBar extends SystemUI implements DemoMode,
} }
} }
/** Whether we should animate an activity launch. */ /**
public boolean areLaunchAnimationsEnabled() { * Whether we should animate an activity launch.
// TODO(b/184121838): Support lock screen launch animations. *
return mState == StatusBarState.SHADE && !isOccluded(); * Note: This method must be called *before* dismissing the keyguard.
*/
public boolean shouldAnimateLaunch(boolean isActivityIntent) {
// TODO(b/184121838): Support launch animations when occluded.
if (isOccluded()) {
return false;
}
// Always animate if we are unlocked.
if (!mKeyguardStateController.isShowing()) {
return true;
}
// If we are locked, only animate if remote unlock animations are enabled and we can dismiss
// the lock screen without challenging the user. We also don't animate non-activity
// launches as they can break the animation.
// TODO(b/184121838): Support non activity launches on the lockscreen.
return isActivityIntent
&& KeyguardService.sEnableRemoteKeyguardAnimation
&& mKeyguardStateController.canDismissLockScreen();
}
@Override
public boolean isOnKeyguard() {
return mKeyguardStateController.isShowing();
}
@Override
public void hideKeyguardWithAnimation(IRemoteAnimationRunner runner) {
if (!mKeyguardStateController.canDismissLockScreen()) {
Log.wtf(TAG,
"Unable to hide keyguard with animation as the keyguard can't be dismissed");
return;
}
mKeyguardViewMediator.hideWithAnimation(runner);
} }
public boolean isDeviceInVrMode() { public boolean isDeviceInVrMode() {
@@ -2796,8 +2834,9 @@ public class StatusBar extends SystemUI implements DemoMode,
final boolean afterKeyguardGone = mActivityIntentHelper.wouldLaunchResolverActivity( final boolean afterKeyguardGone = mActivityIntentHelper.wouldLaunchResolverActivity(
intent, mLockscreenUserManager.getCurrentUserId()); intent, mLockscreenUserManager.getCurrentUserId());
ActivityLaunchAnimator.Controller animController = wrapAnimationController( ActivityLaunchAnimator.Controller animController =
animationController, dismissShade); shouldAnimateLaunch(true /* isActivityIntent */) ? wrapAnimationController(
animationController, dismissShade) : null;
// If we animate, we will dismiss the shade only once the animation is done. This is taken // If we animate, we will dismiss the shade only once the animation is done. This is taken
// care of by the StatusBarLaunchAnimationController. // care of by the StatusBarLaunchAnimationController.
@@ -2811,7 +2850,7 @@ public class StatusBar extends SystemUI implements DemoMode,
int[] result = new int[]{ActivityManager.START_CANCELED}; int[] result = new int[]{ActivityManager.START_CANCELED};
mActivityLaunchAnimator.startIntentWithAnimation(animController, mActivityLaunchAnimator.startIntentWithAnimation(animController,
areLaunchAnimationsEnabled(), intent.getPackage(), (adapter) -> { true /* animate */, intent.getPackage(), (adapter) -> {
ActivityOptions options = new ActivityOptions( ActivityOptions options = new ActivityOptions(
getActivityOptions(mDisplayId, adapter)); getActivityOptions(mDisplayId, adapter));
options.setDisallowEnterPictureInPictureWhileLaunching( options.setDisallowEnterPictureInPictureWhileLaunching(
@@ -4606,6 +4645,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mLockscreenUserManager.getCurrentUserId()); mLockscreenUserManager.getCurrentUserId());
boolean collapse = animationController == null; boolean collapse = animationController == null;
boolean animate = shouldAnimateLaunch(intent.isActivity());
executeActionDismissingKeyguard(() -> { executeActionDismissingKeyguard(() -> {
try { try {
// We wrap animationCallback with a StatusBarLaunchAnimatorController so that the // We wrap animationCallback with a StatusBarLaunchAnimatorController so that the
@@ -4615,7 +4655,7 @@ public class StatusBar extends SystemUI implements DemoMode,
animationController, this, intent.isActivity()) : null; animationController, this, intent.isActivity()) : null;
mActivityLaunchAnimator.startPendingIntentWithAnimation( mActivityLaunchAnimator.startPendingIntentWithAnimation(
controller, areLaunchAnimationsEnabled(), intent.getCreatorPackage(), controller, animate, intent.getCreatorPackage(),
(animationAdapter) -> intent.sendAndReturnResult(null, 0, null, null, null, (animationAdapter) -> intent.sendAndReturnResult(null, 0, null, null, null,
null, getActivityOptions(mDisplayId, animationAdapter))); null, getActivityOptions(mDisplayId, animationAdapter)));
} catch (PendingIntent.CanceledException e) { } catch (PendingIntent.CanceledException e) {

View File

@@ -255,14 +255,14 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
final boolean afterKeyguardGone = isActivityIntent final boolean afterKeyguardGone = isActivityIntent
&& mActivityIntentHelper.wouldLaunchResolverActivity(intent.getIntent(), && mActivityIntentHelper.wouldLaunchResolverActivity(intent.getIntent(),
mLockscreenUserManager.getCurrentUserId()); mLockscreenUserManager.getCurrentUserId());
final boolean wasOccluded = mStatusBar.isOccluded(); final boolean animate = mStatusBar.shouldAnimateLaunch(isActivityIntent);
boolean showOverLockscreen = mKeyguardStateController.isShowing() && intent != null boolean showOverLockscreen = mKeyguardStateController.isShowing() && intent != null
&& mActivityIntentHelper.wouldShowOverLockscreen(intent.getIntent(), && mActivityIntentHelper.wouldShowOverLockscreen(intent.getIntent(),
mLockscreenUserManager.getCurrentUserId()); mLockscreenUserManager.getCurrentUserId());
ActivityStarter.OnDismissAction postKeyguardAction = ActivityStarter.OnDismissAction postKeyguardAction =
() -> handleNotificationClickAfterKeyguardDismissed( () -> handleNotificationClickAfterKeyguardDismissed(
entry, row, controller, intent, entry, row, controller, intent,
isActivityIntent, wasOccluded, showOverLockscreen); isActivityIntent, animate, showOverLockscreen);
if (showOverLockscreen) { if (showOverLockscreen) {
mIsCollapsingToShowActivityOverLockscreen = true; mIsCollapsingToShowActivityOverLockscreen = true;
postKeyguardAction.onDismiss(); postKeyguardAction.onDismiss();
@@ -278,7 +278,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
RemoteInputController controller, RemoteInputController controller,
PendingIntent intent, PendingIntent intent,
boolean isActivityIntent, boolean isActivityIntent,
boolean wasOccluded, boolean animate,
boolean showOverLockscreen) { boolean showOverLockscreen) {
mLogger.logHandleClickAfterKeyguardDismissed(entry.getKey()); mLogger.logHandleClickAfterKeyguardDismissed(entry.getKey());
@@ -293,7 +293,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
final Runnable runnable = () -> handleNotificationClickAfterPanelCollapsed( final Runnable runnable = () -> handleNotificationClickAfterPanelCollapsed(
entry, row, controller, intent, entry, row, controller, intent,
isActivityIntent, wasOccluded); isActivityIntent, animate);
if (showOverLockscreen) { if (showOverLockscreen) {
mShadeController.addPostCollapseAction(runnable); mShadeController.addPostCollapseAction(runnable);
@@ -314,7 +314,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
RemoteInputController controller, RemoteInputController controller,
PendingIntent intent, PendingIntent intent,
boolean isActivityIntent, boolean isActivityIntent,
boolean wasOccluded) { boolean animate) {
String notificationKey = entry.getKey(); String notificationKey = entry.getKey();
mLogger.logHandleClickAfterPanelCollapsed(notificationKey); mLogger.logHandleClickAfterPanelCollapsed(notificationKey);
@@ -360,8 +360,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
removeHUN(row); removeHUN(row);
expandBubbleStackOnMainThread(entry); expandBubbleStackOnMainThread(entry);
} else { } else {
startNotificationIntent( startNotificationIntent(intent, fillInIntent, entry, row, animate, isActivityIntent);
intent, fillInIntent, entry, row, wasOccluded, isActivityIntent);
} }
if (isActivityIntent || canBubble) { if (isActivityIntent || canBubble) {
mAssistManagerLazy.get().hideAssist(); mAssistManagerLazy.get().hideAssist();
@@ -426,7 +425,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
Intent fillInIntent, Intent fillInIntent,
NotificationEntry entry, NotificationEntry entry,
ExpandableNotificationRow row, ExpandableNotificationRow row,
boolean wasOccluded, boolean animate,
boolean isActivityIntent) { boolean isActivityIntent) {
mLogger.logStartNotificationIntent(entry.getKey(), intent); mLogger.logStartNotificationIntent(entry.getKey(), intent);
try { try {
@@ -436,8 +435,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
isActivityIntent); isActivityIntent);
mActivityLaunchAnimator.startPendingIntentWithAnimation(animationController, mActivityLaunchAnimator.startPendingIntentWithAnimation(animationController,
!wasOccluded && mStatusBar.areLaunchAnimationsEnabled(), animate, intent.getCreatorPackage(), (adapter) -> {
intent.getCreatorPackage(), (adapter) -> {
long eventTime = row.getAndResetLastActionUpTime(); long eventTime = row.getAndResetLastActionUpTime();
Bundle options = eventTime > 0 Bundle options = eventTime > 0
? getActivityOptions( ? getActivityOptions(
@@ -460,6 +458,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
@Override @Override
public void startNotificationGutsIntent(final Intent intent, final int appUid, public void startNotificationGutsIntent(final Intent intent, final int appUid,
ExpandableNotificationRow row) { ExpandableNotificationRow row) {
boolean animate = mStatusBar.shouldAnimateLaunch(true /* isActivityIntent */);
mActivityStarter.dismissKeyguardThenExecute(() -> { mActivityStarter.dismissKeyguardThenExecute(() -> {
AsyncTask.execute(() -> { AsyncTask.execute(() -> {
ActivityLaunchAnimator.Controller animationController = ActivityLaunchAnimator.Controller animationController =
@@ -468,8 +467,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mStatusBar, true /* isActivityIntent */); mStatusBar, true /* isActivityIntent */);
mActivityLaunchAnimator.startIntentWithAnimation( mActivityLaunchAnimator.startIntentWithAnimation(
animationController, mStatusBar.areLaunchAnimationsEnabled(), animationController, animate, intent.getPackage(),
intent.getPackage(),
(adapter) -> TaskStackBuilder.create(mContext) (adapter) -> TaskStackBuilder.create(mContext)
.addNextIntentWithParentStack(intent) .addNextIntentWithParentStack(intent)
.startActivities(getActivityOptions( .startActivities(getActivityOptions(
@@ -483,6 +481,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
@Override @Override
public void startHistoryIntent(View view, boolean showHistory) { public void startHistoryIntent(View view, boolean showHistory) {
boolean animate = mStatusBar.shouldAnimateLaunch(true /* isActivityIntent */);
mActivityStarter.dismissKeyguardThenExecute(() -> { mActivityStarter.dismissKeyguardThenExecute(() -> {
AsyncTask.execute(() -> { AsyncTask.execute(() -> {
Intent intent = showHistory ? new Intent( Intent intent = showHistory ? new Intent(
@@ -499,8 +498,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
ActivityLaunchAnimator.Controller.fromView(view), mStatusBar, ActivityLaunchAnimator.Controller.fromView(view), mStatusBar,
true /* isActivityIntent */); true /* isActivityIntent */);
mActivityLaunchAnimator.startIntentWithAnimation(animationController, mActivityLaunchAnimator.startIntentWithAnimation(animationController, animate,
mStatusBar.areLaunchAnimationsEnabled(), intent.getPackage(), intent.getPackage(),
(adapter) -> tsb.startActivities( (adapter) -> tsb.startActivities(
getActivityOptions(mStatusBar.getDisplayId(), adapter), getActivityOptions(mStatusBar.getDisplayId(), adapter),
UserHandle.CURRENT)); UserHandle.CURRENT));

View File

@@ -8,6 +8,7 @@ import android.os.Looper
import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper import android.testing.TestableLooper.RunWithLooper
import android.view.IRemoteAnimationFinishedCallback import android.view.IRemoteAnimationFinishedCallback
import android.view.IRemoteAnimationRunner
import android.view.RemoteAnimationAdapter import android.view.RemoteAnimationAdapter
import android.view.RemoteAnimationTarget import android.view.RemoteAnimationTarget
import android.view.SurfaceControl import android.view.SurfaceControl
@@ -15,6 +16,7 @@ import android.view.ViewGroup
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
import junit.framework.Assert.assertFalse import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertNotNull import junit.framework.Assert.assertNotNull
import junit.framework.Assert.assertNull import junit.framework.Assert.assertNull
@@ -27,6 +29,7 @@ import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.never import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import org.mockito.Spy import org.mockito.Spy
import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoJUnit
@@ -36,14 +39,16 @@ import kotlin.concurrent.thread
@RunWith(AndroidTestingRunner::class) @RunWith(AndroidTestingRunner::class)
@RunWithLooper @RunWithLooper
class ActivityLaunchAnimatorTest : SysuiTestCase() { class ActivityLaunchAnimatorTest : SysuiTestCase() {
private val activityLaunchAnimator = ActivityLaunchAnimator(mContext)
private val launchContainer = LinearLayout(mContext) private val launchContainer = LinearLayout(mContext)
private val keyguardHandler = TestLaunchAnimatorKeyguardHandler(isOnKeyguard = false)
@Spy private val controller = TestLaunchAnimatorController(launchContainer) @Spy private val controller = TestLaunchAnimatorController(launchContainer)
@Mock lateinit var iCallback: IRemoteAnimationFinishedCallback @Mock lateinit var iCallback: IRemoteAnimationFinishedCallback
private val activityLaunchAnimator = ActivityLaunchAnimator(keyguardHandler, mContext)
@get:Rule val rule = MockitoJUnit.rule() @get:Rule val rule = MockitoJUnit.rule()
private fun startIntentWithAnimation( private fun startIntentWithAnimation(
animator: ActivityLaunchAnimator = this.activityLaunchAnimator,
controller: ActivityLaunchAnimator.Controller? = this.controller, controller: ActivityLaunchAnimator.Controller? = this.controller,
animate: Boolean = true, animate: Boolean = true,
intentStarter: (RemoteAnimationAdapter?) -> Int intentStarter: (RemoteAnimationAdapter?) -> Int
@@ -51,7 +56,7 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
// We start in a new thread so that we can ensure that the callbacks are called in the main // We start in a new thread so that we can ensure that the callbacks are called in the main
// thread. // thread.
thread { thread {
activityLaunchAnimator.startIntentWithAnimation( animator.startIntentWithAnimation(
controller = controller, controller = controller,
animate = animate, animate = animate,
intentStarter = intentStarter intentStarter = intentStarter
@@ -100,6 +105,27 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
assertFalse(willAnimateCaptor.value) assertFalse(willAnimateCaptor.value)
} }
@Test
fun animatesIfActivityIsAlreadyOpenAndIsOnKeyguard() {
val keyguardHandler = spy(TestLaunchAnimatorKeyguardHandler(isOnKeyguard = true))
val animator = ActivityLaunchAnimator(keyguardHandler, context)
val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
var animationAdapter: RemoteAnimationAdapter? = null
startIntentWithAnimation(animator) { adapter ->
animationAdapter = adapter
ActivityManager.START_DELIVERED_TO_TOP
}
waitForIdleSync()
verify(controller).onIntentStarted(willAnimateCaptor.capture())
verify(keyguardHandler).hideKeyguardWithAnimation(any())
assertTrue(willAnimateCaptor.value)
assertNull(animationAdapter)
}
@Test @Test
fun doesNotAnimateIfAnimateIsFalse() { fun doesNotAnimateIfAnimateIsFalse() {
val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java) val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
@@ -149,6 +175,16 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
} }
} }
private class TestLaunchAnimatorKeyguardHandler(
private val isOnKeyguard: Boolean
) : ActivityLaunchAnimator.KeyguardHandler {
override fun isOnKeyguard(): Boolean = isOnKeyguard
override fun hideKeyguardWithAnimation(runner: IRemoteAnimationRunner) {
// Do nothing.
}
}
/** /**
* A simple implementation of [ActivityLaunchAnimator.Controller] which throws if it is called * A simple implementation of [ActivityLaunchAnimator.Controller] which throws if it is called
* outside of the main thread. * outside of the main thread.