Animate app launches from lockscreen

This CL animates the app launches (from notification, media, etc) on the
lockscreen when the lockscreen can be dismissed (i.e. when there is no
security or when SmartLock/FaceAuth is enabled).

Note that in order to try this CL, you need to enable remote keyguard
animations:

$ adb shell setprop persist.wm.enable_remote_keyguard_animation 1
$ adb reboot

See b/184726377#comment20 for before/after videos.

Bug: b/184726377
Test: Click notification/media/QS from lock screen.
Change-Id: I27e616c1549676f6d1aca53b71efc18db8eec77a
This commit is contained in:
Jordan Demeulenaere
2021-05-05 11:14:32 +02:00
parent 56fb52b010
commit f90ca12d50
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
* 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()
}

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -2290,6 +2290,23 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
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
public int getIntrinsicHeight() {
if (isUserLocked()) {

View File

@@ -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;
@@ -163,6 +164,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;
@@ -266,7 +268,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;
@@ -1405,7 +1408,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(),
@@ -2062,10 +2065,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() {
@@ -2786,8 +2824,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.
@@ -2801,7 +2840,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(
@@ -4556,6 +4595,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
@@ -4565,7 +4605,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) {

View File

@@ -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));

View File

@@ -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
@@ -100,6 +105,27 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
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
fun doesNotAnimateIfAnimateIsFalse() {
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
* outside of the main thread.