diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index a5fcec6229569..890e67d7bc97c 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -4219,6 +4219,7 @@ public class Activity extends ContextThemeWrapper 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,16 @@ public class Activity extends ContextThemeWrapper } } + private Bundle transferSpringboardActivityOptions(Bundle options) { + if (options == null && (mWindow != null && !mWindow.isActive())) { + final ActivityOptions activityOptions = getActivityOptions(); + if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) { + return activityOptions.toBundle(); + } + } + return options; + } + /** * @hide Implement to provide correct calling token. */ @@ -4282,6 +4293,7 @@ public class Activity extends ContextThemeWrapper 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 +4329,7 @@ public class Activity extends ContextThemeWrapper 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 +4362,7 @@ public class Activity extends ContextThemeWrapper 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 +4802,7 @@ public class Activity extends ContextThemeWrapper */ 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 +4868,7 @@ public class Activity extends ContextThemeWrapper if (referrer != null) { intent.putExtra(Intent.EXTRA_REFERRER, referrer); } + options = transferSpringboardActivityOptions(options); Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, who, diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 4c8ddc7eb6b13..ccc37d72e8463 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -31,10 +31,13 @@ import android.os.IRemoteCallback; 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 class ActivityOptions { public static ActivityOptions makeSceneTransitionAnimation(Activity activity, Pair... 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. + *

+ * 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... 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[] sharedElements) { + if (!window.hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) { + opts.mAnimationType = ANIM_DEFAULT; + return null; + } opts.mAnimationType = ANIM_SCENE_TRANSITION; ArrayList names = new ArrayList(); @@ -665,18 +729,22 @@ public class ActivityOptions { } } - 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 sharedElementNames, int resultCode, Intent resultData) { ActivityOptions opts = new ActivityOptions(); @@ -900,6 +968,16 @@ public class ActivityOptions { return mIsReturning; } + /** + * Returns whether or not the ActivityOptions was created with + * {@link #startSharedElementAnimation(Window, Pair[])}. + * + * @hide + */ + boolean isCrossTask() { + return mExitCoordinatorIndex < 0; + } + /** @hide */ public ArrayList getSharedElementNames() { return mSharedElementNames; @@ -1191,4 +1269,65 @@ public class ActivityOptions { + ", 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 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 02eb4d3ded1c1..221923821bbbc 100644 --- a/core/java/android/app/ActivityTransitionState.java +++ b/core/java/android/app/ActivityTransitionState.java @@ -185,7 +185,12 @@ class ActivityTransitionState { 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(); @@ -275,7 +280,8 @@ class ActivityTransitionState { } private void restoreReenteringViews() { - if (mEnterTransitionCoordinator != null && mEnterTransitionCoordinator.isReturning()) { + if (mEnterTransitionCoordinator != null && mEnterTransitionCoordinator.isReturning() && + !mEnterTransitionCoordinator.isCrossTask()) { mEnterTransitionCoordinator.forceViewsToAppear(); mExitingFrom = null; mExitingTo = null; @@ -302,8 +308,9 @@ class ActivityTransitionState { } } - 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 8bf1e9a97e484..5d12b0da6ab81 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -59,12 +59,14 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { private boolean mIsViewsTransitionStarted; private Transition mEnterViewsTransition; private OnPreDrawListener mViewsReadyListener; + private final boolean mIsCrossTask; public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver, - ArrayList sharedElementNames, boolean isReturning) { + ArrayList 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 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { } } + boolean isCrossTask() { + return mIsCrossTask; + } + public void viewInstancesReady(ArrayList accepted, ArrayList localNames, ArrayList localViews) { boolean remap = false; @@ -325,7 +331,9 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { 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 0404288773714..160c28592582a 100644 --- a/core/java/android/app/ExitTransitionCoordinator.java +++ b/core/java/android/app/ExitTransitionCoordinator.java @@ -35,6 +35,7 @@ import android.transition.TransitionManager; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; +import android.view.Window; import java.util.ArrayList; @@ -59,18 +60,20 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { private Bundle mExitSharedElementBundle; private boolean mIsExitStarted; private boolean mSharedElementsHidden; + private HideSharedElementsCallback mHideSharedElementsCallback; - public ExitTransitionCoordinator(Activity activity, ArrayList names, + public ExitTransitionCoordinator(Activity activity, Window window, + SharedElementCallback listener, ArrayList names, ArrayList accepted, ArrayList 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 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { private void hideSharedElements() { moveSharedElementsFromOverlay(); + if (mHideSharedElementsCallback != null) { + mHideSharedElementsCallback.hideSharedElements(); + } if (!mIsHidden) { hideViews(mSharedElements); } @@ -207,7 +213,11 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { startTransition(new Runnable() { @Override public void run() { - beginTransitions(); + if (mActivity != null) { + beginTransitions(); + } else { + startExitTransition(); + } } }); } @@ -508,4 +518,8 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { return getWindow().getSharedElementExitTransition(); } } + + interface HideSharedElementsCallback { + void hideSharedElements(); + } } diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java index 316c7e3f60844..8823605c52b6e 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -1940,6 +1940,26 @@ public abstract class Transition implements Cloneable { } } + /** + * Force the transition to move to its end state, ending all the animators. + * + * @hide + */ + void forceToEnd(ViewGroup sceneRoot) { + ArrayMap 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. * diff --git a/core/java/android/transition/TransitionManager.java b/core/java/android/transition/TransitionManager.java index 71c80991bb197..f2c871e3c718d 100644 --- a/core/java/android/transition/TransitionManager.java +++ b/core/java/android/transition/TransitionManager.java @@ -440,7 +440,7 @@ public class TransitionManager { ArrayList 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 583dc0f1ef2c4..a41fe64d0be10 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.util.AttributeSet; import android.view.View; import android.view.ViewGroup; +import com.android.internal.R; + import java.util.ArrayList; /** @@ -498,6 +498,16 @@ public class TransitionSet extends Transition { } } + /** @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 7f8acd3d61ccd..5c165e6709eed 100644 --- a/core/res/res/values/ids.xml +++ b/core/res/res/values/ids.xml @@ -122,9 +122,11 @@ - + + + diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 8edd9d1bb1d6e..10092945e8f81 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2612,4 +2612,7 @@ + + +