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(