Internal API for cross-task Activity used by assistant.
29091742
A new internal API has been created for use by assistant
to launch an Activity Transition from a non-Activity.
The ActivityOptions are also passed along when using
a spring board Activity so that the shared elements
can be properly synchronized.
This also fixes TransitionManager.endTransition so
that it forces Transitions to end the animations.
Change-Id: Id18d9765bfc0c7b438e17966455aa66d3fa3aeda
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index ac5f3ef..f5ef703 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -4219,6 +4219,7 @@
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
if (mParent == null) {
+ options = transferSpringboardActivityOptions(options);
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
@@ -4267,6 +4268,14 @@
}
}
+ private Bundle transferSpringboardActivityOptions(Bundle options) {
+ if (options == null && (mWindow != null && !mWindow.isActive())) {
+ return mActivityTransitionState.transferEnterActivityOptions();
+ } else {
+ return options;
+ }
+ }
+
/**
* @hide Implement to provide correct calling token.
*/
@@ -4282,6 +4291,7 @@
if (mParent != null) {
throw new RuntimeException("Can't be called from a child");
}
+ options = transferSpringboardActivityOptions(options);
Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode,
options, user);
@@ -4317,6 +4327,7 @@
if (mParent != null) {
throw new RuntimeException("Can't be called from a child");
}
+ options = transferSpringboardActivityOptions(options);
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
@@ -4349,6 +4360,7 @@
if (mParent != null) {
throw new RuntimeException("Can't be called from a child");
}
+ options = transferSpringboardActivityOptions(options);
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivityAsCaller(
this, mMainThread.getApplicationThread(), mToken, this,
@@ -4788,6 +4800,7 @@
*/
public void startActivityFromChild(@NonNull Activity child, @RequiresPermission Intent intent,
int requestCode, @Nullable Bundle options) {
+ options = transferSpringboardActivityOptions(options);
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, child,
@@ -4853,6 +4866,7 @@
if (referrer != null) {
intent.putExtra(Intent.EXTRA_REFERRER, referrer);
}
+ options = transferSpringboardActivityOptions(options);
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, who,
@@ -6650,11 +6664,11 @@
mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
com.android.internal.R.styleable.Window_windowNoDisplay, false);
mFragments.dispatchActivityCreated();
- mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
}
final void performCreate(Bundle icicle) {
restoreHasCurrentPermissionRequest(icicle);
+ mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
onCreate(icicle);
mActivityTransitionState.readState(icicle);
performCreateCommon();
@@ -6662,6 +6676,7 @@
final void performCreate(Bundle icicle, PersistableBundle persistentState) {
restoreHasCurrentPermissionRequest(icicle);
+ mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
onCreate(icicle, persistentState);
mActivityTransitionState.readState(icicle);
performCreateCommon();
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 4c8ddc7..ccc37d72 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -31,10 +31,13 @@
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.transition.Transition;
+import android.transition.TransitionManager;
import android.util.Pair;
import android.util.Slog;
import android.view.AppTransitionAnimationSpec;
import android.view.View;
+import android.view.ViewGroup;
import android.view.Window;
import java.util.ArrayList;
@@ -640,10 +643,71 @@
public static ActivityOptions makeSceneTransitionAnimation(Activity activity,
Pair<View, String>... sharedElements) {
ActivityOptions opts = new ActivityOptions();
- if (!activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) {
- opts.mAnimationType = ANIM_DEFAULT;
+ makeSceneTransitionAnimation(activity, activity.getWindow(), opts,
+ activity.mExitTransitionListener, sharedElements);
+ return opts;
+ }
+
+ /**
+ * Call this immediately prior to startActivity to begin a shared element transition
+ * from a non-Activity. The window must support Window.FEATURE_ACTIVITY_TRANSITIONS.
+ * The exit transition will start immediately and the shared element transition will
+ * start once the launched Activity's shared element is ready.
+ * <p>
+ * When all transitions have completed and the shared element has been transfered,
+ * the window's decor View will have its visibility set to View.GONE.
+ *
+ * @hide
+ */
+ @SafeVarargs
+ public static ActivityOptions startSharedElementAnimation(Window window,
+ Pair<View, String>... sharedElements) {
+ ActivityOptions opts = new ActivityOptions();
+ final View decorView = window.getDecorView();
+ if (decorView == null) {
return opts;
}
+ final ExitTransitionCoordinator exit =
+ makeSceneTransitionAnimation(null, window, opts, null, sharedElements);
+ if (exit != null) {
+ HideWindowListener listener = new HideWindowListener(window, exit);
+ exit.setHideSharedElementsCallback(listener);
+ exit.startExit();
+ }
+ return opts;
+ }
+
+ /**
+ * This method should be called when the {@link #startSharedElementAnimation(Window, Pair[])}
+ * animation must be stopped and the Views reset. This can happen if there was an error
+ * from startActivity or a springboard activity and the animation should stop and reset.
+ *
+ * @hide
+ */
+ public static void stopSharedElementAnimation(Window window) {
+ final View decorView = window.getDecorView();
+ if (decorView == null) {
+ return;
+ }
+ final ExitTransitionCoordinator exit = (ExitTransitionCoordinator)
+ decorView.getTag(com.android.internal.R.id.cross_task_transition);
+ if (exit != null) {
+ exit.cancelPendingTransitions();
+ decorView.setTagInternal(com.android.internal.R.id.cross_task_transition, null);
+ TransitionManager.endTransitions((ViewGroup) decorView);
+ exit.resetViews();
+ exit.clearState();
+ decorView.setVisibility(View.VISIBLE);
+ }
+ }
+
+ static ExitTransitionCoordinator makeSceneTransitionAnimation(Activity activity, Window window,
+ ActivityOptions opts, SharedElementCallback callback,
+ Pair<View, String>[] sharedElements) {
+ if (!window.hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) {
+ opts.mAnimationType = ANIM_DEFAULT;
+ return null;
+ }
opts.mAnimationType = ANIM_SCENE_TRANSITION;
ArrayList<String> names = new ArrayList<String>();
@@ -665,18 +729,22 @@
}
}
- ExitTransitionCoordinator exit = new ExitTransitionCoordinator(activity, names, names,
- views, false);
+ ExitTransitionCoordinator exit = new ExitTransitionCoordinator(activity, window,
+ callback, names, names, views, false);
opts.mTransitionReceiver = exit;
opts.mSharedElementNames = names;
- opts.mIsReturning = false;
- opts.mExitCoordinatorIndex =
- activity.mActivityTransitionState.addExitTransitionCoordinator(exit);
- return opts;
+ opts.mIsReturning = (activity == null);
+ if (activity == null) {
+ opts.mExitCoordinatorIndex = -1;
+ } else {
+ opts.mExitCoordinatorIndex =
+ activity.mActivityTransitionState.addExitTransitionCoordinator(exit);
+ }
+ return exit;
}
/** @hide */
- public static ActivityOptions makeSceneTransitionAnimation(Activity activity,
+ static ActivityOptions makeSceneTransitionAnimation(Activity activity,
ExitTransitionCoordinator exitCoordinator, ArrayList<String> sharedElementNames,
int resultCode, Intent resultData) {
ActivityOptions opts = new ActivityOptions();
@@ -900,6 +968,16 @@
return mIsReturning;
}
+ /**
+ * Returns whether or not the ActivityOptions was created with
+ * {@link #startSharedElementAnimation(Window, Pair[])}.
+ *
+ * @hide
+ */
+ boolean isCrossTask() {
+ return mExitCoordinatorIndex < 0;
+ }
+
/** @hide */
public ArrayList<String> getSharedElementNames() {
return mSharedElementNames;
@@ -1191,4 +1269,65 @@
+ ", mAnimationType=" + mAnimationType + ", mStartX=" + mStartX + ", mStartY="
+ mStartY + ", mWidth=" + mWidth + ", mHeight=" + mHeight;
}
+
+ private static class HideWindowListener extends Transition.TransitionListenerAdapter
+ implements ExitTransitionCoordinator.HideSharedElementsCallback {
+ private final Window mWindow;
+ private final ExitTransitionCoordinator mExit;
+ private final boolean mWaitingForTransition;
+ private boolean mTransitionEnded;
+ private boolean mSharedElementHidden;
+ private ArrayList<View> mSharedElements;
+
+ public HideWindowListener(Window window, ExitTransitionCoordinator exit) {
+ mWindow = window;
+ mExit = exit;
+ mSharedElements = new ArrayList<>(exit.mSharedElements);
+ Transition transition = mWindow.getExitTransition();
+ if (transition != null) {
+ transition.addListener(this);
+ mWaitingForTransition = true;
+ } else {
+ mWaitingForTransition = false;
+ }
+ View decorView = mWindow.getDecorView();
+ if (decorView != null) {
+ if (decorView.getTag(com.android.internal.R.id.cross_task_transition) != null) {
+ throw new IllegalStateException(
+ "Cannot start a transition while one is running");
+ }
+ decorView.setTagInternal(com.android.internal.R.id.cross_task_transition, exit);
+ }
+ }
+
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ mTransitionEnded = true;
+ hideWhenDone();
+ transition.removeListener(this);
+ }
+
+ @Override
+ public void hideSharedElements() {
+ mSharedElementHidden = true;
+ hideWhenDone();
+ }
+
+ private void hideWhenDone() {
+ if (mSharedElementHidden && (!mWaitingForTransition || mTransitionEnded)) {
+ mExit.resetViews();
+ int numSharedElements = mSharedElements.size();
+ for (int i = 0; i < numSharedElements; i++) {
+ View view = mSharedElements.get(i);
+ view.requestLayout();
+ }
+ View decorView = mWindow.getDecorView();
+ if (decorView != null) {
+ decorView.setTagInternal(
+ com.android.internal.R.id.cross_task_transition, null);
+ decorView.setVisibility(View.GONE);
+ }
+ }
+ }
+ }
}
diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java
index 02eb4d3..88526fb 100644
--- a/core/java/android/app/ActivityTransitionState.java
+++ b/core/java/android/app/ActivityTransitionState.java
@@ -105,6 +105,12 @@
private boolean mIsEnterTriggered;
+ /**
+ * The ActivityOptions Bundle. This is used to transfer ActivityOptions through a
+ * springboard Activity.
+ */
+ private Bundle mEnterBundle;
+
public ActivityTransitionState() {
}
@@ -150,6 +156,10 @@
}
public void setEnterActivityOptions(Activity activity, ActivityOptions options) {
+ if (options != null && mEnterBundle == null &&
+ options.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
+ mEnterBundle = options.toBundle();
+ }
final Window window = activity.getWindow();
if (window == null) {
return;
@@ -185,7 +195,12 @@
activity.getWindow().getDecorView().setVisibility(View.VISIBLE);
}
mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
- resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning());
+ resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning(),
+ mEnterActivityOptions.isCrossTask());
+ if (mEnterActivityOptions.isCrossTask()) {
+ mExitingFrom = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
+ mExitingTo = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
+ }
if (!mIsEnterPostponed) {
startEnter();
@@ -224,6 +239,13 @@
mEnterActivityOptions = null;
}
+ Bundle transferEnterActivityOptions() {
+ mEnterActivityOptions = null;
+ Bundle options = mEnterBundle;
+ mEnterBundle = null;
+ return options;
+ }
+
public void onStop() {
restoreExitedViews();
if (mEnterTransitionCoordinator != null) {
@@ -275,7 +297,8 @@
}
private void restoreReenteringViews() {
- if (mEnterTransitionCoordinator != null && mEnterTransitionCoordinator.isReturning()) {
+ if (mEnterTransitionCoordinator != null && mEnterTransitionCoordinator.isReturning() &&
+ !mEnterTransitionCoordinator.isCrossTask()) {
mEnterTransitionCoordinator.forceViewsToAppear();
mExitingFrom = null;
mExitingTo = null;
@@ -302,8 +325,9 @@
}
}
- mReturnExitCoordinator =
- new ExitTransitionCoordinator(activity, mEnteringNames, null, null, true);
+ mReturnExitCoordinator = new ExitTransitionCoordinator(activity,
+ activity.getWindow(), activity.mEnterTransitionListener, mEnteringNames,
+ null, null, true);
if (enterViewsTransition != null && decor != null) {
enterViewsTransition.resume(decor);
}
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index 8bf1e9a..5d12b0d 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -59,12 +59,14 @@
private boolean mIsViewsTransitionStarted;
private Transition mEnterViewsTransition;
private OnPreDrawListener mViewsReadyListener;
+ private final boolean mIsCrossTask;
public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver,
- ArrayList<String> sharedElementNames, boolean isReturning) {
+ ArrayList<String> sharedElementNames, boolean isReturning, boolean isCrossTask) {
super(activity.getWindow(), sharedElementNames,
- getListener(activity, isReturning), isReturning);
+ getListener(activity, isReturning && !isCrossTask), isReturning);
mActivity = activity;
+ mIsCrossTask = isCrossTask;
setResultReceiver(resultReceiver);
prepareEnter();
Bundle resultReceiverBundle = new Bundle();
@@ -85,6 +87,10 @@
}
}
+ boolean isCrossTask() {
+ return mIsCrossTask;
+ }
+
public void viewInstancesReady(ArrayList<String> accepted, ArrayList<String> localNames,
ArrayList<View> localViews) {
boolean remap = false;
@@ -325,7 +331,9 @@
if (mActivity == null || decorView == null) {
return;
}
- mActivity.overridePendingTransition(0, 0);
+ if (!isCrossTask()) {
+ mActivity.overridePendingTransition(0, 0);
+ }
if (!mIsReturning) {
mWasOpaque = mActivity.convertToTranslucent(null, null);
Drawable background = decorView.getBackground();
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index 0404288..160c285 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -35,6 +35,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
+import android.view.Window;
import java.util.ArrayList;
@@ -59,18 +60,20 @@
private Bundle mExitSharedElementBundle;
private boolean mIsExitStarted;
private boolean mSharedElementsHidden;
+ private HideSharedElementsCallback mHideSharedElementsCallback;
- public ExitTransitionCoordinator(Activity activity, ArrayList<String> names,
+ public ExitTransitionCoordinator(Activity activity, Window window,
+ SharedElementCallback listener, ArrayList<String> names,
ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning) {
- super(activity.getWindow(), names, getListener(activity, isReturning), isReturning);
+ super(window, names, listener, isReturning);
viewsReady(mapSharedElements(accepted, mapped));
stripOffscreenViews();
mIsBackgroundReady = !isReturning;
mActivity = activity;
}
- private static SharedElementCallback getListener(Activity activity, boolean isReturning) {
- return isReturning ? activity.mEnterTransitionListener : activity.mExitTransitionListener;
+ void setHideSharedElementsCallback(HideSharedElementsCallback callback) {
+ mHideSharedElementsCallback = callback;
}
@Override
@@ -188,6 +191,9 @@
private void hideSharedElements() {
moveSharedElementsFromOverlay();
+ if (mHideSharedElementsCallback != null) {
+ mHideSharedElementsCallback.hideSharedElements();
+ }
if (!mIsHidden) {
hideViews(mSharedElements);
}
@@ -207,7 +213,11 @@
startTransition(new Runnable() {
@Override
public void run() {
- beginTransitions();
+ if (mActivity != null) {
+ beginTransitions();
+ } else {
+ startExitTransition();
+ }
}
});
}
@@ -508,4 +518,8 @@
return getWindow().getSharedElementExitTransition();
}
}
+
+ interface HideSharedElementsCallback {
+ void hideSharedElements();
+ }
}
diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java
index 316c7e3..8823605 100644
--- a/core/java/android/transition/Transition.java
+++ b/core/java/android/transition/Transition.java
@@ -1941,6 +1941,26 @@
}
/**
+ * Force the transition to move to its end state, ending all the animators.
+ *
+ * @hide
+ */
+ void forceToEnd(ViewGroup sceneRoot) {
+ ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
+ int numOldAnims = runningAnimators.size();
+ if (sceneRoot != null) {
+ WindowId windowId = sceneRoot.getWindowId();
+ for (int i = numOldAnims - 1; i >= 0; i--) {
+ AnimationInfo info = runningAnimators.valueAt(i);
+ if (info.view != null && windowId != null && windowId.equals(info.windowId)) {
+ Animator anim = runningAnimators.keyAt(i);
+ anim.end();
+ }
+ }
+ }
+ }
+
+ /**
* This method cancels a transition that is currently running.
*
* @hide
diff --git a/core/java/android/transition/TransitionManager.java b/core/java/android/transition/TransitionManager.java
index 71c8099..f2c871e3 100644
--- a/core/java/android/transition/TransitionManager.java
+++ b/core/java/android/transition/TransitionManager.java
@@ -440,7 +440,7 @@
ArrayList<Transition> copy = new ArrayList(runningTransitions);
for (int i = copy.size() - 1; i >= 0; i--) {
final Transition transition = copy.get(i);
- transition.end();
+ transition.forceToEnd(sceneRoot);
}
}
diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java
index 583dc0f..a41fe64 100644
--- a/core/java/android/transition/TransitionSet.java
+++ b/core/java/android/transition/TransitionSet.java
@@ -16,8 +16,6 @@
package android.transition;
-import com.android.internal.R;
-
import android.animation.TimeInterpolator;
import android.content.Context;
import android.content.res.TypedArray;
@@ -26,6 +24,8 @@
import android.view.View;
import android.view.ViewGroup;
+import com.android.internal.R;
+
import java.util.ArrayList;
/**
@@ -498,6 +498,16 @@
}
}
+ /** @hide */
+ @Override
+ void forceToEnd(ViewGroup sceneRoot) {
+ super.forceToEnd(sceneRoot);
+ int numTransitions = mTransitions.size();
+ for (int i = 0; i < numTransitions; ++i) {
+ mTransitions.get(i).forceToEnd(sceneRoot);
+ }
+ }
+
@Override
TransitionSet setSceneRoot(ViewGroup sceneRoot) {
super.setSceneRoot(sceneRoot);
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index 7f8acd3..5c165e6 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -122,9 +122,11 @@
<!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_SET_PROGRESS}. -->
<item type="id" name="accessibilityActionSetProgress" />
-
+
<!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_CONTEXT_CLICK}. -->
<item type="id" name="accessibilityActionContextClick" />
<item type="id" name="remote_input_tag" />
+
+ <item type="id" name="cross_task_transition" />
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 3b51cc2..55702cc 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2609,4 +2609,7 @@
<java-symbol type="array" name="config_defaultPinnerServiceFiles" />
<java-symbol type="string" name="suspended_widget_accessibility" />
+
+ <!-- Used internally for assistant to launch activity transitions -->
+ <java-symbol type="id" name="cross_task_transition" />
</resources>