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
This commit is contained in:
Issei Suzuki
2021-02-03 21:13:22 +01:00
parent 31b601537b
commit 8bc3db2b30
9 changed files with 91 additions and 6 deletions

View File

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

View File

@@ -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 <var>listener</var> 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 <var>otherOptions</var>. 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;
}

View File

@@ -2538,7 +2538,7 @@
<permission android:name="android.permission.REAL_GET_TASKS"
android:protectionLevel="signature|privileged" />
<!-- Allows an application to start a task from a ActivityManager#RecentTaskInfo.
<!-- @TestApi Allows an application to start a task from a ActivityManager#RecentTaskInfo.
@hide -->
<permission android:name="android.permission.START_TASKS_FROM_RECENTS"
android:protectionLevel="signature|privileged|recents" />

View File

@@ -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() {

View File

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

View File

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

View File

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

View File

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

View File

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