diff options
| -rw-r--r-- | core/java/android/app/Activity.java | 17 | ||||
| -rw-r--r-- | core/java/android/app/ActivityOptions.java | 157 | ||||
| -rw-r--r-- | core/java/android/app/ActivityTransitionState.java | 32 | ||||
| -rw-r--r-- | core/java/android/app/EnterTransitionCoordinator.java | 14 | ||||
| -rw-r--r-- | core/java/android/app/ExitTransitionCoordinator.java | 24 | ||||
| -rw-r--r-- | core/java/android/transition/Transition.java | 20 | ||||
| -rw-r--r-- | core/java/android/transition/TransitionManager.java | 2 | ||||
| -rw-r--r-- | core/java/android/transition/TransitionSet.java | 14 | ||||
| -rw-r--r-- | core/res/res/values/ids.xml | 4 | ||||
| -rw-r--r-- | core/res/res/values/symbols.xml | 3 |
10 files changed, 261 insertions, 26 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index ac5f3effa1f9..f5ef703e39fa 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,14 @@ public class Activity extends ContextThemeWrapper } } + private Bundle transferSpringboardActivityOptions(Bundle options) { + if (options == null && (mWindow != null && !mWindow.isActive())) { + return mActivityTransitionState.transferEnterActivityOptions(); + } else { + return options; + } + } + /** * @hide Implement to provide correct calling token. */ @@ -4282,6 +4291,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 +4327,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 +4360,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 +4800,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 +4866,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, @@ -6650,11 +6664,11 @@ public class Activity extends ContextThemeWrapper mVisibleFromClient = !mWindow.getWindowStyle().getBoolean( com.android.internal.R.styleable.Window_windowNoDisplay, false); mFragments.dispatchActivityCreated(); - mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions()); } final void performCreate(Bundle icicle) { restoreHasCurrentPermissionRequest(icicle); + mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions()); onCreate(icicle); mActivityTransitionState.readState(icicle); performCreateCommon(); @@ -6662,6 +6676,7 @@ public class Activity extends ContextThemeWrapper final void performCreate(Bundle icicle, PersistableBundle persistentState) { restoreHasCurrentPermissionRequest(icicle); + mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions()); onCreate(icicle, persistentState); mActivityTransitionState.readState(icicle); performCreateCommon(); diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 4c8ddc7eb6b1..ccc37d72e846 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<View, String>... 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. + * <p> + * 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<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; + } + + /** + * 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<View, String>[] sharedElements) { + if (!window.hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) { + opts.mAnimationType = ANIM_DEFAULT; + return null; + } opts.mAnimationType = ANIM_SCENE_TRANSITION; ArrayList<String> names = new ArrayList<String>(); @@ -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<String> 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<String> 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<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); + } + } + } + } } diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java index 02eb4d3ded1c..88526fb65162 100644 --- a/core/java/android/app/ActivityTransitionState.java +++ b/core/java/android/app/ActivityTransitionState.java @@ -105,6 +105,12 @@ class ActivityTransitionState { private boolean mIsEnterTriggered; + /** + * The ActivityOptions Bundle. This is used to transfer ActivityOptions through a + * springboard Activity. + */ + private Bundle mEnterBundle; + public ActivityTransitionState() { } @@ -150,6 +156,10 @@ class ActivityTransitionState { } public void setEnterActivityOptions(Activity activity, ActivityOptions options) { + if (options != null && mEnterBundle == null && + options.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) { + mEnterBundle = options.toBundle(); + } final Window window = activity.getWindow(); if (window == null) { return; @@ -185,7 +195,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(); @@ -224,6 +239,13 @@ class ActivityTransitionState { mEnterActivityOptions = null; } + Bundle transferEnterActivityOptions() { + mEnterActivityOptions = null; + Bundle options = mEnterBundle; + mEnterBundle = null; + return options; + } + public void onStop() { restoreExitedViews(); if (mEnterTransitionCoordinator != null) { @@ -275,7 +297,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 +325,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 8bf1e9a97e48..5d12b0da6ab8 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<String> sharedElementNames, boolean isReturning) { + ArrayList<String> 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<String> accepted, ArrayList<String> localNames, ArrayList<View> 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 040428877371..160c28592582 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<String> names, + public ExitTransitionCoordinator(Activity activity, Window window, + SharedElementCallback listener, ArrayList<String> names, ArrayList<String> accepted, ArrayList<View> 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 316c7e3f6084..8823605c52b6 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -1941,6 +1941,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<Animator, AnimationInfo> 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. * * @hide diff --git a/core/java/android/transition/TransitionManager.java b/core/java/android/transition/TransitionManager.java index 71c80991bb19..f2c871e3c718 100644 --- a/core/java/android/transition/TransitionManager.java +++ b/core/java/android/transition/TransitionManager.java @@ -440,7 +440,7 @@ public class TransitionManager { ArrayList<Transition> 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 583dc0f1ef2c..a41fe64d0be1 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 7f8acd3d61cc..5c165e6709ee 100644 --- a/core/res/res/values/ids.xml +++ b/core/res/res/values/ids.xml @@ -122,9 +122,11 @@ <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_SET_PROGRESS}. --> <item type="id" name="accessibilityActionSetProgress" /> - + <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_CONTEXT_CLICK}. --> <item type="id" name="accessibilityActionContextClick" /> <item type="id" name="remote_input_tag" /> + + <item type="id" name="cross_task_transition" /> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 8edd9d1bb1d6..10092945e8f8 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2612,4 +2612,7 @@ <java-symbol type="array" name="config_defaultPinnerServiceFiles" /> <java-symbol type="string" name="suspended_widget_accessibility" /> + + <!-- Used internally for assistant to launch activity transitions --> + <java-symbol type="id" name="cross_task_transition" /> </resources> |