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

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.