From 8bc3db2b306407898b67f4807915a075db449d50 Mon Sep 17 00:00:00 2001 From: Issei Suzuki Date: Wed, 3 Feb 2021 21:13:22 +0100 Subject: [PATCH] Fix flicker when starting an activity from Recents. In the task switcher, a user can choose a task by tapping or pulling down the task image. In the later case, the task image already occupies the full screen, so the launcher suppresses an app transition animation by calling ActivityOptions#makeCustomAnimation. However, due to task animation lockdown, this API has no effect when the task opens or closes (the API still works as expected when an activity starts within a same task). The new API ActivityOptions#makeCustomTaskAnimation lets us to customize an animation to run when the task opens or closes. Test: atest ActivityTransitionTests Bug: 170251468 Change-Id: I445e64fdbf6be2d4fdf1936844bcd27dbdf8d69a --- core/api/test-current.txt | 2 + core/java/android/app/ActivityOptions.java | 49 +++++++++++++++++++ core/res/AndroidManifest.xml | 2 +- .../shared/system/ActivityOptionsCompat.java | 6 ++- .../server/wm/ActivityClientController.java | 3 +- .../com/android/server/wm/ActivityRecord.java | 6 ++- .../com/android/server/wm/AppTransition.java | 8 ++- .../server/wm/AppTransitionController.java | 7 +++ .../server/wm/SafeActivityOptions.java | 14 ++++++ 9 files changed, 91 insertions(+), 6 deletions(-) diff --git a/core/api/test-current.txt b/core/api/test-current.txt index e0391eee5077d..c2287b460166e 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -27,6 +27,7 @@ package android { field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE"; field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS"; field public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS"; + field public static final String START_TASKS_FROM_RECENTS = "android.permission.START_TASKS_FROM_RECENTS"; field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS"; field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC"; field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS"; @@ -118,6 +119,7 @@ package android.app { public class ActivityOptions { method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener); + method @NonNull @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) public static android.app.ActivityOptions makeCustomTaskAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener); method public static void setExitTransitionTimeout(long); method public void setLaunchActivityType(int); method public void setLaunchTaskId(int); diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 2b5e18d3feece..28da1c3a3eb7e 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -17,6 +17,7 @@ package android.app; import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS; +import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.Display.INVALID_DISPLAY; @@ -310,6 +311,9 @@ public class ActivityOptions { private static final String KEY_REMOTE_TRANSITION = "android:activity.remoteTransition"; + private static final String KEY_OVERRIDE_TASK_TRANSITION = + "android:activity.overrideTaskTransition"; + /** * @see #setLaunchCookie * @hide @@ -393,6 +397,7 @@ public class ActivityOptions { private RemoteAnimationAdapter mRemoteAnimationAdapter; private IBinder mLaunchCookie; private IRemoteTransition mRemoteTransition; + private boolean mOverrideTaskTransition; /** * Create an ActivityOptions specifying a custom animation to run when @@ -475,6 +480,40 @@ public class ActivityOptions { return opts; } + /** + * Create an ActivityOptions specifying a custom animation to run when the activity in the + * different task is displayed. + * + * @param context Who is defining this. This is the application that the + * animation resources will be loaded from. + * @param enterResId A resource ID of the animation resource to use for + * the incoming activity. Use 0 for no animation. + * @param exitResId A resource ID of the animation resource to use for + * the outgoing activity. Use 0 for no animation. + * @param handler If listener is non-null this must be a valid + * Handler on which to dispatch the callback; otherwise it should be null. + * @param startedListener Optional OnAnimationStartedListener to find out when the + * requested animation has started running. If for some reason the animation + * is not executed, the callback will happen immediately. + * @param finishedListener Optional OnAnimationFinishedListener when the animation + * has finished running. + * + * @return Returns a new ActivityOptions object that you can use to + * supply these options as the options Bundle when starting an activity. + * @hide + */ + @RequiresPermission(START_TASKS_FROM_RECENTS) + @TestApi + public static @NonNull ActivityOptions makeCustomTaskAnimation(@NonNull Context context, + int enterResId, int exitResId, @Nullable Handler handler, + @Nullable OnAnimationStartedListener startedListener, + @Nullable OnAnimationFinishedListener finishedListener) { + ActivityOptions opts = makeCustomAnimation(context, enterResId, exitResId, handler, + startedListener, finishedListener); + opts.mOverrideTaskTransition = true; + return opts; + } + /** * Creates an ActivityOptions specifying a custom animation to run in place on an existing * activity. @@ -1107,6 +1146,7 @@ public class ActivityOptions { mLaunchCookie = opts.getBinder(KEY_LAUNCH_COOKIE); mRemoteTransition = IRemoteTransition.Stub.asInterface(opts.getBinder( KEY_REMOTE_TRANSITION)); + mOverrideTaskTransition = opts.getBoolean(KEY_OVERRIDE_TASK_TRANSITION); } /** @@ -1561,6 +1601,12 @@ public class ActivityOptions { return mLaunchCookie; } + + /** @hide */ + public boolean getOverrideTaskTransition() { + return mOverrideTaskTransition; + } + /** * Update the current values in this ActivityOptions from those supplied * in otherOptions. Any values @@ -1789,6 +1835,9 @@ public class ActivityOptions { if (mRemoteTransition != null) { b.putBinder(KEY_REMOTE_TRANSITION, mRemoteTransition.asBinder()); } + if (mOverrideTaskTransition) { + b.putBoolean(KEY_OVERRIDE_TASK_TRANSITION, mOverrideTaskTransition); + } return b; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 989e25d5483e9..ffbd17414d034 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2538,7 +2538,7 @@ - diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java index 3584c82bc57df..e2ca349cc5c8c 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java @@ -72,9 +72,13 @@ public abstract class ActivityOptionsCompat { return ActivityOptions.makeRemoteTransition(remoteTransition.getTransition()); } + /** + * Returns ActivityOptions for overriding task transition animation. + */ public static ActivityOptions makeCustomAnimation(Context context, int enterResId, int exitResId, final Runnable callback, final Handler callbackHandler) { - return ActivityOptions.makeCustomAnimation(context, enterResId, exitResId, callbackHandler, + return ActivityOptions.makeCustomTaskAnimation(context, enterResId, exitResId, + callbackHandler, new ActivityOptions.OnAnimationStartedListener() { @Override public void onAnimationStarted() { diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index 5fe853a38dd7f..a1ff693202d98 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -963,7 +963,8 @@ class ActivityClientController extends IActivityClientController.Stub { final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token); if (r != null && r.isState(Task.ActivityState.RESUMED, Task.ActivityState.PAUSING)) { r.mDisplayContent.mAppTransition.overridePendingAppTransition( - packageName, enterAnim, exitAnim, null, null); + packageName, enterAnim, exitAnim, null, null, + r.mOverrideTaskTransition); } } Binder.restoreCallingIdentity(origId); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 36c503703b9c8..81baaa291fc0b 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -682,6 +682,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean mRequestForceTransition; boolean mEnteringAnimation; + boolean mOverrideTaskTransition; boolean mAppStopped; // A hint to override the window specified rotation animation, or -1 to use the window specified @@ -1627,6 +1628,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (rotationAnimation >= 0) { mRotationAnimationHint = rotationAnimation; } + + mOverrideTaskTransition = options.getOverrideTaskTransition(); } ColorDisplayService.ColorDisplayServiceInternal cds = LocalServices.getService( @@ -3997,7 +4000,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pendingOptions.getCustomEnterResId(), pendingOptions.getCustomExitResId(), pendingOptions.getAnimationStartedListener(), - pendingOptions.getAnimationFinishedListener()); + pendingOptions.getAnimationFinishedListener(), + pendingOptions.getOverrideTaskTransition()); break; case ANIM_CLIP_REVEAL: displayContent.mAppTransition.overridePendingAppTransitionClipReveal( diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 90070c8f50686..eba3f93eac9ae 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -266,6 +266,7 @@ public class AppTransition implements Dump { private final boolean mLowRamRecentsEnabled; private final int mDefaultWindowAnimationStyleResId; + private boolean mOverrideTaskTransition; private RemoteAnimationController mRemoteAnimationController; @@ -971,7 +972,8 @@ public class AppTransition implements Dump { @Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean isVoiceInteraction, boolean freeform, WindowContainer container) { - if (mNextAppTransitionOverrideRequested && container.canCustomizeAppTransition()) { + if (mNextAppTransitionOverrideRequested + && (container.canCustomizeAppTransition() || mOverrideTaskTransition)) { mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM; } @@ -1175,7 +1177,8 @@ public class AppTransition implements Dump { } void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim, - IRemoteCallback startedCallback, IRemoteCallback endedCallback) { + IRemoteCallback startedCallback, IRemoteCallback endedCallback, + boolean overrideTaskTransaction) { if (canOverridePendingAppTransition()) { clear(); mNextAppTransitionOverrideRequested = true; @@ -1185,6 +1188,7 @@ public class AppTransition implements Dump { postAnimationCallback(); mNextAppTransitionCallback = startedCallback; mAnimationFinishedCallback = endedCallback; + mOverrideTaskTransition = overrideTaskTransaction; } } diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 582aeb36b00ba..6e8257bd72447 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -709,6 +709,13 @@ public class AppTransitionController { applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp, voiceInteraction); + for (int i = 0; i < openingApps.size(); ++i) { + openingApps.valueAtUnchecked(i).mOverrideTaskTransition = false; + } + for (int i = 0; i < closingApps.size(); ++i) { + closingApps.valueAtUnchecked(i).mOverrideTaskTransition = false; + } + final AccessibilityController accessibilityController = mDisplayContent.mWmService.mAccessibilityController; if (accessibilityController != null) { diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java index 6df4536847344..65671959e8846 100644 --- a/services/core/java/com/android/server/wm/SafeActivityOptions.java +++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java @@ -253,6 +253,20 @@ public class SafeActivityOptions { throw new SecurityException(msg); } + // Check if the caller is allowed to override any app transition animation. + final boolean overrideTaskTransition = options.getOverrideTaskTransition(); + if (aInfo != null && overrideTaskTransition) { + final int startTasksFromRecentsPerm = ActivityTaskManagerService.checkPermission( + START_TASKS_FROM_RECENTS, callingPid, callingUid); + if (startTasksFromRecentsPerm != PERMISSION_GRANTED) { + final String msg = "Permission Denial: starting " + getIntentString(intent) + + " from " + callerApp + " (pid=" + callingPid + + ", uid=" + callingUid + ") with overrideTaskTransition=true"; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + } + // Check permission for remote animations final RemoteAnimationAdapter adapter = options.getRemoteAnimationAdapter(); if (adapter != null && supervisor.mService.checkPermission(