summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jordan Demeulenaere <jdemeulenaere@google.com> 2021-05-31 08:54:58 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2021-05-31 08:54:58 +0000
commit2c52d52f6d5dd5715cdf012ad0e4129944fc4ba6 (patch)
tree55b190d81d3021327accd52004d35db779adc02d
parentad1cfd0db8be9d88343260787a37fca6064f9106 (diff)
parentf90ca12d50b3b4aed3ffc32af68e1e1bdb241043 (diff)
Merge "Animate app launches from lockscreen" into sc-dev
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt88
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java81
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt40
9 files changed, 251 insertions, 76 deletions
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 2b8773783048..4f179c46a3d9 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -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
* nicely into the starting window.
*/
-class ActivityLaunchAnimator(context: Context) {
+class ActivityLaunchAnimator(
+ private val keyguardHandler: KeyguardHandler,
+ context: Context
+) {
private val TAG = this::class.java.simpleName
companion object {
@@ -104,15 +107,22 @@ class ActivityLaunchAnimator(context: Context) {
Log.d(TAG, "Starting intent with a launch animation")
val runner = Runner(controller)
- val animationAdapter = RemoteAnimationAdapter(
- runner,
- ANIMATION_DURATION,
- ANIMATION_DURATION - 150 /* statusBarTransitionDelay */
- )
+ val isOnKeyguard = keyguardHandler.isOnKeyguard()
+
+ // Pass the RemoteAnimationAdapter to the intent starter only if we are not on the keyguard.
+ 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
// activity launches.
- if (packageName != null) {
+ if (packageName != null && animationAdapter != null) {
try {
ActivityTaskManager.getService().registerRemoteAnimationForNextActivityStart(
packageName, animationAdapter)
@@ -122,20 +132,30 @@ class ActivityLaunchAnimator(context: Context) {
}
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)
// If we expect an animation, post a timeout to cancel it in case the remote animation is
// never started.
if (willAnimate) {
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()) {
this.launchContainer.context.mainExecutor.execute {
this.onIntentStarted(willAnimate)
@@ -179,6 +199,14 @@ class ActivityLaunchAnimator(context: Context) {
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.
*
@@ -337,17 +365,17 @@ class ActivityLaunchAnimator(context: Context) {
override fun onAnimationStart(
@WindowManager.TransitionOldType transit: Int,
- remoteAnimationTargets: Array<out RemoteAnimationTarget>,
- remoteAnimationWallpaperTargets: Array<out RemoteAnimationTarget>,
- remoteAnimationNonAppTargets: Array<out RemoteAnimationTarget>,
- iRemoteAnimationFinishedCallback: IRemoteAnimationFinishedCallback
+ apps: Array<out RemoteAnimationTarget>?,
+ wallpapers: Array<out RemoteAnimationTarget>?,
+ nonApps: Array<out RemoteAnimationTarget>?,
+ iCallback: IRemoteAnimationFinishedCallback?
) {
removeTimeout()
// The animation was started too late and we already notified the controller that it
// timed out.
if (timedOut) {
- invokeCallback(iRemoteAnimationFinishedCallback)
+ iCallback?.invoke()
return
}
@@ -358,30 +386,29 @@ class ActivityLaunchAnimator(context: Context) {
}
context.mainExecutor.execute {
- startAnimation(remoteAnimationTargets, remoteAnimationNonAppTargets,
- iRemoteAnimationFinishedCallback)
+ startAnimation(apps, nonApps, iCallback)
}
}
private fun startAnimation(
- remoteAnimationTargets: Array<out RemoteAnimationTarget>,
- remoteAnimationNonAppTargets: Array<out RemoteAnimationTarget>,
- iCallback: IRemoteAnimationFinishedCallback
+ apps: Array<out RemoteAnimationTarget>?,
+ nonApps: Array<out RemoteAnimationTarget>?,
+ iCallback: IRemoteAnimationFinishedCallback?
) {
Log.d(TAG, "Remote animation started")
- val window = remoteAnimationTargets.firstOrNull {
+ val window = apps?.firstOrNull {
it.mode == RemoteAnimationTarget.MODE_OPENING
}
if (window == null) {
Log.d(TAG, "Aborting the animation as no window is opening")
removeTimeout()
- invokeCallback(iCallback)
+ iCallback?.invoke()
controller.onLaunchAnimationCancelled()
return
}
- val navigationBar = remoteAnimationNonAppTargets.firstOrNull {
+ val navigationBar = nonApps?.firstOrNull {
it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
}
@@ -439,7 +466,7 @@ class ActivityLaunchAnimator(context: Context) {
override fun onAnimationEnd(animation: Animator?) {
Log.d(TAG, "Animation ended")
- invokeCallback(iCallback)
+ iCallback?.invoke()
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
- // keep the original radius.
- val cornerRadius = minOf(state.topCornerRadius, state.bottomCornerRadius) / scale
+ // keep the original radius. We use the max of (topCornerRadius, bottomCornerRadius) to
+ // 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)
.withAlpha(1f)
.withMatrix(matrix)
@@ -585,9 +615,9 @@ class ActivityLaunchAnimator(context: Context) {
}
}
- private fun invokeCallback(iCallback: IRemoteAnimationFinishedCallback) {
+ private fun IRemoteAnimationFinishedCallback.invoke() {
try {
- iCallback.onAnimationFinished()
+ onAnimationFinished()
} catch (e: RemoteException) {
e.printStackTrace()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 666afed41c35..9b372b8bdec6 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
*/
- static boolean sEnableRemoteKeyguardAnimation =
+ public static boolean sEnableRemoteKeyguardAnimation =
SystemProperties.getBoolean(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, false);
private final KeyguardViewMediator mKeyguardViewMediator;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 36a0acc6fb4c..387f24f88b63 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -52,6 +52,7 @@ import android.media.SoundPool;
import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
@@ -71,6 +72,7 @@ import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget;
import android.view.SyncRtSurfaceTransactionApplier;
import android.view.View;
@@ -426,6 +428,11 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
*/
private IRemoteAnimationFinishedCallback mSurfaceBehindRemoteAnimationFinishedCallback;
+ /**
+ * The animation runner to use for the next exit animation.
+ */
+ private IRemoteAnimationRunner mKeyguardExitAnimationRunner;
+
private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
new DeviceConfig.OnPropertiesChangedListener() {
@Override
@@ -1605,6 +1612,16 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
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() {
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
// still completes and makes the screen blank.
if (DEBUG) Log.d(TAG, "Split system user, quit unlocking.");
+ mKeyguardExitAnimationRunner = null;
return;
}
mHiding = true;
@@ -2103,9 +2121,37 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
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,
// it will dismiss the panel in that case.
- if (KeyguardService.sEnableRemoteKeyguardAnimation
+ } else if (KeyguardService.sEnableRemoteKeyguardAnimation
&& !mStatusBarStateController.leaveOpenOnKeyguardHide()
&& apps != null && apps.length > 0) {
mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
@@ -2115,9 +2161,6 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
mKeyguardUnlockAnimationControllerLazy.get().notifyStartKeyguardExitAnimation(
apps[0], startTime, mSurfaceBehindRemoteAnimationRequested);
} else {
- setShowingLocked(false);
- mWakeAndUnlocking = false;
- mDismissCallbackRegistry.notifyDismissSucceeded();
mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
// 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 =
new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
primary.leash)
- .withAlpha(animation.getAnimatedFraction())
- .build();
+ .withAlpha(animation.getAnimatedFraction())
+ .build();
applier.scheduleApply(params);
});
anim.addListener(new AnimatorListenerAdapter() {
@@ -2165,16 +2208,24 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
});
anim.start();
});
- resetKeyguardDonePendingLocked();
- mHideAnimationRun = false;
- adjustStatusBarLocked();
- sendUserPresentBroadcast();
+
+ onKeyguardExitFinished();
}
}
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
* 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.
mKeyguardViewControllerLazy.get().blockPanelExpansionFromCurrentTouch();
final boolean wasShowing = mShowing;
- setShowingLocked(false);
-
- mWakeAndUnlocking = false;
- mDismissCallbackRegistry.notifyDismissSucceeded();
+ onKeyguardExitFinished();
if (mKeyguardStateController.isDismissingFromSwipe() || !wasShowing) {
mKeyguardUnlockAnimationControllerLazy.get().hideKeyguardViewAfterRemoteAnimation();
}
finishSurfaceBehindRemoteAnimation();
-
- resetKeyguardDonePendingLocked();
- mHideAnimationRun = false;
- adjustStatusBarLocked();
- sendUserPresentBroadcast();
mSurfaceBehindRemoteAnimationRequested = false;
mKeyguardUnlockAnimationControllerLazy.get().notifyFinishedKeyguardExitAnimation();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index b237f6f0a46e..3e2bcf9562b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -211,6 +211,12 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
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. */
void setLastActionUpTime(long eventTime) {
mLastActionUpTime = eventTime;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
index 0a63e19acc1a..c4961029dc33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
@@ -118,7 +118,11 @@ public class ActivatableNotificationViewController
if (ev.getAction() == MotionEvent.ACTION_UP) {
// 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;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index ced284e21963..c6753b3b42f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -2291,6 +2291,23 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
@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
public int getIntrinsicHeight() {
if (isUserLocked()) {
return getActualHeight();
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 c27497e43400..2396272e30e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -105,6 +105,7 @@ import android.util.Log;
import android.util.MathUtils;
import android.util.Slog;
import android.view.Display;
+import android.view.IRemoteAnimationRunner;
import android.view.IWindowManager;
import android.view.InsetsState.InternalInsetsType;
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.FragmentHostManager;
import com.android.systemui.keyguard.DismissCallbackRegistry;
+import com.android.systemui.keyguard.KeyguardService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
@@ -267,7 +269,8 @@ public class StatusBar extends SystemUI implements DemoMode,
OnHeadsUpChangedListener, CommandQueue.Callbacks,
ColorExtractor.OnColorsChangedListener, ConfigurationListener,
StatusBarStateController.StateListener,
- LifecycleOwner, BatteryController.BatteryStateChangeCallback {
+ LifecycleOwner, BatteryController.BatteryStateChangeCallback,
+ ActivityLaunchAnimator.KeyguardHandler {
public static final boolean MULTIUSER_DEBUG = false;
protected static final int MSG_HIDE_RECENT_APPS = 1020;
@@ -1413,7 +1416,7 @@ public class StatusBar extends SystemUI implements DemoMode,
private void setUpPresenter() {
// Set up the initial notification state.
- mActivityLaunchAnimator = new ActivityLaunchAnimator(mContext);
+ mActivityLaunchAnimator = new ActivityLaunchAnimator(this, mContext);
mNotificationAnimationProvider = new NotificationLaunchAnimatorControllerProvider(
mNotificationShadeWindowViewController,
mStackScrollerController.getNotificationListContainer(),
@@ -2072,10 +2075,45 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- /** Whether we should animate an activity launch. */
- public boolean areLaunchAnimationsEnabled() {
- // TODO(b/184121838): Support lock screen launch animations.
- return mState == StatusBarState.SHADE && !isOccluded();
+ /**
+ * Whether we should animate an activity launch.
+ *
+ * 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() {
@@ -2796,8 +2834,9 @@ public class StatusBar extends SystemUI implements DemoMode,
final boolean afterKeyguardGone = mActivityIntentHelper.wouldLaunchResolverActivity(
intent, mLockscreenUserManager.getCurrentUserId());
- ActivityLaunchAnimator.Controller animController = wrapAnimationController(
- animationController, dismissShade);
+ ActivityLaunchAnimator.Controller animController =
+ shouldAnimateLaunch(true /* isActivityIntent */) ? wrapAnimationController(
+ animationController, dismissShade) : null;
// If we animate, we will dismiss the shade only once the animation is done. This is taken
// care of by the StatusBarLaunchAnimationController.
@@ -2811,7 +2850,7 @@ public class StatusBar extends SystemUI implements DemoMode,
int[] result = new int[]{ActivityManager.START_CANCELED};
mActivityLaunchAnimator.startIntentWithAnimation(animController,
- areLaunchAnimationsEnabled(), intent.getPackage(), (adapter) -> {
+ true /* animate */, intent.getPackage(), (adapter) -> {
ActivityOptions options = new ActivityOptions(
getActivityOptions(mDisplayId, adapter));
options.setDisallowEnterPictureInPictureWhileLaunching(
@@ -4606,6 +4645,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mLockscreenUserManager.getCurrentUserId());
boolean collapse = animationController == null;
+ boolean animate = shouldAnimateLaunch(intent.isActivity());
executeActionDismissingKeyguard(() -> {
try {
// 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;
mActivityLaunchAnimator.startPendingIntentWithAnimation(
- controller, areLaunchAnimationsEnabled(), intent.getCreatorPackage(),
+ controller, animate, intent.getCreatorPackage(),
(animationAdapter) -> intent.sendAndReturnResult(null, 0, null, null, null,
null, getActivityOptions(mDisplayId, animationAdapter)));
} catch (PendingIntent.CanceledException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 5cd4e13bdcce..f5dd19552b7f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -255,14 +255,14 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
final boolean afterKeyguardGone = isActivityIntent
&& mActivityIntentHelper.wouldLaunchResolverActivity(intent.getIntent(),
mLockscreenUserManager.getCurrentUserId());
- final boolean wasOccluded = mStatusBar.isOccluded();
+ final boolean animate = mStatusBar.shouldAnimateLaunch(isActivityIntent);
boolean showOverLockscreen = mKeyguardStateController.isShowing() && intent != null
&& mActivityIntentHelper.wouldShowOverLockscreen(intent.getIntent(),
mLockscreenUserManager.getCurrentUserId());
ActivityStarter.OnDismissAction postKeyguardAction =
() -> handleNotificationClickAfterKeyguardDismissed(
entry, row, controller, intent,
- isActivityIntent, wasOccluded, showOverLockscreen);
+ isActivityIntent, animate, showOverLockscreen);
if (showOverLockscreen) {
mIsCollapsingToShowActivityOverLockscreen = true;
postKeyguardAction.onDismiss();
@@ -278,7 +278,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
RemoteInputController controller,
PendingIntent intent,
boolean isActivityIntent,
- boolean wasOccluded,
+ boolean animate,
boolean showOverLockscreen) {
mLogger.logHandleClickAfterKeyguardDismissed(entry.getKey());
@@ -293,7 +293,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
final Runnable runnable = () -> handleNotificationClickAfterPanelCollapsed(
entry, row, controller, intent,
- isActivityIntent, wasOccluded);
+ isActivityIntent, animate);
if (showOverLockscreen) {
mShadeController.addPostCollapseAction(runnable);
@@ -314,7 +314,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
RemoteInputController controller,
PendingIntent intent,
boolean isActivityIntent,
- boolean wasOccluded) {
+ boolean animate) {
String notificationKey = entry.getKey();
mLogger.logHandleClickAfterPanelCollapsed(notificationKey);
@@ -360,8 +360,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
removeHUN(row);
expandBubbleStackOnMainThread(entry);
} else {
- startNotificationIntent(
- intent, fillInIntent, entry, row, wasOccluded, isActivityIntent);
+ startNotificationIntent(intent, fillInIntent, entry, row, animate, isActivityIntent);
}
if (isActivityIntent || canBubble) {
mAssistManagerLazy.get().hideAssist();
@@ -426,7 +425,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
Intent fillInIntent,
NotificationEntry entry,
ExpandableNotificationRow row,
- boolean wasOccluded,
+ boolean animate,
boolean isActivityIntent) {
mLogger.logStartNotificationIntent(entry.getKey(), intent);
try {
@@ -436,8 +435,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
isActivityIntent);
mActivityLaunchAnimator.startPendingIntentWithAnimation(animationController,
- !wasOccluded && mStatusBar.areLaunchAnimationsEnabled(),
- intent.getCreatorPackage(), (adapter) -> {
+ animate, intent.getCreatorPackage(), (adapter) -> {
long eventTime = row.getAndResetLastActionUpTime();
Bundle options = eventTime > 0
? getActivityOptions(
@@ -460,6 +458,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
@Override
public void startNotificationGutsIntent(final Intent intent, final int appUid,
ExpandableNotificationRow row) {
+ boolean animate = mStatusBar.shouldAnimateLaunch(true /* isActivityIntent */);
mActivityStarter.dismissKeyguardThenExecute(() -> {
AsyncTask.execute(() -> {
ActivityLaunchAnimator.Controller animationController =
@@ -468,8 +467,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mStatusBar, true /* isActivityIntent */);
mActivityLaunchAnimator.startIntentWithAnimation(
- animationController, mStatusBar.areLaunchAnimationsEnabled(),
- intent.getPackage(),
+ animationController, animate, intent.getPackage(),
(adapter) -> TaskStackBuilder.create(mContext)
.addNextIntentWithParentStack(intent)
.startActivities(getActivityOptions(
@@ -483,6 +481,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
@Override
public void startHistoryIntent(View view, boolean showHistory) {
+ boolean animate = mStatusBar.shouldAnimateLaunch(true /* isActivityIntent */);
mActivityStarter.dismissKeyguardThenExecute(() -> {
AsyncTask.execute(() -> {
Intent intent = showHistory ? new Intent(
@@ -499,8 +498,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
ActivityLaunchAnimator.Controller.fromView(view), mStatusBar,
true /* isActivityIntent */);
- mActivityLaunchAnimator.startIntentWithAnimation(animationController,
- mStatusBar.areLaunchAnimationsEnabled(), intent.getPackage(),
+ mActivityLaunchAnimator.startIntentWithAnimation(animationController, animate,
+ intent.getPackage(),
(adapter) -> tsb.startActivities(
getActivityOptions(mStatusBar.getDisplayId(), adapter),
UserHandle.CURRENT));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
index f2ef5c38e9b9..9a0ed9897e83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
@@ -8,6 +8,7 @@ import android.os.Looper
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.IRemoteAnimationFinishedCallback
+import android.view.IRemoteAnimationRunner
import android.view.RemoteAnimationAdapter
import android.view.RemoteAnimationTarget
import android.view.SurfaceControl
@@ -15,6 +16,7 @@ import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertNotNull
import junit.framework.Assert.assertNull
@@ -27,6 +29,7 @@ import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.Mock
import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.Spy
import org.mockito.junit.MockitoJUnit
@@ -36,14 +39,16 @@ import kotlin.concurrent.thread
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
class ActivityLaunchAnimatorTest : SysuiTestCase() {
- private val activityLaunchAnimator = ActivityLaunchAnimator(mContext)
private val launchContainer = LinearLayout(mContext)
+ private val keyguardHandler = TestLaunchAnimatorKeyguardHandler(isOnKeyguard = false)
@Spy private val controller = TestLaunchAnimatorController(launchContainer)
@Mock lateinit var iCallback: IRemoteAnimationFinishedCallback
+ private val activityLaunchAnimator = ActivityLaunchAnimator(keyguardHandler, mContext)
@get:Rule val rule = MockitoJUnit.rule()
private fun startIntentWithAnimation(
+ animator: ActivityLaunchAnimator = this.activityLaunchAnimator,
controller: ActivityLaunchAnimator.Controller? = this.controller,
animate: Boolean = true,
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
// thread.
thread {
- activityLaunchAnimator.startIntentWithAnimation(
+ animator.startIntentWithAnimation(
controller = controller,
animate = animate,
intentStarter = intentStarter
@@ -101,6 +106,27 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
}
@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
fun doesNotAnimateIfAnimateIsFalse() {
val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
startIntentWithAnimation(animate = false) { ActivityManager.START_SUCCESS }
@@ -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
* outside of the main thread.