diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 1c2f682c94924..5ee5597e19842 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -2590,7 +2590,7 @@ public class Activity extends ContextThemeWrapper protected void onStop() { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this); if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false); - mActivityTransitionState.onStop(); + mActivityTransitionState.onStop(this); dispatchActivityStopped(); mTranslucentCallback = null; mCalled = true; diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index ef146b118c338..0b0781e917e3b 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -27,6 +27,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.TestApi; +import android.app.ExitTransitionCoordinator.ActivityExitTransitionCallbacks; +import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; @@ -44,8 +46,6 @@ import android.os.Parcelable; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.UserHandle; -import android.transition.Transition; -import android.transition.TransitionListenerAdapter; import android.transition.TransitionManager; import android.util.Pair; import android.util.Slog; @@ -806,8 +806,11 @@ public class ActivityOptions { public static ActivityOptions makeSceneTransitionAnimation(Activity activity, Pair... sharedElements) { ActivityOptions opts = new ActivityOptions(); - makeSceneTransitionAnimation(activity, activity.getWindow(), opts, - activity.mExitTransitionListener, sharedElements); + ExitTransitionCoordinator exit = makeSceneTransitionAnimation( + new ActivityExitTransitionCallbacks(activity), activity.mExitTransitionListener, + activity.getWindow(), opts, sharedElements); + opts.mExitCoordinatorIndex = + activity.mActivityTransitionState.addExitTransitionCoordinator(exit); return opts; } @@ -823,25 +826,19 @@ public class ActivityOptions { * @hide */ @SafeVarargs - public static ActivityOptions startSharedElementAnimation(Window window, + public static Pair startSharedElementAnimation( + Window window, ExitTransitionCallbacks exitCallbacks, SharedElementCallback callback, 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; + ExitTransitionCoordinator exit = makeSceneTransitionAnimation( + exitCallbacks, callback, window, opts, sharedElements); + opts.mExitCoordinatorIndex = -1; + return Pair.create(opts, exit); } /** - * This method should be called when the {@link #startSharedElementAnimation(Window, Pair[])} + * This method should be called when the + * {@link #startSharedElementAnimation(Window, ExitTransitionCallbacks, 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. * @@ -864,9 +861,9 @@ public class ActivityOptions { } } - static ExitTransitionCoordinator makeSceneTransitionAnimation(Activity activity, Window window, - ActivityOptions opts, SharedElementCallback callback, - Pair[] sharedElements) { + static ExitTransitionCoordinator makeSceneTransitionAnimation( + ExitTransitionCallbacks exitCallbacks, SharedElementCallback callback, Window window, + ActivityOptions opts, Pair[] sharedElements) { if (!window.hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) { opts.mAnimationType = ANIM_DEFAULT; return null; @@ -892,17 +889,11 @@ public class ActivityOptions { } } - ExitTransitionCoordinator exit = new ExitTransitionCoordinator(activity, window, + ExitTransitionCoordinator exit = new ExitTransitionCoordinator(exitCallbacks, window, callback, names, names, views, false); opts.mTransitionReceiver = exit; opts.mSharedElementNames = names; - opts.mIsReturning = (activity == null); - if (activity == null) { - opts.mExitCoordinatorIndex = -1; - } else { - opts.mExitCoordinatorIndex = - activity.mActivityTransitionState.addExitTransitionCoordinator(exit); - } + opts.mIsReturning = false; return exit; } @@ -928,8 +919,12 @@ public class ActivityOptions { opts.mIsReturning = true; opts.mResultCode = resultCode; opts.mResultData = resultData; - opts.mExitCoordinatorIndex = - activity.mActivityTransitionState.addExitTransitionCoordinator(exitCoordinator); + if (activity == null) { + opts.mExitCoordinatorIndex = -1; + } else { + opts.mExitCoordinatorIndex = + activity.mActivityTransitionState.addExitTransitionCoordinator(exitCoordinator); + } return opts; } @@ -1868,67 +1863,6 @@ public class ActivityOptions { + mStartY + ", mWidth=" + mWidth + ", mHeight=" + mHeight; } - private static class HideWindowListener extends 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); - } - } - } - } - /** * The information about the source of activity launch. E.g. describe an activity is launched * from launcher by receiving a motion event with a timestamp. diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java index 5c4125e01dfd7..62619509184a8 100644 --- a/core/java/android/app/ActivityTransitionState.java +++ b/core/java/android/app/ActivityTransitionState.java @@ -247,14 +247,14 @@ class ActivityTransitionState { mEnterActivityOptions = null; } - public void onStop() { + public void onStop(Activity activity) { restoreExitedViews(); if (mEnterTransitionCoordinator != null) { mEnterTransitionCoordinator.stop(); mEnterTransitionCoordinator = null; } if (mReturnExitCoordinator != null) { - mReturnExitCoordinator.stop(); + mReturnExitCoordinator.stop(activity); mReturnExitCoordinator = null; } } @@ -331,7 +331,8 @@ class ActivityTransitionState { } } - mReturnExitCoordinator = new ExitTransitionCoordinator(activity, + mReturnExitCoordinator = new ExitTransitionCoordinator( + new ExitTransitionCoordinator.ActivityExitTransitionCallbacks(activity), activity.getWindow(), activity.mEnterTransitionListener, pendingExitNames, null, null, true); if (enterViewsTransition != null && decor != null) { @@ -341,12 +342,11 @@ class ActivityTransitionState { final ViewGroup finalDecor = decor; OneShotPreDrawListener.add(decor, () -> { if (mReturnExitCoordinator != null) { - mReturnExitCoordinator.startExit(activity.mResultCode, - activity.mResultData); + mReturnExitCoordinator.startExit(activity); } }); } else { - mReturnExitCoordinator.startExit(activity.mResultCode, activity.mResultData); + mReturnExitCoordinator.startExit(activity); } } return true; diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java index 68824cd26eaaf..9fdff5979cd06 100644 --- a/core/java/android/app/ExitTransitionCoordinator.java +++ b/core/java/android/app/ExitTransitionCoordinator.java @@ -18,6 +18,7 @@ package android.app; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; +import android.annotation.NonNull; import android.app.SharedElementCallback.OnSharedElementsReadyListener; import android.content.Intent; import android.graphics.Color; @@ -45,15 +46,17 @@ import java.util.ArrayList; * This ActivityTransitionCoordinator is created in ActivityOptions#makeSceneTransitionAnimation * to govern the exit of the Scene and the shared elements when calling an Activity as well as * the reentry of the Scene when coming back from the called Activity. + * + * @hide */ -class ExitTransitionCoordinator extends ActivityTransitionCoordinator { +public class ExitTransitionCoordinator extends ActivityTransitionCoordinator { private static final String TAG = "ExitTransitionCoordinator"; static long sMaxWaitMillis = 1000; private Bundle mSharedElementBundle; private boolean mExitNotified; private boolean mSharedElementNotified; - private Activity mActivity; + private ExitTransitionCallbacks mExitCallbacks; private boolean mIsBackgroundReady; private boolean mIsCanceled; private Handler mHandler; @@ -62,20 +65,15 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { private Bundle mExitSharedElementBundle; private boolean mIsExitStarted; private boolean mSharedElementsHidden; - private HideSharedElementsCallback mHideSharedElementsCallback; - public ExitTransitionCoordinator(Activity activity, Window window, - SharedElementCallback listener, ArrayList names, + public ExitTransitionCoordinator(ExitTransitionCallbacks exitCallbacks, + Window window, SharedElementCallback listener, ArrayList names, ArrayList accepted, ArrayList mapped, boolean isReturning) { super(window, names, listener, isReturning); viewsReady(mapSharedElements(accepted, mapped)); stripOffscreenViews(); mIsBackgroundReady = !isReturning; - mActivity = activity; - } - - void setHideSharedElementsCallback(HideSharedElementsCallback callback) { - mHideSharedElementsCallback = callback; + mExitCallbacks = exitCallbacks; } @Override @@ -190,8 +188,8 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { private void hideSharedElements() { moveSharedElementsFromOverlay(); - if (mHideSharedElementsCallback != null) { - mHideSharedElementsCallback.hideSharedElements(); + if (mExitCallbacks != null) { + mExitCallbacks.hideSharedElements(); } if (!mIsHidden) { hideViews(mSharedElements); @@ -210,20 +208,16 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { decorView.suppressLayout(true); } moveSharedElementsToOverlay(); - startTransition(new Runnable() { - @Override - public void run() { - if (mActivity != null) { - beginTransitions(); - } else { - startExitTransition(); - } - } - }); + startTransition(this::beginTransitions); } } - public void startExit(int resultCode, Intent data) { + /** + * Starts the exit animation and sends back the activity result + */ + public void startExit(Activity activity) { + int resultCode = activity.mResultCode; + Intent data = activity.mResultData; if (!mIsExitStarted) { mIsExitStarted = true; pauseInput(); @@ -247,9 +241,9 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { .getApplicationInfo().targetSdkVersion >= VERSION_CODES.M; ArrayList sharedElementNames = targetsM ? mSharedElementNames : mAllSharedElementNames; - ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, this, + ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(activity, this, sharedElementNames, resultCode, data); - mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() { + activity.convertToTranslucent(new Activity.TranslucentConversionListener() { @Override public void onTranslucentConversionComplete(boolean drawComplete) { if (!mIsCanceled) { @@ -257,21 +251,19 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { } } }, options); - startTransition(new Runnable() { - @Override - public void run() { - startExitTransition(); - } - }); + startTransition(this::startExitTransition); } } - public void stop() { - if (mIsReturning && mActivity != null) { + /** + * Called from {@link Activity#onStop()} + */ + public void stop(Activity activity) { + if (mIsReturning && mExitCallbacks != null) { // Override the previous ActivityOptions. We don't want the // activity to have options since we're essentially canceling the // transition and finishing right now. - mActivity.convertToTranslucent(null, null); + activity.convertToTranslucent(null, null); finish(); } } @@ -434,7 +426,7 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { mSharedElementNotified = true; delayCancel(); - if (!mActivity.isTopOfTask()) { + if (mExitCallbacks.isReturnTransitionAllowed()) { mResultReceiver.send(MSG_ALLOW_RETURN_TRANSITION, null); } @@ -474,22 +466,20 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { } private void finishIfNecessary() { - if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty() || - mSharedElementsHidden)) { + if (mIsReturning && mExitNotified && mExitCallbacks != null && (mSharedElements.isEmpty() + || mSharedElementsHidden)) { finish(); } if (!mIsReturning && mExitNotified) { - mActivity = null; // don't need it anymore + mExitCallbacks = null; // don't need it anymore } } private void finish() { stopCancel(); - if (mActivity != null) { - mActivity.mActivityTransitionState.clear(); - mActivity.finish(); - mActivity.overridePendingTransition(0, 0); - mActivity = null; + if (mExitCallbacks != null) { + mExitCallbacks.onFinish(); + mExitCallbacks = null; } // Clear the state so that we can't hold any references accidentally and leak memory. clearState(); @@ -529,7 +519,49 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { } } - interface HideSharedElementsCallback { - void hideSharedElements(); + /** + * @hide + */ + public interface ExitTransitionCallbacks { + + /** + * Returns true if reverse exit animation is supported + */ + boolean isReturnTransitionAllowed(); + + /** + * Called then the transition finishes + */ + void onFinish(); + + /** + * Optional callback when the transition is hiding elements in the source surface + */ + default void hideSharedElements() { }; + } + + /** + * @hide + */ + public static class ActivityExitTransitionCallbacks implements ExitTransitionCallbacks { + + @NonNull + final Activity mActivity; + + ActivityExitTransitionCallbacks(@NonNull Activity activity) { + mActivity = activity; + } + + @Override + public boolean isReturnTransitionAllowed() { + return !mActivity.isTopOfTask(); + } + + @Override + public void onFinish() { + mActivity.mActivityTransitionState.clear(); + mActivity.finish(); + mActivity.overridePendingTransition(0, 0); + } } }