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

Commit 7b5abdcd authored by Jon Miranda's avatar Jon Miranda Committed by Sunny Goyal
Browse files

Fixing support for shared element transitions from a window

Previously animation from window always ran in exit mode
causing the animation to play in the calling window surface
instead of the launching activity surface. This causes
flicker on transition end when the rendering switches to
the new activity surface.

Test: Verified with demo app
Change-Id: I07f5aa4e390fd9dab9883894ba7b780170c0e212
parent 83c8965c
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);
        }
    }
}