Add support for cross-activity scenes and transitions
* Add theme attributes for specifying a top-level TransitionManager
for an activity window.
* Add window feature for automatic content transitions. This
automatically assigns/creates a Scene for setContentView calls.
* Add named transitions. This allows apps to define APIs for
handshake-agreements about which exit/entrance transitions to play.
* Add new transition type for ActivityOptions. This lets the system
use ActivityOptions to communicate transition specifics and
arguments to the called activity.
* Have ActivityManager pass appropriate ActivityOptions through to the
called Activity. Have the called activity call back into the caller
to let it know which transition of a possible requested set was
chosen.
Still to do:
* Define and pass arguments for transitions. This will require
defining a Parcelable version of TransitionValues and deciding how
much leeway apps should have for these things.
* Determine how to appropriately filter the ActivityOptions bundle so
that only appropriate data reaches the target.
* Determine if generalizing the auto-Scenes functionality to
ViewGroups is appropriate.
Change-Id: I10684b926129ab2fbc1adec9ef31767237acae79
diff --git a/api/current.txt b/api/current.txt
index 0a88f87..2ccce61 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2830,6 +2830,8 @@
method public java.lang.String getCallingPackage();
method public int getChangingConfigurations();
method public android.content.ComponentName getComponentName();
+ method public android.transition.Scene getContentScene();
+ method public android.transition.TransitionManager getContentTransitionManager();
method public android.view.View getCurrentFocus();
method public android.app.FragmentManager getFragmentManager();
method public android.content.Intent getIntent();
@@ -2935,6 +2937,7 @@
method public void reportFullyDrawn();
method public final boolean requestWindowFeature(int);
method public final void runOnUiThread(java.lang.Runnable);
+ method public void setContentTransitionManager(android.transition.TransitionManager);
method public void setContentView(int);
method public void setContentView(android.view.View);
method public void setContentView(android.view.View, android.view.ViewGroup.LayoutParams);
@@ -26363,10 +26366,15 @@
ctor public TransitionManager();
method public static void beginDelayedTransition(android.view.ViewGroup);
method public static void beginDelayedTransition(android.view.ViewGroup, android.transition.Transition);
+ method public android.transition.Transition getNamedTransition(java.lang.String, android.transition.Scene);
+ method public android.transition.Transition getNamedTransition(android.transition.Scene, java.lang.String);
+ method public java.lang.String[] getTargetSceneNames(android.transition.Scene);
method public static void go(android.transition.Scene);
method public static void go(android.transition.Scene, android.transition.Transition);
method public void setTransition(android.transition.Scene, android.transition.Transition);
method public void setTransition(android.transition.Scene, android.transition.Scene, android.transition.Transition);
+ method public void setTransition(android.transition.Scene, java.lang.String, android.transition.Transition);
+ method public void setTransition(java.lang.String, android.transition.Scene, android.transition.Transition);
method public void transitionTo(android.transition.Scene);
}
@@ -29289,6 +29297,7 @@
method public final android.view.WindowManager.LayoutParams getAttributes();
method public final android.view.Window.Callback getCallback();
method public final android.view.Window getContainer();
+ method public android.transition.Scene getContentScene();
method public final android.content.Context getContext();
method public abstract android.view.View getCurrentFocus();
method public abstract android.view.View getDecorView();
@@ -29296,6 +29305,7 @@
method protected final int getForcedWindowFlags();
method public abstract android.view.LayoutInflater getLayoutInflater();
method protected final int getLocalFeatures();
+ method public android.transition.TransitionManager getTransitionManager();
method public abstract int getVolumeControlStream();
method public android.view.WindowManager getWindowManager();
method public final android.content.res.TypedArray getWindowStyle();
@@ -29345,6 +29355,8 @@
method public void setSoftInputMode(int);
method public abstract void setTitle(java.lang.CharSequence);
method public abstract deprecated void setTitleColor(int);
+ method public void setTransitionManager(android.transition.TransitionManager);
+ method public void setTransitionOptions(android.os.Bundle);
method public void setType(int);
method public void setUiOptions(int);
method public void setUiOptions(int, int);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index d34b05d..698f06a 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -17,6 +17,9 @@
package android.app;
import android.annotation.NonNull;
+import android.transition.Scene;
+import android.transition.Transition;
+import android.transition.TransitionManager;
import android.util.ArrayMap;
import android.util.SuperNotCalledException;
import com.android.internal.app.ActionBarImpl;
@@ -1990,6 +1993,41 @@
}
/**
+ * Retrieve the {@link TransitionManager} responsible for default transitions in this window.
+ * Requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * <p>This method will return non-null after content has been initialized (e.g. by using
+ * {@link #setContentView}) if {@link Window#FEATURE_CONTENT_TRANSITIONS} has been granted.</p>
+ *
+ * @return This window's content TransitionManager or null if none is set.
+ */
+ public TransitionManager getContentTransitionManager() {
+ return getWindow().getTransitionManager();
+ }
+
+ /**
+ * Set the {@link TransitionManager} to use for default transitions in this window.
+ * Requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * @param tm The TransitionManager to use for scene changes.
+ */
+ public void setContentTransitionManager(TransitionManager tm) {
+ getWindow().setTransitionManager(tm);
+ }
+
+ /**
+ * Retrieve the {@link Scene} representing this window's current content.
+ * Requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * <p>This method will return null if the current content is not represented by a Scene.</p>
+ *
+ * @return Current Scene being shown or null
+ */
+ public Scene getContentScene() {
+ return getWindow().getContentScene();
+ }
+
+ /**
* Sets whether this activity is finished when touched outside its window's
* bounds.
*/
@@ -3408,7 +3446,29 @@
* @see #startActivity
*/
public void startActivityForResult(Intent intent, int requestCode) {
- startActivityForResult(intent, requestCode, null);
+ final TransitionManager tm = getWindow().getTransitionManager();
+ final Scene currScene = getWindow().getContentScene();
+ final String[] targetSceneNames = currScene != null && tm != null ?
+ tm.getTargetSceneNames(currScene) : null;
+
+ if (targetSceneNames == null || targetSceneNames.length == 0) {
+ startActivityForResult(intent, requestCode, null);
+ } else {
+ // TODO Capture the scene transition args and send along
+ final ActivityOptions opts = ActivityOptions.makeSceneTransitionAnimation(
+ targetSceneNames, null,
+ new ActivityOptions.OnSceneTransitionStartedListener() {
+ @Override public void onSceneTransitionStarted(String destSceneName) {
+ final Transition t = tm.getNamedTransition(currScene, destSceneName);
+ // TODO Fill this in to notify the outgoing activity that it should
+ // treat this as a sync point for the transition - the target
+ // transition has started.
+ Log.d(TAG, "Scene transition to scene " + destSceneName +
+ " transition " + t);
+ }
+ }, mHandler);
+ startActivityForResult(intent, requestCode, opts.toBundle());
+ }
}
/**
@@ -5229,6 +5289,16 @@
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config) {
+ attach(context, aThread, instr, token, ident, application, intent, info, title, parent, id,
+ lastNonConfigurationInstances, config, null);
+ }
+
+ final void attach(Context context, ActivityThread aThread,
+ Instrumentation instr, IBinder token, int ident,
+ Application application, Intent intent, ActivityInfo info,
+ CharSequence title, Activity parent, String id,
+ NonConfigurationInstances lastNonConfigurationInstances,
+ Configuration config, Bundle options) {
attachBaseContext(context);
mFragments.attachActivity(this, mContainer, null);
@@ -5265,6 +5335,7 @@
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
+ mWindow.setTransitionOptions(options);
mCurrentConfig = config;
}
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 44f6859..582ce3c 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -22,6 +22,8 @@
import android.os.Handler;
import android.os.IRemoteCallback;
import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
import android.view.View;
/**
@@ -30,6 +32,8 @@
* Context.startActivity(Intent, Bundle)} and related methods.
*/
public class ActivityOptions {
+ private static final String TAG = "ActivityOptions";
+
/**
* The package name that created the options.
* @hide
@@ -481,8 +485,19 @@
}
/** @hide */
- public IRemoteCallback getOnSceneTransitionStartedListener() {
- return mSceneTransitionStartedListener;
+ public void dispatchSceneTransitionStarted(String destScene) {
+ if (mSceneTransitionStartedListener != null) {
+ Bundle data = null;
+ if (!TextUtils.isEmpty(destScene)) {
+ data = new Bundle();
+ data.putString(KEY_DEST_SCENE_NAME_CHOSEN, destScene);
+ }
+ try {
+ mSceneTransitionStartedListener.sendResult(data);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Caught exception dispatching scene transition start", e);
+ }
+ }
}
/** @hide */
@@ -493,6 +508,12 @@
} catch (RemoteException e) {
}
}
+ if (mSceneTransitionStartedListener != null) {
+ try {
+ mSceneTransitionStartedListener.sendResult(null);
+ } catch (RemoteException e) {
+ }
+ }
}
/** @hide */
@@ -572,6 +593,8 @@
}
mSceneTransitionStartedListener = otherOptions.mSceneTransitionStartedListener;
mDestSceneNames = otherOptions.mDestSceneNames;
+ mTransitionArgs = otherOptions.mTransitionArgs;
+ mThumbnail = null;
mAnimationStartedListener = null;
break;
}
@@ -595,7 +618,7 @@
b.putInt(KEY_ANIM_TYPE, mAnimationType);
b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId);
b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId);
- b.putIBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
+ b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
!= null ? mAnimationStartedListener.asBinder() : null);
break;
case ANIM_SCALE_UP:
@@ -611,10 +634,31 @@
b.putParcelable(KEY_ANIM_THUMBNAIL, mThumbnail);
b.putInt(KEY_ANIM_START_X, mStartX);
b.putInt(KEY_ANIM_START_Y, mStartY);
- b.putIBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
+ b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
!= null ? mAnimationStartedListener.asBinder() : null);
break;
+ case ANIM_SCENE_TRANSITION:
+ b.putInt(KEY_ANIM_TYPE, mAnimationType);
+ b.putStringArray(KEY_DEST_SCENE_NAMES, mDestSceneNames);
+ b.putBundle(KEY_SCENE_TRANSITION_ARGS, mTransitionArgs);
+ b.putBinder(KEY_SCENE_TRANSITION_START_LISTENER, mSceneTransitionStartedListener
+ != null ? mSceneTransitionStartedListener.asBinder() : null);
+ break;
}
return b;
}
+
+ /**
+ * Return the filtered options only meant to be seen by the target activity itself
+ * @hide
+ */
+ public ActivityOptions forTargetActivity() {
+ if (mAnimationType == ANIM_SCENE_TRANSITION) {
+ final ActivityOptions result = new ActivityOptions();
+ result.update(this);
+ return result;
+ }
+
+ return null;
+ }
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 5e3dc02..e667bad 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -56,6 +56,7 @@
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
+import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
@@ -68,12 +69,15 @@
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
+import android.transition.Scene;
+import android.transition.TransitionManager;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.util.LogPrinter;
+import android.util.Pair;
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SuperNotCalledException;
@@ -284,6 +288,7 @@
boolean isForward;
int pendingConfigChanges;
boolean onlyLocalRequest;
+ Bundle activityOptions;
View mPendingRemoveWindow;
WindowManager mPendingRemoveWindowManager;
@@ -576,9 +581,10 @@
}
public final void scheduleResumeActivity(IBinder token, int processState,
- boolean isForward) {
+ boolean isForward, Bundle resumeArgs) {
updateProcessState(processState, false);
- sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0);
+ sendMessage(H.RESUME_ACTIVITY, new Pair<IBinder, Bundle>(token, resumeArgs),
+ isForward ? 1 : 0);
}
public final void scheduleSendResult(IBinder token, List<ResultInfo> results) {
@@ -594,7 +600,8 @@
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
int procState, Bundle state, List<ResultInfo> pendingResults,
List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
- String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) {
+ String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler,
+ Bundle resumeArgs) {
updateProcessState(procState, false);
@@ -616,6 +623,7 @@
r.profileFile = profileName;
r.profileFd = profileFd;
r.autoStopProfiler = autoStopProfiler;
+ r.activityOptions = resumeArgs;
updatePendingConfiguration(curConfig);
@@ -1189,7 +1197,7 @@
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
- ActivityClientRecord r = (ActivityClientRecord)msg.obj;
+ final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
@@ -1235,7 +1243,8 @@
break;
case RESUME_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
- handleResumeActivity((IBinder)msg.obj, true,
+ final Pair<IBinder, Bundle> resumeArgs = (Pair<IBinder, Bundle>) msg.obj;
+ handleResumeActivity(resumeArgs.first, resumeArgs.second, true,
msg.arg1 != 0, true);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
@@ -2032,7 +2041,7 @@
+ ", comp=" + name
+ ", token=" + token);
}
- return performLaunchActivity(r, null);
+ return performLaunchActivity(r, null, null);
}
public final Activity getActivity(IBinder token) {
@@ -2085,7 +2094,8 @@
sendMessage(H.CLEAN_UP_CONTEXT, cci);
}
- private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
+ private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent,
+ Bundle options) {
// System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");
ActivityInfo aInfo = r.activityInfo;
@@ -2143,7 +2153,7 @@
+ r.activityInfo.name + " with config " + config);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
- r.embeddedID, r.lastNonConfigurationInstances, config);
+ r.embeddedID, r.lastNonConfigurationInstances, config, options);
if (customIntent != null) {
activity.mIntent = customIntent;
@@ -2242,12 +2252,13 @@
if (localLOGV) Slog.v(
TAG, "Handling launch of " + r);
- Activity a = performLaunchActivity(r, customIntent);
+
+ Activity a = performLaunchActivity(r, customIntent, r.activityOptions);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
Bundle oldState = r.state;
- handleResumeActivity(r.token, false, r.isForward,
+ handleResumeActivity(r.token, r.activityOptions, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed);
if (!r.activity.mFinished && r.startsNotResumed) {
@@ -2808,12 +2819,13 @@
r.mPendingRemoveWindowManager = null;
}
- final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
- boolean reallyResume) {
+ final void handleResumeActivity(IBinder token, Bundle resumeArgs,
+ boolean clearHide, boolean isForward, boolean reallyResume) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
+ // TODO Push resumeArgs into the activity for consideration
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
@@ -3734,6 +3746,7 @@
}
}
r.startsNotResumed = tmp.startsNotResumed;
+ r.activityOptions = null;
handleLaunchActivity(r, currentIntent);
}
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index 347d43f..c8f1280 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -113,7 +113,8 @@
IBinder b = data.readStrongBinder();
int procState = data.readInt();
boolean isForward = data.readInt() != 0;
- scheduleResumeActivity(b, procState, isForward);
+ Bundle resumeArgs = data.readBundle();
+ scheduleResumeActivity(b, procState, isForward, resumeArgs);
return true;
}
@@ -145,8 +146,10 @@
ParcelFileDescriptor profileFd = data.readInt() != 0
? ParcelFileDescriptor.CREATOR.createFromParcel(data) : null;
boolean autoStopProfiler = data.readInt() != 0;
+ Bundle resumeArgs = data.readBundle();
scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo, procState, state,
- ri, pi, notResumed, isForward, profileName, profileFd, autoStopProfiler);
+ ri, pi, notResumed, isForward, profileName, profileFd, autoStopProfiler,
+ resumeArgs);
return true;
}
@@ -696,13 +699,15 @@
data.recycle();
}
- public final void scheduleResumeActivity(IBinder token, int procState, boolean isForward)
+ public final void scheduleResumeActivity(IBinder token, int procState, boolean isForward,
+ Bundle resumeArgs)
throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeStrongBinder(token);
data.writeInt(procState);
data.writeInt(isForward ? 1 : 0);
+ data.writeBundle(resumeArgs);
mRemote.transact(SCHEDULE_RESUME_ACTIVITY_TRANSACTION, data, null,
IBinder.FLAG_ONEWAY);
data.recycle();
@@ -722,9 +727,10 @@
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
int procState, Bundle state, List<ResultInfo> pendingResults,
- List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
- String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler)
- throws RemoteException {
+ List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
+ String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler,
+ Bundle resumeArgs)
+ throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
intent.writeToParcel(data, 0);
@@ -747,6 +753,7 @@
data.writeInt(0);
}
data.writeInt(autoStopProfiler ? 1 : 0);
+ data.writeBundle(resumeArgs);
mRemote.transact(SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION, data, null,
IBinder.FLAG_ONEWAY);
data.recycle();
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index d0cc1bb..1ea9d87 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -50,15 +50,16 @@
int configChanges) throws RemoteException;
void scheduleWindowVisibility(IBinder token, boolean showWindow) throws RemoteException;
void scheduleSleeping(IBinder token, boolean sleeping) throws RemoteException;
- void scheduleResumeActivity(IBinder token, int procState, boolean isForward)
+ void scheduleResumeActivity(IBinder token, int procState, boolean isForward, Bundle resumeArgs)
throws RemoteException;
void scheduleSendResult(IBinder token, List<ResultInfo> results) throws RemoteException;
void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
int procState, Bundle state, List<ResultInfo> pendingResults,
- List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
- String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler)
- throws RemoteException;
+ List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
+ String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler,
+ Bundle resumeArgs)
+ throws RemoteException;
void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults,
List<Intent> pendingNewIntents, int configChanges,
boolean notResumed, Configuration config) throws RemoteException;
diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java
index 9f77d5e..9fa554c 100644
--- a/core/java/android/transition/TransitionInflater.java
+++ b/core/java/android/transition/TransitionInflater.java
@@ -20,6 +20,7 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Xml;
import android.view.InflateException;
@@ -291,19 +292,40 @@
int toId = a.getResourceId(
com.android.internal.R.styleable.TransitionManager_toScene, -1);
if (toId >= 0) toScene = Scene.getSceneForLayout(sceneRoot, toId, mContext);
+ String fromName = a.getString(
+ com.android.internal.R.styleable.TransitionManager_fromSceneName);
+ String toName = a.getString(
+ com.android.internal.R.styleable.TransitionManager_toSceneName);
if (transitionId >= 0) {
Transition transition = inflateTransition(transitionId);
if (transition != null) {
if (fromScene != null) {
- if (toScene == null){
- throw new RuntimeException("No matching toScene for given fromScene " +
- "for transition ID " + transitionId);
- } else {
+ boolean hasDest = false;
+ if (toScene != null) {
transitionManager.setTransition(fromScene, toScene, transition);
+ hasDest = true;
+ }
+
+ if (!TextUtils.isEmpty(toName)) {
+ transitionManager.setTransition(fromScene, toName, transition);
+ hasDest = true;
+ }
+
+ if (!hasDest) {
+ throw new RuntimeException("No matching toScene or toSceneName for given " +
+ "fromScene for transition ID " + transitionId);
}
} else if (toId >= 0) {
transitionManager.setTransition(toScene, transition);
}
+ if (fromName != null) {
+ if (toScene != null) {
+ transitionManager.setTransition(fromName, toScene, transition);
+ } else {
+ throw new RuntimeException("No matching toScene for given fromSceneName " +
+ "for transition ID " + transitionId);
+ }
+ }
}
}
a.recycle();
diff --git a/core/java/android/transition/TransitionManager.java b/core/java/android/transition/TransitionManager.java
index 3bf6790..0106f7fb 100644
--- a/core/java/android/transition/TransitionManager.java
+++ b/core/java/android/transition/TransitionManager.java
@@ -67,9 +67,15 @@
private static Transition sDefaultTransition = new AutoTransition();
+ private static final String[] EMPTY_STRINGS = new String[0];
+
ArrayMap<Scene, Transition> mSceneTransitions = new ArrayMap<Scene, Transition>();
ArrayMap<Scene, ArrayMap<Scene, Transition>> mScenePairTransitions =
new ArrayMap<Scene, ArrayMap<Scene, Transition>>();
+ ArrayMap<Scene, ArrayMap<String, Transition>> mSceneNameTransitions =
+ new ArrayMap<Scene, ArrayMap<String, Transition>>();
+ ArrayMap<String, ArrayMap<Scene, Transition>> mNameSceneTransitions =
+ new ArrayMap<String, ArrayMap<Scene, Transition>>();
private static ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>
sRunningTransitions =
new ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>();
@@ -218,6 +224,141 @@
}
/**
+ * Retrieve the transition from a named scene to a target defined scene if one has been
+ * associated with this TransitionManager.
+ *
+ * <p>A named scene is an indirect link for a transition. Fundamentally a named
+ * scene represents a potentially arbitrary intersection point of two otherwise independent
+ * transitions. Activity A may define a transition from scene X to "com.example.scene.FOO"
+ * while activity B may define a transition from scene "com.example.scene.FOO" to scene Y.
+ * In this way applications may define an API for more sophisticated transitions between
+ * caller and called activities very similar to the way that <code>Intent</code> extras
+ * define APIs for arguments and data propagation between activities.</p>
+ *
+ * @param fromName Named scene that this transition corresponds to
+ * @param toScene Target scene that this transition will move to
+ * @return Transition corresponding to the given fromName and toScene or null
+ * if no association exists in this TransitionManager
+ *
+ * @see #setTransition(String, Scene, Transition)
+ */
+ public Transition getNamedTransition(String fromName, Scene toScene) {
+ ArrayMap<Scene, Transition> m = mNameSceneTransitions.get(fromName);
+ if (m != null) {
+ return m.get(toScene);
+ }
+ return null;
+ }
+
+ /**
+ * Retrieve the transition from a defined scene to a target named scene if one has been
+ * associated with this TransitionManager.
+ *
+ * <p>A named scene is an indirect link for a transition. Fundamentally a named
+ * scene represents a potentially arbitrary intersection point of two otherwise independent
+ * transitions. Activity A may define a transition from scene X to "com.example.scene.FOO"
+ * while activity B may define a transition from scene "com.example.scene.FOO" to scene Y.
+ * In this way applications may define an API for more sophisticated transitions between
+ * caller and called activities very similar to the way that <code>Intent</code> extras
+ * define APIs for arguments and data propagation between activities.</p>
+ *
+ * @param fromScene Scene that this transition starts from
+ * @param toName Name of the target scene
+ * @return Transition corresponding to the given fromScene and toName or null
+ * if no association exists in this TransitionManager
+ */
+ public Transition getNamedTransition(Scene fromScene, String toName) {
+ ArrayMap<String, Transition> m = mSceneNameTransitions.get(fromScene);
+ if (m != null) {
+ return m.get(toName);
+ }
+ return null;
+ }
+
+ /**
+ * Retrieve the supported target named scenes when transitioning away from the given scene.
+ *
+ * <p>A named scene is an indirect link for a transition. Fundamentally a named
+ * scene represents a potentially arbitrary intersection point of two otherwise independent
+ * transitions. Activity A may define a transition from scene X to "com.example.scene.FOO"
+ * while activity B may define a transition from scene "com.example.scene.FOO" to scene Y.
+ * In this way applications may define an API for more sophisticated transitions between
+ * caller and called activities very similar to the way that <code>Intent</code> extras
+ * define APIs for arguments and data propagation between activities.</p>
+ *
+ * @param fromScene Scene to transition from
+ * @return An array of Strings naming each supported transition starting from
+ * <code>fromScene</code>. If no transitions to a named scene from the given
+ * scene are supported this function will return a String[] of length 0.
+ *
+ * @see #setTransition(Scene, String, Transition)
+ */
+ public String[] getTargetSceneNames(Scene fromScene) {
+ final ArrayMap<String, Transition> m = mSceneNameTransitions.get(fromScene);
+ if (m == null) {
+ return EMPTY_STRINGS;
+ }
+ final int count = m.size();
+ final String[] result = new String[count];
+ for (int i = 0; i < count; i++) {
+ result[i] = m.keyAt(i);
+ }
+ return result;
+ }
+
+ /**
+ * Set a transition from a specific scene to a named scene.
+ *
+ * <p>A named scene is an indirect link for a transition. Fundamentally a named
+ * scene represents a potentially arbitrary intersection point of two otherwise independent
+ * transitions. Activity A may define a transition from scene X to "com.example.scene.FOO"
+ * while activity B may define a transition from scene "com.example.scene.FOO" to scene Y.
+ * In this way applications may define an API for more sophisticated transitions between
+ * caller and called activities very similar to the way that <code>Intent</code> extras
+ * define APIs for arguments and data propagation between activities.</p>
+ *
+ * @param fromScene Scene to transition from
+ * @param toName Named scene to transition to
+ * @param transition Transition to use
+ *
+ * @see #getTargetSceneNames(Scene)
+ */
+ public void setTransition(Scene fromScene, String toName, Transition transition) {
+ ArrayMap<String, Transition> m = mSceneNameTransitions.get(fromScene);
+ if (m == null) {
+ m = new ArrayMap<String, Transition>();
+ mSceneNameTransitions.put(fromScene, m);
+ }
+ m.put(toName, transition);
+ }
+
+ /**
+ * Set a transition from a named scene to a concrete scene.
+ *
+ * <p>A named scene is an indirect link for a transition. Fundamentally a named
+ * scene represents a potentially arbitrary intersection point of two otherwise independent
+ * transitions. Activity A may define a transition from scene X to "com.example.scene.FOO"
+ * while activity B may define a transition from scene "com.example.scene.FOO" to scene Y.
+ * In this way applications may define an API for more sophisticated transitions between
+ * caller and called activities very similar to the way that <code>Intent</code> extras
+ * define APIs for arguments and data propagation between activities.</p>
+ *
+ * @param fromName Named scene to transition from
+ * @param toScene Scene to transition to
+ * @param transition Transition to use
+ *
+ * @see #getNamedTransition(String, Scene)
+ */
+ public void setTransition(String fromName, Scene toScene, Transition transition) {
+ ArrayMap<Scene, Transition> m = mNameSceneTransitions.get(fromName);
+ if (m == null) {
+ m = new ArrayMap<Scene, Transition>();
+ mNameSceneTransitions.put(fromName, m);
+ }
+ m.put(toScene, transition);
+ }
+
+ /**
* This private utility class is used to listen for both OnPreDraw and
* OnAttachStateChange events. OnPreDraw events are the main ones we care
* about since that's what triggers the transition to take place.
@@ -329,7 +470,6 @@
// Auto transition if there is no transition declared for the Scene, but there is
// a root or parent view
changeScene(scene, getTransition(scene));
-
}
/**
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 11d8d36..2f62431 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -27,6 +27,8 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.SystemProperties;
+import android.transition.Scene;
+import android.transition.TransitionManager;
import android.view.accessibility.AccessibilityEvent;
/**
@@ -1333,4 +1335,47 @@
* @param event A key or touch event to inject to this window.
*/
public void injectInputEvent(InputEvent event) { }
+
+ /**
+ * Retrieve the {@link TransitionManager} responsible for for default transitions
+ * in this window. Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * <p>This method will return non-null after content has been initialized (e.g. by using
+ * {@link #setContentView}) if {@link #FEATURE_CONTENT_TRANSITIONS} has been granted.</p>
+ *
+ * @return This window's content TransitionManager or null if none is set.
+ */
+ public TransitionManager getTransitionManager() {
+ return null;
+ }
+
+ /**
+ * Set the {@link TransitionManager} to use for default transitions in this window.
+ * Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * @param tm The TransitionManager to use for scene changes.
+ */
+ public void setTransitionManager(TransitionManager tm) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Retrieve the {@link Scene} representing this window's current content.
+ * Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * <p>This method will return null if the current content is not represented by a Scene.</p>
+ *
+ * @return Current Scene being shown or null
+ */
+ public Scene getContentScene() {
+ return null;
+ }
+
+ /**
+ * Set options that can affect the transition behavior within this window.
+ * @param options Options to set or null for none
+ */
+ public void setTransitionOptions(Bundle options) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index 27a7caf..d0abe9c 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -22,7 +22,9 @@
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.WindowManager.LayoutParams.*;
+import android.app.ActivityOptions;
import android.transition.Scene;
+import android.transition.Transition;
import android.transition.TransitionInflater;
import android.transition.TransitionManager;
import android.view.ViewConfiguration;
@@ -148,6 +150,8 @@
private PanelMenuPresenterCallback mPanelMenuPresenterCallback;
private TransitionManager mTransitionManager;
+ private Scene mContentScene;
+ private Bundle mTransitionOptions;
// The icon resource has been explicitly set elsewhere
// and should not be overwritten with a default.
@@ -284,6 +288,26 @@
}
@Override
+ public TransitionManager getTransitionManager() {
+ return mTransitionManager;
+ }
+
+ @Override
+ public void setTransitionManager(TransitionManager tm) {
+ mTransitionManager = tm;
+ }
+
+ @Override
+ public Scene getContentScene() {
+ return mContentScene;
+ }
+
+ @Override
+ public void setTransitionOptions(Bundle options) {
+ mTransitionOptions = options;
+ }
+
+ @Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
@@ -297,7 +321,7 @@
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
- mTransitionManager.transitionTo(newScene);
+ transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
@@ -326,7 +350,7 @@
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
- mTransitionManager.transitionTo(newScene);
+ transitionTo(newScene);
} else {
mContentParent.addView(view, params);
}
@@ -353,6 +377,33 @@
}
}
+ private void transitionTo(Scene scene) {
+ Transition selected = null;
+ if (mTransitionOptions != null) {
+ final ActivityOptions opts = new ActivityOptions(mTransitionOptions);
+ mTransitionOptions = null;
+
+ String selectedName = null;
+ for (String sceneName : opts.getDestSceneNames()) {
+ final Transition t = mTransitionManager.getNamedTransition(sceneName, scene);
+ if (t != null) {
+ // TODO handle args/state; inject into t/clone with params
+ selected = t;
+ selectedName = sceneName;
+ break;
+ }
+ }
+ opts.dispatchSceneTransitionStarted(selectedName);
+ }
+
+ if (selected != null) {
+ TransitionManager.go(scene, selected);
+ } else {
+ mTransitionManager.transitionTo(scene);
+ }
+ mContentScene = scene;
+ }
+
@Override
public View getCurrentFocus() {
return mDecor != null ? mDecor.findFocus() : null;
diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java
index 5776181..8e85914 100644
--- a/services/java/com/android/server/am/ActivityRecord.java
+++ b/services/java/com/android/server/am/ActivityRecord.java
@@ -723,6 +723,10 @@
}
}
+ ActivityOptions getOptionsForTargetActivityLocked() {
+ return pendingOptions != null ? pendingOptions.forTargetActivity() : null;
+ }
+
void clearOptionsLocked() {
if (pendingOptions != null) {
pendingOptions.abort();
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index cd2cb331..2e914aa 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -36,6 +36,8 @@
import static com.android.server.am.ActivityStackSupervisor.DEBUG_STATES;
import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
+import android.os.Trace;
+import android.util.Log;
import com.android.internal.os.BatteryStatsImpl;
import com.android.internal.util.Objects;
import com.android.server.Watchdog;
@@ -1078,7 +1080,7 @@
mWindowManager.setAppVisibility(r.appToken, true);
}
if (r != starting) {
- mStackSupervisor.startSpecificActivityLocked(r, false, false);
+ mStackSupervisor.startSpecificActivityLocked(r, false, false, null);
}
}
@@ -1511,7 +1513,13 @@
mWindowManager.prepareAppTransition(AppTransition.TRANSIT_ACTIVITY_OPEN, false);
}
}
+
+ Bundle resumeAnimOptions = null;
if (anim) {
+ ActivityOptions opts = next.getOptionsForTargetActivityLocked();
+ if (opts != null) {
+ resumeAnimOptions = opts.toBundle();
+ }
next.applyOptionsLocked();
} else {
next.clearOptionsLocked();
@@ -1604,7 +1612,7 @@
next.app.pendingUiClean = true;
next.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_TOP);
next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
- mService.isNextTransitionForward());
+ mService.isNextTransitionForward(), resumeAnimOptions);
mStackSupervisor.checkReadyForSleepLocked();
@@ -1628,7 +1636,7 @@
next.nonLocalizedLabel, next.labelRes, next.icon, next.logo,
next.windowFlags, null, true);
}
- mStackSupervisor.startSpecificActivityLocked(next, true, false);
+ mStackSupervisor.startSpecificActivityLocked(next, true, false, resumeAnimOptions);
if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
return true;
}
@@ -1666,7 +1674,7 @@
if (DEBUG_SWITCH) Slog.v(TAG, "Restarting: " + next);
}
if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Restarting " + next);
- mStackSupervisor.startSpecificActivityLocked(next, true, true);
+ mStackSupervisor.startSpecificActivityLocked(next, true, true, resumeAnimOptions);
}
if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
diff --git a/services/java/com/android/server/am/ActivityStackSupervisor.java b/services/java/com/android/server/am/ActivityStackSupervisor.java
index f4ca324..04617af 100644
--- a/services/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/java/com/android/server/am/ActivityStackSupervisor.java
@@ -379,7 +379,7 @@
if (hr.app == null && app.uid == hr.info.applicationInfo.uid
&& processName.equals(hr.processName)) {
try {
- if (realStartActivityLocked(hr, app, true, true)) {
+ if (realStartActivityLocked(hr, app, true, true, null)) {
didSomething = true;
}
} catch (Exception e) {
@@ -868,7 +868,7 @@
}
final boolean realStartActivityLocked(ActivityRecord r,
- ProcessRecord app, boolean andResume, boolean checkConfig)
+ ProcessRecord app, boolean andResume, boolean checkConfig, Bundle resumeArgs)
throws RemoteException {
r.startFreezingScreenLocked(app, 0);
@@ -960,13 +960,14 @@
}
}
}
+
app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_TOP);
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
System.identityHashCode(r), r.info,
new Configuration(mService.mConfiguration), r.compat,
app.repProcState, r.icicle, results, newIntents, !andResume,
mService.isNextTransitionForward(), profileFile, profileFd,
- profileAutoStop);
+ profileAutoStop, resumeArgs);
if ((app.info.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) {
// This may be a heavy-weight process! Note that the package
@@ -1040,7 +1041,7 @@
}
void startSpecificActivityLocked(ActivityRecord r,
- boolean andResume, boolean checkConfig) {
+ boolean andResume, boolean checkConfig, Bundle resumeArgs) {
// Is this activity's application already running?
ProcessRecord app = mService.getProcessRecordLocked(r.processName,
r.info.applicationInfo.uid, true);
@@ -1057,7 +1058,7 @@
// separate apk in the process.
app.addPackage(r.info.packageName, mService.mProcessStats);
}
- realStartActivityLocked(r, app, andResume, checkConfig);
+ realStartActivityLocked(r, app, andResume, checkConfig, resumeArgs);
return;
} catch (RemoteException e) {
Slog.w(TAG, "Exception when starting activity "