Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 648d4e5e authored by Sunny Goyal's avatar Sunny Goyal Committed by Android (Google) Code Review
Browse files

Merge "Fixing support for shared element transitions from a window"

parents bb06e94f 7b5abdcd
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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;
+26 −92
Original line number Diff line number Diff line
@@ -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<View, String>... 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<ActivityOptions, ExitTransitionCoordinator> startSharedElementAnimation(
            Window window, ExitTransitionCallbacks exitCallbacks, SharedElementCallback callback,
            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;
        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<View, String>[] sharedElements) {
    static ExitTransitionCoordinator makeSceneTransitionAnimation(
            ExitTransitionCallbacks exitCallbacks, SharedElementCallback callback, Window window,
            ActivityOptions opts, Pair<View, String>[] 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;
        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<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);
                }
            }
        }
    }

    /**
     * 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.
+6 −6
Original line number Diff line number Diff line
@@ -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;
+77 −45
Original line number Diff line number Diff line
@@ -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<String> names,
    public ExitTransitionCoordinator(ExitTransitionCallbacks exitCallbacks,
            Window window, SharedElementCallback listener, ArrayList<String> names,
            ArrayList<String> accepted, ArrayList<View> 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<String> 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);
        }
    }
}