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:
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user