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

This commit is contained in:
Sunny Goyal
2020-12-01 21:19:16 +00:00
committed by Android (Google) Code Review
4 changed files with 110 additions and 144 deletions

View File

@@ -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;

View File

@@ -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;
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<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.

View File

@@ -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;

View File

@@ -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);
}
}
}