Merge "Don't auto scroll empty lists"
diff --git a/api/current.txt b/api/current.txt
index 7fed429..c6c8e421 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1020,6 +1020,7 @@
field public static final int shrinkColumns = 16843082; // 0x101014a
field public static final deprecated int singleLine = 16843101; // 0x101015d
field public static final int singleUser = 16843711; // 0x10103bf
+ field public static final int slideEdge = 16843835; // 0x101043b
field public static final int smallIcon = 16843422; // 0x101029e
field public static final int smallScreens = 16843396; // 0x1010284
field public static final int smoothScrollbar = 16843313; // 0x1010231
@@ -3150,6 +3151,7 @@
method public void finishActivityFromChild(android.app.Activity, int);
method public void finishAffinity();
method public void finishFromChild(android.app.Activity);
+ method public void finishWithTransition();
method public android.app.ActionBar getActionBar();
method public final android.app.Application getApplication();
method public android.content.ComponentName getCallingActivity();
@@ -6840,6 +6842,8 @@
field public static final java.lang.String ACTION_INSTALL_PACKAGE = "android.intent.action.INSTALL_PACKAGE";
field public static final java.lang.String ACTION_LOCALE_CHANGED = "android.intent.action.LOCALE_CHANGED";
field public static final java.lang.String ACTION_MAIN = "android.intent.action.MAIN";
+ field public static final java.lang.String ACTION_MANAGED_PROFILE_ADDED = "android.intent.action.MANAGED_PROFILE_ADDED";
+ field public static final java.lang.String ACTION_MANAGED_PROFILE_REMOVED = "android.intent.action.MANAGED_PROFILE_REMOVED";
field public static final java.lang.String ACTION_MANAGE_NETWORK_USAGE = "android.intent.action.MANAGE_NETWORK_USAGE";
field public static final java.lang.String ACTION_MANAGE_PACKAGE_STORAGE = "android.intent.action.MANAGE_PACKAGE_STORAGE";
field public static final java.lang.String ACTION_MEDIA_BAD_REMOVAL = "android.intent.action.MEDIA_BAD_REMOVAL";
@@ -6989,6 +6993,7 @@
field public static final java.lang.String EXTRA_TEXT = "android.intent.extra.TEXT";
field public static final java.lang.String EXTRA_TITLE = "android.intent.extra.TITLE";
field public static final java.lang.String EXTRA_UID = "android.intent.extra.UID";
+ field public static final java.lang.String EXTRA_USER = "android.intent.extra.user";
field public static final int FILL_IN_ACTION = 1; // 0x1
field public static final int FILL_IN_CATEGORIES = 4; // 0x4
field public static final int FILL_IN_CLIP_DATA = 128; // 0x80
@@ -27403,6 +27408,16 @@
method public void setResizeClip(boolean);
}
+ public class CircularPropagation extends android.transition.VisibilityPropagation {
+ ctor public CircularPropagation();
+ method public long getStartDelay(android.view.ViewGroup, android.transition.Transition, android.transition.TransitionValues, android.transition.TransitionValues);
+ method public void setPropagationSpeed(float);
+ }
+
+ public class Explode extends android.transition.Visibility {
+ ctor public Explode();
+ }
+
public class Fade extends android.transition.Visibility {
ctor public Fade();
ctor public Fade(int);
@@ -27410,6 +27425,12 @@
field public static final int OUT = 2; // 0x2
}
+ public class MoveImage extends android.transition.Transition {
+ ctor public MoveImage();
+ method public void captureEndValues(android.transition.TransitionValues);
+ method public void captureStartValues(android.transition.TransitionValues);
+ }
+
public final class Scene {
ctor public Scene(android.view.ViewGroup);
ctor public Scene(android.view.ViewGroup, android.view.View);
@@ -27422,6 +27443,27 @@
method public void setExitAction(java.lang.Runnable);
}
+ public class SidePropagation extends android.transition.VisibilityPropagation {
+ ctor public SidePropagation();
+ method public long getStartDelay(android.view.ViewGroup, android.transition.Transition, android.transition.TransitionValues, android.transition.TransitionValues);
+ method public void setPropagationSpeed(float);
+ method public void setSide(int);
+ field public static final int BOTTOM = 3; // 0x3
+ field public static final int LEFT = 0; // 0x0
+ field public static final int RIGHT = 2; // 0x2
+ field public static final int TOP = 1; // 0x1
+ }
+
+ public class Slide extends android.transition.Visibility {
+ ctor public Slide();
+ ctor public Slide(int);
+ method public void setSlideEdge(int);
+ field public static final int BOTTOM = 3; // 0x3
+ field public static final int LEFT = 0; // 0x0
+ field public static final int RIGHT = 2; // 0x2
+ field public static final int TOP = 1; // 0x1
+ }
+
public abstract class Transition implements java.lang.Cloneable {
ctor public Transition();
method public android.transition.Transition addListener(android.transition.Transition.TransitionListener);
@@ -27439,8 +27481,11 @@
method public android.transition.Transition excludeTarget(android.view.View, boolean);
method public android.transition.Transition excludeTarget(java.lang.Class, boolean);
method public long getDuration();
+ method public android.graphics.Rect getEpicenter();
+ method public android.transition.Transition.EpicenterCallback getEpicenterCallback();
method public android.animation.TimeInterpolator getInterpolator();
method public java.lang.String getName();
+ method public android.transition.TransitionPropagation getPropagation();
method public long getStartDelay();
method public java.util.List<java.lang.Integer> getTargetIds();
method public java.util.List<android.view.View> getTargets();
@@ -27450,10 +27495,17 @@
method public android.transition.Transition removeTarget(int);
method public android.transition.Transition removeTarget(android.view.View);
method public android.transition.Transition setDuration(long);
+ method public void setEpicenterCallback(android.transition.Transition.EpicenterCallback);
method public android.transition.Transition setInterpolator(android.animation.TimeInterpolator);
+ method public void setPropagation(android.transition.TransitionPropagation);
method public android.transition.Transition setStartDelay(long);
}
+ public static abstract class Transition.EpicenterCallback {
+ ctor public Transition.EpicenterCallback();
+ method public abstract android.graphics.Rect getEpicenter(android.transition.Transition);
+ }
+
public static abstract interface Transition.TransitionListener {
method public abstract void onTransitionCancel(android.transition.Transition);
method public abstract void onTransitionEnd(android.transition.Transition);
@@ -27480,6 +27532,13 @@
method public void transitionTo(android.transition.Scene);
}
+ public abstract class TransitionPropagation {
+ ctor public TransitionPropagation();
+ method public abstract void captureValues(android.transition.TransitionValues);
+ method public abstract java.lang.String[] getPropagationProperties();
+ method public abstract long getStartDelay(android.view.ViewGroup, android.transition.Transition, android.transition.TransitionValues, android.transition.TransitionValues);
+ }
+
public class TransitionSet extends android.transition.Transition {
ctor public TransitionSet();
method public android.transition.TransitionSet addTransition(android.transition.Transition);
@@ -27504,7 +27563,18 @@
method public void captureStartValues(android.transition.TransitionValues);
method public boolean isVisible(android.transition.TransitionValues);
method public android.animation.Animator onAppear(android.view.ViewGroup, android.transition.TransitionValues, int, android.transition.TransitionValues, int);
+ method public android.animation.Animator onAppear(android.view.ViewGroup, android.view.View, android.transition.TransitionValues, android.transition.TransitionValues);
method public android.animation.Animator onDisappear(android.view.ViewGroup, android.transition.TransitionValues, int, android.transition.TransitionValues, int);
+ method public android.animation.Animator onDisappear(android.view.ViewGroup, android.view.View, android.transition.TransitionValues, android.transition.TransitionValues);
+ }
+
+ public abstract class VisibilityPropagation extends android.transition.TransitionPropagation {
+ ctor public VisibilityPropagation();
+ method public void captureValues(android.transition.TransitionValues);
+ method public java.lang.String[] getPropagationProperties();
+ method public int getViewVisibility(android.transition.TransitionValues);
+ method public int getViewX(android.transition.TransitionValues);
+ method public int getViewY(android.transition.TransitionValues);
}
}
@@ -30457,6 +30527,8 @@
method public boolean requestFeature(int);
method public abstract void restoreHierarchyState(android.os.Bundle);
method public abstract android.os.Bundle saveHierarchyState();
+ method public void setAllowOverlappingEnterTransition(boolean);
+ method public void setAllowOverlappingExitTransition(boolean);
method public void setAttributes(android.view.WindowManager.LayoutParams);
method public abstract void setBackgroundDrawable(android.graphics.drawable.Drawable);
method public void setBackgroundDrawableResource(int);
@@ -30485,7 +30557,6 @@
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 setTriggerEarlyEnterTransition(boolean);
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 e8fdcaf..b18eb98 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -21,7 +21,6 @@
import android.transition.Transition;
import android.transition.TransitionManager;
import android.util.ArrayMap;
-import android.util.Pair;
import android.util.SuperNotCalledException;
import android.widget.Toolbar;
import com.android.internal.app.WindowDecorActionBar;
@@ -774,7 +773,6 @@
private Thread mUiThread;
final Handler mHandler = new Handler();
- private ActivityOptions mTransitionActivityOptions;
/** Return the intent that started this activity. */
public Intent getIntent() {
@@ -1400,6 +1398,9 @@
protected void onStop() {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this);
if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
+ if (mWindow != null) {
+ mWindow.restoreViewVisibilityAfterTransitionToCallee();
+ }
getApplication().dispatchActivityStopped(this);
mTranslucentCallback = null;
mCalled = true;
@@ -2300,7 +2301,7 @@
*/
public void onBackPressed() {
if (!mFragments.popBackStackImmediate()) {
- finish();
+ finishWithTransition();
}
}
@@ -3483,8 +3484,7 @@
public void startActivityForResult(Intent intent, int requestCode) {
Bundle options = null;
if (mWindow.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
- final Pair<View, String>[] noSharedElements = null;
- options = ActivityOptions.makeSceneTransitionAnimation(noSharedElements).toBundle();
+ options = ActivityOptions.makeSceneTransitionAnimation().toBundle();
}
startActivityForResult(intent, requestCode, options);
}
@@ -3532,7 +3532,7 @@
mActionBar.captureSharedElements(sharedElementMap);
activityOptions.addSharedElements(sharedElementMap);
}
- options = mWindow.startExitTransition(activityOptions);
+ options = mWindow.startExitTransitionToCallee(options);
}
}
if (mParent == null) {
@@ -4387,6 +4387,23 @@
}
/**
+ * Reverses the Activity Scene entry Transition and triggers the calling Activity
+ * to reverse its exit Transition. When the exit Transition completes,
+ * {@link #finish()} is called. If no entry Transition was used, finish() is called
+ * immediately and the Activity exit Transition is run.
+ * @see android.view.Window#setTriggerEarlySceneTransition(boolean, boolean)
+ * @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.View, String)
+ */
+ public void finishWithTransition() {
+ mWindow.startExitTransitionToCaller(new Runnable() {
+ @Override
+ public void run() {
+ finish();
+ }
+ });
+ }
+
+ /**
* Force finish another activity that you had previously started with
* {@link #startActivityForResult}.
*
@@ -5396,43 +5413,34 @@
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
- mTransitionActivityOptions = null;
- Window.SceneTransitionListener sceneTransitionListener = null;
- if (options != null) {
- ActivityOptions activityOptions = new ActivityOptions(options);
- if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
- mTransitionActivityOptions = activityOptions;
- sceneTransitionListener = new Window.SceneTransitionListener() {
- @Override
- public void nullPendingTransition() {
- overridePendingTransition(0, 0);
- }
-
- @Override
- public void convertFromTranslucent() {
- Activity.this.convertFromTranslucent();
- }
-
- @Override
- public void convertToTranslucent() {
- Activity.this.convertToTranslucent(null);
- }
-
- @Override
- public void sharedElementStart(Transition transition) {
- Activity.this.onCaptureSharedElementStart(transition);
- }
-
- @Override
- public void sharedElementEnd() {
- Activity.this.onCaptureSharedElementEnd();
- }
- };
-
+ Window.SceneTransitionListener sceneTransitionListener
+ = new Window.SceneTransitionListener() {
+ @Override
+ public void nullPendingTransition() {
+ overridePendingTransition(0, 0);
}
- }
- mWindow.setTransitionOptions(mTransitionActivityOptions, sceneTransitionListener);
+ @Override
+ public void convertFromTranslucent() {
+ Activity.this.convertFromTranslucent();
+ }
+
+ @Override
+ public void convertToTranslucent() {
+ Activity.this.convertToTranslucent(null);
+ }
+
+ @Override
+ public void sharedElementStart(Transition transition) {
+ Activity.this.onCaptureSharedElementStart(transition);
+ }
+
+ @Override
+ public void sharedElementEnd() {
+ Activity.this.onCaptureSharedElementEnd();
+ }
+ };
+ mWindow.setTransitionOptions(options, sceneTransitionListener);
}
/** @hide */
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 07247ff..4384580 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -20,8 +20,10 @@
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.transition.Transition;
import android.util.Log;
import android.util.Pair;
@@ -136,6 +138,11 @@
/** @hide */
public static final int ANIM_SCENE_TRANSITION = 5;
+ private static final int MSG_SET_LISTENER = 100;
+ private static final int MSG_HIDE_SHARED_ELEMENTS = 101;
+ private static final int MSG_PREPARE_RESTORE = 102;
+ private static final int MSG_RESTORE = 103;
+
private String mPackageName;
private int mAnimationType = ANIM_NONE;
private int mCustomEnterResId;
@@ -146,7 +153,7 @@
private int mStartWidth;
private int mStartHeight;
private IRemoteCallback mAnimationStartedListener;
- private IRemoteCallback mTransitionCompleteListener;
+ private ResultReceiver mTransitionCompleteListener;
private ArrayList<String> mSharedElementNames;
private ArrayList<String> mLocalElementNames;
@@ -428,8 +435,7 @@
break;
case ANIM_SCENE_TRANSITION:
- mTransitionCompleteListener = IRemoteCallback.Stub.asInterface(
- opts.getBinder(KEY_TRANSITION_COMPLETE_LISTENER));
+ mTransitionCompleteListener = opts.getParcelable(KEY_TRANSITION_COMPLETE_LISTENER);
mSharedElementNames = opts.getStringArrayList(KEY_SHARED_ELEMENT_NAMES);
mLocalElementNames = opts.getStringArrayList(KEY_LOCAL_ELEMENT_NAMES);
break;
@@ -495,7 +501,6 @@
/** @hide */
public void dispatchSceneTransitionStarted(final ActivityTransitionTarget target,
ArrayList<String> sharedElementNames) {
- boolean listenerSent = false;
if (mTransitionCompleteListener != null) {
IRemoteCallback callback = new IRemoteCallback.Stub() {
@Override
@@ -510,27 +515,28 @@
Bundle bundle = new Bundle();
bundle.putBinder(KEY_TRANSITION_TARGET_LISTENER, callback.asBinder());
bundle.putStringArrayList(KEY_SHARED_ELEMENT_NAMES, sharedElementNames);
- try {
- mTransitionCompleteListener.sendResult(bundle);
- listenerSent = true;
- } catch (RemoteException e) {
- Log.w(TAG, "Couldn't retrieve transition notifications", e);
- }
- }
- if (!listenerSent) {
- target.sharedElementTransitionComplete(null);
- target.exitTransitionComplete();
+ mTransitionCompleteListener.send(MSG_SET_LISTENER, bundle);
}
}
/** @hide */
public void dispatchSharedElementsReady() {
if (mTransitionCompleteListener != null) {
- try {
- mTransitionCompleteListener.sendResult(null);
- } catch (RemoteException e) {
- Log.w(TAG, "Couldn't synchronize shared elements", e);
- }
+ mTransitionCompleteListener.send(MSG_HIDE_SHARED_ELEMENTS, null);
+ }
+ }
+
+ /** @hide */
+ public void dispatchPrepareRestore() {
+ if (mTransitionCompleteListener != null) {
+ mTransitionCompleteListener.send(MSG_PREPARE_RESTORE, null);
+ }
+ }
+
+ /** @hide */
+ public void dispatchRestore(Bundle sharedElementsArgs) {
+ if (mTransitionCompleteListener != null) {
+ mTransitionCompleteListener.send(MSG_RESTORE, sharedElementsArgs);
}
}
@@ -658,8 +664,7 @@
case ANIM_SCENE_TRANSITION:
b.putInt(KEY_ANIM_TYPE, mAnimationType);
if (mTransitionCompleteListener != null) {
- b.putBinder(KEY_TRANSITION_COMPLETE_LISTENER,
- mTransitionCompleteListener.asBinder());
+ b.putParcelable(KEY_TRANSITION_COMPLETE_LISTENER, mTransitionCompleteListener);
}
b.putStringArrayList(KEY_SHARED_ELEMENT_NAMES, mSharedElementNames);
b.putStringArrayList(KEY_LOCAL_ELEMENT_NAMES, mLocalElementNames);
@@ -710,13 +715,13 @@
Bundle getSharedElementExitState();
void acceptedSharedElements(ArrayList<String> sharedElementNames);
void hideSharedElements();
+ void restore(Bundle sharedElementState);
+ void prepareForRestore();
}
- private static class ExitTransitionListener extends IRemoteCallback.Stub
+ private static class ExitTransitionListener extends ResultReceiver
implements Transition.TransitionListener {
private boolean mSharedElementNotified;
- private Transition mExitTransition;
- private Transition mSharedElementTransition;
private IRemoteCallback mTransitionCompleteCallback;
private boolean mExitComplete;
private boolean mSharedElementComplete;
@@ -724,25 +729,40 @@
public ExitTransitionListener(Transition exitTransition, Transition sharedElementTransition,
SharedElementSource sharedElementSource) {
+ super(null);
mSharedElementSource = sharedElementSource;
- mExitTransition = exitTransition;
- mExitTransition.addListener(this);
- mSharedElementTransition = sharedElementTransition;
- mSharedElementTransition.addListener(this);
+ exitTransition.addListener(this);
+ sharedElementTransition.addListener(new Transition.TransitionListenerAdapter() {
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ mSharedElementComplete = true;
+ notifySharedElement();
+ transition.removeListener(this);
+ }
+ });
}
@Override
- public void sendResult(Bundle data) throws RemoteException {
- if (data != null) {
- mTransitionCompleteCallback = IRemoteCallback.Stub.asInterface(
- data.getBinder(KEY_TRANSITION_TARGET_LISTENER));
- ArrayList<String> sharedElementNames
- = data.getStringArrayList(KEY_SHARED_ELEMENT_NAMES);
- mSharedElementSource.acceptedSharedElements(sharedElementNames);
- notifySharedElement();
- notifyExit();
- } else {
- mSharedElementSource.hideSharedElements();
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ switch (resultCode) {
+ case MSG_SET_LISTENER:
+ IBinder listener = resultData.getBinder(KEY_TRANSITION_TARGET_LISTENER);
+ mTransitionCompleteCallback = IRemoteCallback.Stub.asInterface(listener);
+ ArrayList<String> sharedElementNames
+ = resultData.getStringArrayList(KEY_SHARED_ELEMENT_NAMES);
+ mSharedElementSource.acceptedSharedElements(sharedElementNames);
+ notifySharedElement();
+ notifyExit();
+ break;
+ case MSG_HIDE_SHARED_ELEMENTS:
+ mSharedElementSource.hideSharedElements();
+ break;
+ case MSG_PREPARE_RESTORE:
+ mSharedElementSource.prepareForRestore();
+ break;
+ case MSG_RESTORE:
+ mSharedElementSource.restore(resultData);
+ break;
}
}
@@ -752,15 +772,9 @@
@Override
public void onTransitionEnd(Transition transition) {
- if (transition == mExitTransition) {
- mExitComplete = true;
- notifyExit();
- mExitTransition.removeListener(this);
- } else {
- mSharedElementComplete = true;
- notifySharedElement();
- mSharedElementTransition.removeListener(this);
- }
+ mExitComplete = true;
+ notifyExit();
+ transition.removeListener(this);
}
@Override
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index d189a34..9881428 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2608,6 +2608,23 @@
"android.intent.action.USER_INFO_CHANGED";
/**
+ * Broadcast sent to the primary user when an associated managed profile is added (the profile
+ * was created and is ready to be used). Carries an extra {@link #EXTRA_USER} that specifies
+ * the UserHandle of the profile that was added. This is only sent to registered receivers,
+ * not manifest receivers.
+ */
+ public static final String ACTION_MANAGED_PROFILE_ADDED =
+ "android.intent.action.MANAGED_PROFILE_ADDED";
+
+ /**
+ * Broadcast sent to the primary user when an associated managed profile is removed. Carries an
+ * extra {@link #EXTRA_USER} that specifies the UserHandle of the profile that was removed. This
+ * is only sent to registered receivers, not manifest receivers.
+ */
+ public static final String ACTION_MANAGED_PROFILE_REMOVED =
+ "android.intent.action.MANAGED_PROFILE_REMOVED";
+
+ /**
* Sent when the user taps on the clock widget in the system's "quick settings" area.
*/
public static final String ACTION_QUICK_CLOCK =
@@ -3301,15 +3318,23 @@
"android.intent.extra.ALLOW_MULTIPLE";
/**
- * The userHandle carried with broadcast intents related to addition, removal and switching of
- * users
- * - {@link #ACTION_USER_ADDED}, {@link #ACTION_USER_REMOVED} and {@link #ACTION_USER_SWITCHED}.
+ * The integer userHandle carried with broadcast intents related to addition, removal and
+ * switching of users and managed profiles - {@link #ACTION_USER_ADDED},
+ * {@link #ACTION_USER_REMOVED} and {@link #ACTION_USER_SWITCHED}.
+ *
* @hide
*/
public static final String EXTRA_USER_HANDLE =
"android.intent.extra.user_handle";
/**
+ * The UserHandle carried with broadcasts intents related to addition and removal of managed
+ * profiles - {@link #ACTION_MANAGED_PROFILE_ADDED} and {@link #ACTION_MANAGED_PROFILE_REMOVED}.
+ */
+ public static final String EXTRA_USER =
+ "android.intent.extra.user";
+
+ /**
* Extra used in the response from a BroadcastReceiver that handles
* {@link #ACTION_GET_RESTRICTION_ENTRIES}. The type of the extra is
* <code>ArrayList<RestrictionEntry></code>.
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 535eee1..638ef22 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -433,7 +433,7 @@
}
if (mLineCount >= mMaximumVisibleLineCount) {
- break;
+ return;
}
}
}
diff --git a/core/java/android/transition/CircularPropagation.java b/core/java/android/transition/CircularPropagation.java
new file mode 100644
index 0000000..18a3d22
--- /dev/null
+++ b/core/java/android/transition/CircularPropagation.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.transition;
+
+import android.graphics.Rect;
+import android.util.FloatMath;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A propagation that varies with the distance to the epicenter of the Transition
+ * or center of the scene if no epicenter exists. When a View is visible in the
+ * start of the transition, Views farther from the epicenter will transition
+ * sooner than Views closer to the epicenter. When a View is not in the start
+ * of the transition or is not visible at the start of the transition, it will
+ * transition sooner when closer to the epicenter and later when farther from
+ * the epicenter. This is the default TransitionPropagation used with
+ * {@link android.transition.Explode}.
+ */
+public class CircularPropagation extends VisibilityPropagation {
+ private static final String TAG = "CircularPropagation";
+
+ private float mPropagationSpeed = 4.0f;
+
+ /**
+ * Sets the speed at which transition propagation happens, relative to the duration of the
+ * Transition. A <code>propagationSpeed</code> of 1 means that a View centered farthest from
+ * the epicenter and View centered at the epicenter will have a difference
+ * in start delay of approximately the duration of the Transition. A speed of 2 means the
+ * start delay difference will be approximately half of the duration of the transition. A
+ * value of 0 is illegal, but negative values will invert the propagation.
+ *
+ * @param propagationSpeed The speed at which propagation occurs, relative to the duration
+ * of the transition. A speed of 4 means it works 4 times as fast
+ * as the duration of the transition. May not be 0.
+ */
+ public void setPropagationSpeed(float propagationSpeed) {
+ if (propagationSpeed == 0) {
+ throw new IllegalArgumentException("propagationSpeed may not be 0");
+ }
+ mPropagationSpeed = propagationSpeed;
+ }
+
+ @Override
+ public long getStartDelay(ViewGroup sceneRoot, Transition transition,
+ TransitionValues startValues, TransitionValues endValues) {
+ if (startValues == null && endValues == null) {
+ return 0;
+ }
+ int directionMultiplier = 1;
+ TransitionValues positionValues;
+ if (endValues == null || getViewVisibility(startValues) == View.VISIBLE) {
+ positionValues = startValues;
+ directionMultiplier = -1;
+ } else {
+ positionValues = endValues;
+ }
+
+ int viewCenterX = getViewX(positionValues);
+ int viewCenterY = getViewY(positionValues);
+
+ Rect epicenter = transition.getEpicenter();
+ int epicenterX;
+ int epicenterY;
+ if (epicenter != null) {
+ epicenterX = epicenter.centerX();
+ epicenterY = epicenter.centerY();
+ } else {
+ int[] loc = new int[2];
+ sceneRoot.getLocationOnScreen(loc);
+ epicenterX = Math.round(loc[0] + (sceneRoot.getWidth() / 2)
+ + sceneRoot.getTranslationX());
+ epicenterY = Math.round(loc[1] + (sceneRoot.getHeight() / 2)
+ + sceneRoot.getTranslationY());
+ }
+ float distance = distance(viewCenterX, viewCenterY, epicenterX, epicenterY);
+ float maxDistance = distance(0, 0, sceneRoot.getWidth(), sceneRoot.getHeight());
+ float distanceFraction = distance/maxDistance;
+
+ return Math.round(transition.getDuration() * directionMultiplier / mPropagationSpeed
+ * distanceFraction);
+ }
+
+ private static float distance(float x1, float y1, float x2, float y2) {
+ float x = x2 - x1;
+ float y = y2 - y1;
+ return FloatMath.sqrt((x * x) + (y * y));
+ }
+}
diff --git a/core/java/android/transition/Explode.java b/core/java/android/transition/Explode.java
new file mode 100644
index 0000000..fae527c
--- /dev/null
+++ b/core/java/android/transition/Explode.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.util.FloatMath;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+
+/**
+ * This transition tracks changes to the visibility of target views in the
+ * start and end scenes and moves views in or out from the edges of the
+ * scene. Visibility is determined by both the
+ * {@link View#setVisibility(int)} state of the view as well as whether it
+ * is parented in the current view hierarchy. Disappearing Views are
+ * limited as described in {@link Visibility#onDisappear(android.view.ViewGroup,
+ * TransitionValues, int, TransitionValues, int)}.
+ * <p>Views move away from the focal View or the center of the Scene if
+ * no epicenter was provided.</p>
+ */
+public class Explode extends Visibility {
+ private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
+ private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
+ private static final String TAG = "Explode";
+
+ private static final String PROPNAME_SCREEN_BOUNDS = "android:out:screenBounds";
+
+ private int[] mTempLoc = new int[2];
+
+ public Explode() {
+ setPropagation(new CircularPropagation());
+ }
+
+ private void captureValues(TransitionValues transitionValues) {
+ View view = transitionValues.view;
+ view.getLocationOnScreen(mTempLoc);
+ int left = mTempLoc[0] + Math.round(view.getTranslationX());
+ int top = mTempLoc[1] + Math.round(view.getTranslationY());
+ int right = left + view.getWidth();
+ int bottom = top + view.getHeight();
+ transitionValues.values.put(PROPNAME_SCREEN_BOUNDS, new Rect(left, top, right, bottom));
+ }
+
+ @Override
+ public void captureStartValues(TransitionValues transitionValues) {
+ super.captureStartValues(transitionValues);
+ captureValues(transitionValues);
+ }
+
+ @Override
+ public void captureEndValues(TransitionValues transitionValues) {
+ super.captureEndValues(transitionValues);
+ captureValues(transitionValues);
+ }
+
+ private Animator createAnimation(final View view, float startX, float startY, float endX,
+ float endY, float terminalX, float terminalY, TimeInterpolator interpolator) {
+ view.setTranslationX(startX);
+ view.setTranslationY(startY);
+ if (startY == endY && startX == endX) {
+ return null;
+ }
+ Path path = new Path();
+ path.moveTo(startX, startY);
+ path.lineTo(endX, endY);
+ ObjectAnimator pathAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
+ View.TRANSLATION_Y, path);
+ pathAnimator.setInterpolator(interpolator);
+ OutAnimatorListener listener = new OutAnimatorListener(view, terminalX, terminalY,
+ endX, endY);
+ pathAnimator.addListener(listener);
+ pathAnimator.addPauseListener(listener);
+
+ return pathAnimator;
+ }
+
+ @Override
+ public Animator onAppear(ViewGroup sceneRoot, View view,
+ TransitionValues startValues, TransitionValues endValues) {
+ if (endValues == null) {
+ return null;
+ }
+ Rect bounds = (Rect) endValues.values.get(PROPNAME_SCREEN_BOUNDS);
+ calculateOut(sceneRoot, bounds, mTempLoc);
+
+ final float endX = view.getTranslationX();
+ final float startX = endX + mTempLoc[0];
+ final float endY = view.getTranslationY();
+ final float startY = endY + mTempLoc[1];
+
+ return createAnimation(view, startX, startY, endX, endY, endX, endY, sDecelerate);
+ }
+
+ @Override
+ public Animator onDisappear(ViewGroup sceneRoot, View view,
+ TransitionValues startValues, TransitionValues endValues) {
+ Rect bounds = (Rect) startValues.values.get(PROPNAME_SCREEN_BOUNDS);
+ calculateOut(sceneRoot, bounds, mTempLoc);
+
+ final float startX = view.getTranslationX();
+ final float endX = startX + mTempLoc[0];
+ final float startY = view.getTranslationY();
+ final float endY = startY + mTempLoc[1];
+
+ return createAnimation(view, startX, startY, endX, endY, startX, startY,
+ sAccelerate);
+ }
+
+ private void calculateOut(View sceneRoot, Rect bounds, int[] outVector) {
+ sceneRoot.getLocationOnScreen(mTempLoc);
+ int sceneRootX = mTempLoc[0];
+ int sceneRootY = mTempLoc[1];
+ int focalX;
+ int focalY;
+
+ Rect epicenter = getEpicenter();
+ if (epicenter == null) {
+ focalX = sceneRootX + (sceneRoot.getWidth() / 2)
+ + Math.round(sceneRoot.getTranslationX());
+ focalY = sceneRootY + (sceneRoot.getHeight() / 2)
+ + Math.round(sceneRoot.getTranslationY());
+ } else {
+ focalX = epicenter.centerX();
+ focalY = epicenter.centerY();
+ }
+
+ int centerX = bounds.centerX();
+ int centerY = bounds.centerY();
+ float xVector = centerX - focalX;
+ float yVector = centerY - focalY;
+
+ if (xVector == 0 && yVector == 0) {
+ // Random direction when View is centered on focal View.
+ xVector = (float)(Math.random() * 2) - 1;
+ yVector = (float)(Math.random() * 2) - 1;
+ }
+ float vectorSize = calculateDistance(xVector, yVector);
+ xVector /= vectorSize;
+ yVector /= vectorSize;
+
+ float maxDistance =
+ calculateMaxDistance(sceneRoot, focalX - sceneRootX, focalY - sceneRootY);
+
+ outVector[0] = Math.round(maxDistance * xVector);
+ outVector[1] = Math.round(maxDistance * yVector);
+ }
+
+ private static float calculateMaxDistance(View sceneRoot, int focalX, int focalY) {
+ int maxX = Math.max(focalX, sceneRoot.getWidth() - focalX);
+ int maxY = Math.max(focalY, sceneRoot.getHeight() - focalY);
+ return calculateDistance(maxX, maxY);
+ }
+
+ private static float calculateDistance(float x, float y) {
+ return FloatMath.sqrt((x * x) + (y * y));
+ }
+
+ private static class OutAnimatorListener extends AnimatorListenerAdapter {
+ private final View mView;
+ private boolean mCanceled = false;
+ private float mPausedX;
+ private float mPausedY;
+ private final float mTerminalX;
+ private final float mTerminalY;
+ private final float mEndX;
+ private final float mEndY;
+
+ public OutAnimatorListener(View view, float terminalX, float terminalY,
+ float endX, float endY) {
+ mView = view;
+ mTerminalX = terminalX;
+ mTerminalY = terminalY;
+ mEndX = endX;
+ mEndY = endY;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ mView.setTranslationX(mTerminalX);
+ mView.setTranslationY(mTerminalY);
+ mCanceled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ if (!mCanceled) {
+ mView.setTranslationX(mTerminalX);
+ mView.setTranslationY(mTerminalY);
+ }
+ }
+
+ @Override
+ public void onAnimationPause(Animator animator) {
+ mPausedX = mView.getTranslationX();
+ mPausedY = mView.getTranslationY();
+ mView.setTranslationY(mEndX);
+ mView.setTranslationY(mEndY);
+ }
+
+ @Override
+ public void onAnimationResume(Animator animator) {
+ mView.setTranslationX(mPausedX);
+ mView.setTranslationY(mPausedY);
+ }
+ }
+}
diff --git a/core/java/android/transition/Fade.java b/core/java/android/transition/Fade.java
index 8edb1ff..08e27d3 100644
--- a/core/java/android/transition/Fade.java
+++ b/core/java/android/transition/Fade.java
@@ -59,8 +59,6 @@
private static boolean DBG = Transition.DBG && false;
private static final String LOG_TAG = "Fade";
- private static final String PROPNAME_SCREEN_X = "android:fade:screenX";
- private static final String PROPNAME_SCREEN_Y = "android:fade:screenY";
/**
* Fading mode used in {@link #Fade(int)} to make the transition
@@ -98,245 +96,81 @@
/**
* Utility method to handle creating and running the Animator.
*/
- private Animator createAnimation(View view, float startAlpha, float endAlpha,
- AnimatorListenerAdapter listener) {
+ private Animator createAnimation(View view, float startAlpha, float endAlpha) {
if (startAlpha == endAlpha) {
- // run listener if we're noop'ing the animation, to get the end-state results now
- if (listener != null) {
- listener.onAnimationEnd(null);
- }
return null;
}
- final ObjectAnimator anim = ObjectAnimator.ofFloat(view, "transitionAlpha", startAlpha,
- endAlpha);
+ view.setTransitionAlpha(startAlpha);
+ final ObjectAnimator anim = ObjectAnimator.ofFloat(view, "transitionAlpha", endAlpha);
if (DBG) {
Log.d(LOG_TAG, "Created animator " + anim);
}
- if (listener != null) {
- anim.addListener(listener);
- anim.addPauseListener(listener);
- }
+ FadeAnimatorListener listener = new FadeAnimatorListener(view, endAlpha);
+ anim.addListener(listener);
+ anim.addPauseListener(listener);
return anim;
}
- private void captureValues(TransitionValues transitionValues) {
- int[] loc = new int[2];
- transitionValues.view.getLocationOnScreen(loc);
- transitionValues.values.put(PROPNAME_SCREEN_X, loc[0]);
- transitionValues.values.put(PROPNAME_SCREEN_Y, loc[1]);
- }
-
@Override
- public void captureStartValues(TransitionValues transitionValues) {
- super.captureStartValues(transitionValues);
- captureValues(transitionValues);
- }
-
- @Override
- public Animator onAppear(ViewGroup sceneRoot,
- TransitionValues startValues, int startVisibility,
- TransitionValues endValues, int endVisibility) {
+ public Animator onAppear(ViewGroup sceneRoot, View view,
+ TransitionValues startValues,
+ TransitionValues endValues) {
if ((mFadingMode & IN) != IN || endValues == null) {
return null;
}
- final View endView = endValues.view;
if (DBG) {
View startView = (startValues != null) ? startValues.view : null;
Log.d(LOG_TAG, "Fade.onAppear: startView, startVis, endView, endVis = " +
- startView + ", " + startVisibility + ", " + endView + ", " + endVisibility);
+ startView + ", " + view);
}
- endView.setTransitionAlpha(0);
- TransitionListener transitionListener = new TransitionListenerAdapter() {
- boolean mCanceled = false;
- float mPausedAlpha;
-
- @Override
- public void onTransitionCancel(Transition transition) {
- endView.setTransitionAlpha(1);
- mCanceled = true;
- }
-
- @Override
- public void onTransitionEnd(Transition transition) {
- if (!mCanceled) {
- endView.setTransitionAlpha(1);
- }
- }
-
- @Override
- public void onTransitionPause(Transition transition) {
- mPausedAlpha = endView.getTransitionAlpha();
- endView.setTransitionAlpha(1);
- }
-
- @Override
- public void onTransitionResume(Transition transition) {
- endView.setTransitionAlpha(mPausedAlpha);
- }
- };
- addListener(transitionListener);
- return createAnimation(endView, 0, 1, null);
+ return createAnimation(view, 0, 1);
}
@Override
- public Animator onDisappear(ViewGroup sceneRoot,
- TransitionValues startValues, int startVisibility,
- TransitionValues endValues, int endVisibility) {
+ public Animator onDisappear(ViewGroup sceneRoot, final View view, TransitionValues startValues,
+ TransitionValues endValues) {
if ((mFadingMode & OUT) != OUT) {
return null;
}
- View view = null;
- View startView = (startValues != null) ? startValues.view : null;
- View endView = (endValues != null) ? endValues.view : null;
- if (DBG) {
- Log.d(LOG_TAG, "Fade.onDisappear: startView, startVis, endView, endVis = " +
- startView + ", " + startVisibility + ", " + endView + ", " + endVisibility);
- }
- View overlayView = null;
- View viewToKeep = null;
- if (endView == null || endView.getParent() == null) {
- if (endView != null) {
- // endView was removed from its parent - add it to the overlay
- view = overlayView = endView;
- } else if (startView != null) {
- // endView does not exist. Use startView only under certain
- // conditions, because placing a view in an overlay necessitates
- // it being removed from its current parent
- if (startView.getParent() == null) {
- // no parent - safe to use
- view = overlayView = startView;
- } else if (startView.getParent() instanceof View &&
- startView.getParent().getParent() == null) {
- View startParent = (View) startView.getParent();
- int id = startParent.getId();
- if (id != View.NO_ID && sceneRoot.findViewById(id) != null && mCanRemoveViews) {
- // no parent, but its parent is unparented but the parent
- // hierarchy has been replaced by a new hierarchy with the same id
- // and it is safe to un-parent startView
- view = overlayView = startView;
- }
- }
- }
- } else {
- // visibility change
- if (endVisibility == View.INVISIBLE) {
- view = endView;
- viewToKeep = view;
- } else {
- // Becoming GONE
- if (startView == endView) {
- view = endView;
- viewToKeep = view;
- } else {
- view = startView;
- overlayView = view;
- }
- }
- }
- final int finalVisibility = endVisibility;
- // TODO: add automatic facility to Visibility superclass for keeping views around
- if (overlayView != null) {
- // TODO: Need to do this for general case of adding to overlay
- int screenX = (Integer) startValues.values.get(PROPNAME_SCREEN_X);
- int screenY = (Integer) startValues.values.get(PROPNAME_SCREEN_Y);
- int[] loc = new int[2];
- sceneRoot.getLocationOnScreen(loc);
- overlayView.offsetLeftAndRight((screenX - loc[0]) - overlayView.getLeft());
- overlayView.offsetTopAndBottom((screenY - loc[1]) - overlayView.getTop());
- sceneRoot.getOverlay().add(overlayView);
- // TODO: add automatic facility to Visibility superclass for keeping views around
- final float startAlpha = 1;
- float endAlpha = 0;
- final View finalView = view;
- final View finalOverlayView = overlayView;
- final View finalViewToKeep = viewToKeep;
- final ViewGroup finalSceneRoot = sceneRoot;
- final AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- finalView.setTransitionAlpha(startAlpha);
- // TODO: restore view offset from overlay repositioning
- if (finalViewToKeep != null) {
- finalViewToKeep.setVisibility(finalVisibility);
- }
- if (finalOverlayView != null) {
- finalSceneRoot.getOverlay().remove(finalOverlayView);
- }
- }
- @Override
- public void onAnimationPause(Animator animation) {
- if (finalOverlayView != null) {
- finalSceneRoot.getOverlay().remove(finalOverlayView);
- }
- }
-
- @Override
- public void onAnimationResume(Animator animation) {
- if (finalOverlayView != null) {
- finalSceneRoot.getOverlay().add(finalOverlayView);
- }
- }
- };
- return createAnimation(view, startAlpha, endAlpha, endListener);
- }
- if (viewToKeep != null) {
- // TODO: find a different way to do this, like just changing the view to be
- // VISIBLE for the duration of the transition
- viewToKeep.setVisibility((View.VISIBLE));
- // TODO: add automatic facility to Visibility superclass for keeping views around
- final float startAlpha = 1;
- float endAlpha = 0;
- final View finalView = view;
- final View finalOverlayView = overlayView;
- final View finalViewToKeep = viewToKeep;
- final ViewGroup finalSceneRoot = sceneRoot;
- final AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() {
- boolean mCanceled = false;
- float mPausedAlpha = -1;
-
- @Override
- public void onAnimationPause(Animator animation) {
- if (finalViewToKeep != null && !mCanceled) {
- finalViewToKeep.setVisibility(finalVisibility);
- }
- mPausedAlpha = finalView.getTransitionAlpha();
- finalView.setTransitionAlpha(startAlpha);
- }
-
- @Override
- public void onAnimationResume(Animator animation) {
- if (finalViewToKeep != null && !mCanceled) {
- finalViewToKeep.setVisibility(View.VISIBLE);
- }
- finalView.setTransitionAlpha(mPausedAlpha);
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mCanceled = true;
- if (mPausedAlpha >= 0) {
- finalView.setTransitionAlpha(mPausedAlpha);
- }
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (!mCanceled) {
- finalView.setTransitionAlpha(startAlpha);
- }
- // TODO: restore view offset from overlay repositioning
- if (finalViewToKeep != null && !mCanceled) {
- finalViewToKeep.setVisibility(finalVisibility);
- }
- if (finalOverlayView != null) {
- finalSceneRoot.getOverlay().remove(finalOverlayView);
- }
- }
- };
- return createAnimation(view, startAlpha, endAlpha, endListener);
- }
- return null;
+ return createAnimation(view, 1, 0);
}
-}
\ No newline at end of file
+ private static class FadeAnimatorListener extends AnimatorListenerAdapter {
+ private final View mView;
+ private final float mEndAlpha;
+ private boolean mCanceled = false;
+ private float mPausedAlpha;
+
+ public FadeAnimatorListener(View view, float endAlpha) {
+ mView = view;
+ mEndAlpha = endAlpha;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ mCanceled = true;
+ if (mPausedAlpha >= 0) {
+ mView.setTransitionAlpha(mPausedAlpha);
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ if (!mCanceled) {
+ mView.setTransitionAlpha(mEndAlpha);
+ }
+ }
+
+ @Override
+ public void onAnimationPause(Animator animator) {
+ mPausedAlpha = mView.getTransitionAlpha();
+ mView.setTransitionAlpha(mEndAlpha);
+ }
+
+ @Override
+ public void onAnimationResume(Animator animator) {
+ mView.setTransitionAlpha(mPausedAlpha);
+ }
+ }
+}
diff --git a/core/java/android/transition/MatrixClippedDrawable.java b/core/java/android/transition/MatrixClippedDrawable.java
new file mode 100644
index 0000000..ebaad59
--- /dev/null
+++ b/core/java/android/transition/MatrixClippedDrawable.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.transition;
+
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.Property;
+
+/**
+ * Used in MoveImage to mock an ImageView as a Drawable to be scaled in the scene root Overlay.
+ * @hide
+ */
+class MatrixClippedDrawable extends Drawable implements Drawable.Callback {
+ private static final String TAG = "MatrixClippedDrawable";
+
+ private ClippedMatrixState mClippedMatrixState;
+
+ public static final Property<MatrixClippedDrawable, Rect> CLIP_PROPERTY
+ = new Property<MatrixClippedDrawable, Rect>(Rect.class, "clipRect") {
+
+ @Override
+ public Rect get(MatrixClippedDrawable object) {
+ return object.getClipRect();
+ }
+
+ @Override
+ public void set(MatrixClippedDrawable object, Rect value) {
+ object.setClipRect(value);
+ }
+ };
+
+ public static final Property<MatrixClippedDrawable, Matrix> MATRIX_PROPERTY
+ = new Property<MatrixClippedDrawable, Matrix>(Matrix.class, "matrix") {
+ @Override
+ public void set(MatrixClippedDrawable object, Matrix value) {
+ object.setMatrix(value);
+ }
+
+ @Override
+ public Matrix get(MatrixClippedDrawable object) {
+ return object.getMatrix();
+ }
+ };
+
+ public MatrixClippedDrawable(Drawable drawable) {
+ this(null, null);
+
+ mClippedMatrixState.mDrawable = drawable;
+
+ if (drawable != null) {
+ drawable.setCallback(this);
+ }
+ }
+
+ public void setMatrix(Matrix matrix) {
+ if (matrix == null) {
+ mClippedMatrixState.mMatrix = null;
+ } else {
+ if (mClippedMatrixState.mMatrix == null) {
+ mClippedMatrixState.mMatrix = new Matrix();
+ }
+ mClippedMatrixState.mMatrix.set(matrix);
+ }
+ invalidateSelf();
+ }
+
+ public Matrix getMatrix() {
+ return mClippedMatrixState.mMatrix;
+ }
+
+ public Rect getClipRect() {
+ return mClippedMatrixState.mClipRect;
+ }
+
+ public void setClipRect(Rect clipRect) {
+ if (clipRect == null) {
+ if (mClippedMatrixState.mClipRect != null) {
+ mClippedMatrixState.mClipRect = null;
+ invalidateSelf();
+ }
+ } else {
+ if (mClippedMatrixState.mClipRect == null) {
+ mClippedMatrixState.mClipRect = new Rect(clipRect);
+ } else {
+ mClippedMatrixState.mClipRect.set(clipRect);
+ }
+ invalidateSelf();
+ }
+ }
+
+ // overrides from Drawable.Callback
+
+ public void invalidateDrawable(Drawable who) {
+ final Drawable.Callback callback = getCallback();
+ if (callback != null) {
+ callback.invalidateDrawable(this);
+ }
+ }
+
+ public void scheduleDrawable(Drawable who, Runnable what, long when) {
+ final Drawable.Callback callback = getCallback();
+ if (callback != null) {
+ callback.scheduleDrawable(this, what, when);
+ }
+ }
+
+ public void unscheduleDrawable(Drawable who, Runnable what) {
+ final Drawable.Callback callback = getCallback();
+ if (callback != null) {
+ callback.unscheduleDrawable(this, what);
+ }
+ }
+
+ // overrides from Drawable
+
+ @Override
+ public int getChangingConfigurations() {
+ return super.getChangingConfigurations()
+ | mClippedMatrixState.mChangingConfigurations
+ | mClippedMatrixState.mDrawable.getChangingConfigurations();
+ }
+
+ @Override
+ public boolean getPadding(Rect padding) {
+ // XXX need to adjust padding!
+ return mClippedMatrixState.mDrawable.getPadding(padding);
+ }
+
+ @Override
+ public boolean setVisible(boolean visible, boolean restart) {
+ mClippedMatrixState.mDrawable.setVisible(visible, restart);
+ return super.setVisible(visible, restart);
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ mClippedMatrixState.mDrawable.setAlpha(alpha);
+ }
+
+ @Override
+ public int getAlpha() {
+ return mClippedMatrixState.mDrawable.getAlpha();
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ mClippedMatrixState.mDrawable.setColorFilter(cf);
+ }
+
+ @Override
+ public int getOpacity() {
+ return mClippedMatrixState.mDrawable.getOpacity();
+ }
+
+ @Override
+ public boolean isStateful() {
+ return mClippedMatrixState.mDrawable.isStateful();
+ }
+
+ @Override
+ protected boolean onStateChange(int[] state) {
+ return mClippedMatrixState.mDrawable.setState(state);
+ }
+
+ @Override
+ protected boolean onLevelChange(int level) {
+ mClippedMatrixState.mDrawable.setLevel(level);
+ invalidateSelf();
+ return true;
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.setBounds(bounds);
+ if (mClippedMatrixState.mMatrix == null) {
+ mClippedMatrixState.mDrawable.setBounds(bounds);
+ } else {
+ int drawableWidth = mClippedMatrixState.mDrawable.getIntrinsicWidth();
+ int drawableHeight = mClippedMatrixState.mDrawable.getIntrinsicHeight();
+ mClippedMatrixState.mDrawable.setBounds(bounds.left, bounds.top,
+ drawableWidth + bounds.left, drawableHeight + bounds.top);
+ }
+ invalidateSelf();
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ Rect bounds = getBounds();
+ int left = bounds.left;
+ int top = bounds.top;
+ int saveCount = canvas.getSaveCount();
+ canvas.save();
+ if (mClippedMatrixState.mClipRect != null) {
+ canvas.clipRect(mClippedMatrixState.mClipRect);
+ } else {
+ canvas.clipRect(bounds);
+ }
+
+ if (mClippedMatrixState != null && !mClippedMatrixState.mMatrix.isIdentity()) {
+ canvas.translate(left, top);
+ canvas.concat(mClippedMatrixState.mMatrix);
+ canvas.translate(-left, -top);
+ }
+ mClippedMatrixState.mDrawable.draw(canvas);
+ canvas.restoreToCount(saveCount);
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mClippedMatrixState.mDrawable.getIntrinsicWidth();
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mClippedMatrixState.mDrawable.getIntrinsicHeight();
+ }
+
+ @Override
+ public Drawable.ConstantState getConstantState() {
+ if (mClippedMatrixState.canConstantState()) {
+ mClippedMatrixState.mChangingConfigurations = getChangingConfigurations();
+ return mClippedMatrixState;
+ }
+ return null;
+ }
+
+ final static class ClippedMatrixState extends Drawable.ConstantState {
+ Drawable mDrawable;
+ Matrix mMatrix;
+ Rect mClipRect;
+
+ private boolean mCheckedConstantState;
+ private boolean mCanConstantState;
+ int mChangingConfigurations;
+
+ ClippedMatrixState(ClippedMatrixState orig, MatrixClippedDrawable owner, Resources res) {
+ if (orig != null) {
+ if (res != null) {
+ mDrawable = orig.mDrawable.getConstantState().newDrawable(res);
+ } else {
+ mDrawable = orig.mDrawable.getConstantState().newDrawable();
+ }
+ mDrawable.setCallback(owner);
+ mCheckedConstantState = mCanConstantState = true;
+ if (orig.mMatrix != null) {
+ mMatrix = new Matrix(orig.mMatrix);
+ }
+ if (orig.mClipRect != null) {
+ mClipRect = new Rect(orig.mClipRect);
+ }
+ }
+ }
+
+ @Override
+ public Drawable newDrawable() {
+ return new MatrixClippedDrawable(this, null);
+ }
+
+ @Override
+ public Drawable newDrawable(Resources res) {
+ return new MatrixClippedDrawable(this, res);
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return mChangingConfigurations;
+ }
+
+ boolean canConstantState() {
+ if (!mCheckedConstantState) {
+ mCanConstantState = mDrawable.getConstantState() != null;
+ mCheckedConstantState = true;
+ }
+
+ return mCanConstantState;
+ }
+ }
+
+ private MatrixClippedDrawable(ClippedMatrixState state, Resources res) {
+ mClippedMatrixState = new ClippedMatrixState(state, this, res);
+ }
+
+}
diff --git a/core/java/android/transition/MoveImage.java b/core/java/android/transition/MoveImage.java
new file mode 100644
index 0000000..d68e971
--- /dev/null
+++ b/core/java/android/transition/MoveImage.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.RectEvaluator;
+import android.animation.TypeEvaluator;
+import android.animation.ValueAnimator;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.util.FloatMath;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroupOverlay;
+import android.view.ViewParent;
+import android.widget.ImageView;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+/**
+ * Transitions ImageViews, including size, scaleType, and matrix. The ImageView drawable
+ * must remain the same between both start and end states, but the
+ * {@link ImageView#setScaleType(android.widget.ImageView.ScaleType)} may
+ * differ.
+ */
+public class MoveImage extends Transition {
+ private static final String TAG = "MoveImage";
+ private static final String PROPNAME_MATRIX = "android:moveImage:matrix";
+ private static final String PROPNAME_BOUNDS = "android:moveImage:bounds";
+ private static final String PROPNAME_CLIP = "android:moveImage:clip";
+ private static final String PROPNAME_DRAWABLE = "android:moveImage:drawable";
+
+ private int[] mTempLoc = new int[2];
+
+ private static final String[] sTransitionProperties = {
+ PROPNAME_MATRIX,
+ PROPNAME_BOUNDS,
+ PROPNAME_CLIP,
+ PROPNAME_DRAWABLE,
+ };
+
+ private void captureValues(TransitionValues transitionValues) {
+ View view = transitionValues.view;
+ if (!(view instanceof ImageView) || view.getVisibility() != View.VISIBLE) {
+ return;
+ }
+ Map<String, Object> values = transitionValues.values;
+
+ ViewGroup parent = (ViewGroup) view.getParent();
+ parent.getLocationInWindow(mTempLoc);
+ int paddingLeft = view.getPaddingLeft();
+ int paddingTop = view.getPaddingTop();
+ int paddingRight = view.getPaddingRight();
+ int paddingBottom = view.getPaddingBottom();
+ int left = mTempLoc[0] + paddingLeft + view.getLeft() + Math.round(view.getTranslationX());
+ int top = mTempLoc[1] + paddingTop + view.getTop() + Math.round(view.getTranslationY());
+ int right = left + view.getWidth() - paddingRight - paddingLeft;
+ int bottom = top + view.getHeight() - paddingTop - paddingBottom;
+
+ Rect bounds = new Rect(left, top, right, bottom);
+ values.put(PROPNAME_BOUNDS, bounds);
+ ImageView imageView = (ImageView) view;
+ Matrix matrix = getMatrix(imageView);
+ values.put(PROPNAME_MATRIX, matrix);
+ values.put(PROPNAME_CLIP, findClip(imageView));
+ values.put(PROPNAME_DRAWABLE, imageView.getDrawable());
+ }
+
+ @Override
+ public void captureStartValues(TransitionValues transitionValues) {
+ captureValues(transitionValues);
+ }
+
+ @Override
+ public void captureEndValues(TransitionValues transitionValues) {
+ captureValues(transitionValues);
+ }
+
+ @Override
+ public String[] getTransitionProperties() {
+ return sTransitionProperties;
+ }
+
+ /**
+ * Creates an Animator for ImageViews moving, changing dimensions, and/or changing
+ * {@link android.widget.ImageView.ScaleType}.
+ * @param sceneRoot The root of the transition hierarchy.
+ * @param startValues The values for a specific target in the start scene.
+ * @param endValues The values for the target in the end scene.
+ * @return An Animator to move an ImageView or null if the View is not an ImageView,
+ * the Drawable changed, the View is not VISIBLE, or there was no change.
+ */
+ @Override
+ public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
+ TransitionValues endValues) {
+ if (startValues == null || endValues == null
+ || startValues.values.get(PROPNAME_BOUNDS) == null
+ || endValues.values.get(PROPNAME_BOUNDS) == null
+ || startValues.values.get(PROPNAME_DRAWABLE)
+ != endValues.values.get(PROPNAME_DRAWABLE)) {
+ return null;
+ }
+ ArrayList<PropertyValuesHolder> changes = new ArrayList<PropertyValuesHolder>();
+
+ Matrix startMatrix = (Matrix) startValues.values.get(PROPNAME_MATRIX);
+ Matrix endMatrix = (Matrix) endValues.values.get(PROPNAME_MATRIX);
+
+ if (!startMatrix.equals(endMatrix)) {
+ changes.add(PropertyValuesHolder.ofObject(MatrixClippedDrawable.MATRIX_PROPERTY,
+ new MatrixEvaluator(), startMatrix, endMatrix));
+ }
+
+ sceneRoot.getLocationInWindow(mTempLoc);
+ int rootX = mTempLoc[0];
+ int rootY = mTempLoc[1];
+ final ImageView imageView = (ImageView) endValues.view;
+
+ Drawable drawable = imageView.getDrawable();
+
+ Rect startBounds = new Rect((Rect) startValues.values.get(PROPNAME_BOUNDS));
+ Rect endBounds = new Rect((Rect) endValues.values.get(PROPNAME_BOUNDS));
+ startBounds.offset(-rootX, -rootY);
+ endBounds.offset(-rootX, -rootY);
+
+ if (!startBounds.equals(endBounds)) {
+ changes.add(PropertyValuesHolder.ofObject("bounds", new RectEvaluator(new Rect()),
+ startBounds, endBounds));
+ }
+
+ Rect startClip = (Rect) startValues.values.get(PROPNAME_CLIP);
+ Rect endClip = (Rect) endValues.values.get(PROPNAME_CLIP);
+ if (startClip != null || endClip != null) {
+ startClip = nonNullClip(startClip, sceneRoot, rootX, rootY);
+ endClip = nonNullClip(endClip, sceneRoot, rootX, rootY);
+
+ expandClip(startBounds, startMatrix, startClip, endClip);
+ expandClip(endBounds, endMatrix, endClip, startClip);
+ boolean clipped = !startClip.contains(startBounds) || !endClip.contains(endBounds);
+ if (!clipped) {
+ startClip = null;
+ } else if (!startClip.equals(endClip)) {
+ changes.add(PropertyValuesHolder.ofObject(MatrixClippedDrawable.CLIP_PROPERTY,
+ new RectEvaluator(), startClip, endClip));
+ }
+ }
+
+ if (changes.isEmpty()) {
+ return null;
+ }
+
+ drawable = drawable.getConstantState().newDrawable();
+ final MatrixClippedDrawable matrixClippedDrawable = new MatrixClippedDrawable(drawable);
+ matrixClippedDrawable.setMatrix(startMatrix);
+ matrixClippedDrawable.setBounds(startBounds);
+ matrixClippedDrawable.setClipRect(startClip);
+
+ imageView.setVisibility(View.INVISIBLE);
+ final ViewGroupOverlay overlay = sceneRoot.getOverlay();
+ overlay.add(matrixClippedDrawable);
+ ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(matrixClippedDrawable,
+ changes.toArray(new PropertyValuesHolder[changes.size()]));
+
+ AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ imageView.setVisibility(View.VISIBLE);
+ overlay.remove(matrixClippedDrawable);
+ }
+
+ @Override
+ public void onAnimationPause(Animator animation) {
+ imageView.setVisibility(View.VISIBLE);
+ overlay.remove(matrixClippedDrawable);
+ }
+
+ @Override
+ public void onAnimationResume(Animator animation) {
+ imageView.setVisibility(View.INVISIBLE);
+ overlay.add(matrixClippedDrawable);
+ }
+ };
+
+ animator.addListener(listener);
+ animator.addPauseListener(listener);
+
+ return animator;
+ }
+
+ private static Rect nonNullClip(Rect clip, ViewGroup sceneRoot, int rootX, int rootY) {
+ if (clip != null) {
+ clip = new Rect(clip);
+ clip.offset(-rootX, -rootY);
+ } else {
+ clip = new Rect(0, 0, sceneRoot.getWidth(), sceneRoot.getHeight());
+ }
+ return clip;
+ }
+
+ private static void expandClip(Rect bounds, Matrix matrix, Rect clip, Rect otherClip) {
+ RectF boundsF = new RectF(bounds);
+ matrix.mapRect(boundsF);
+ clip.left = expandMinDimension(boundsF.left, clip.left, otherClip.left);
+ clip.top = expandMinDimension(boundsF.top, clip.top, otherClip.top);
+ clip.right = expandMaxDimension(boundsF.right, clip.right, otherClip.right);
+ clip.bottom = expandMaxDimension(boundsF.bottom, clip.bottom, otherClip.bottom);
+ }
+
+ private static int expandMinDimension(float boundsDimension, int clipDimension,
+ int otherClipDimension) {
+ if (clipDimension > boundsDimension) {
+ // Already clipped in that dimension, return the clipped value
+ return clipDimension;
+ }
+ return Math.min(clipDimension, otherClipDimension);
+ }
+
+ private static int expandMaxDimension(float boundsDimension, int clipDimension,
+ int otherClipDimension) {
+ return -expandMinDimension(-boundsDimension, -clipDimension, -otherClipDimension);
+ }
+
+ private static Matrix getMatrix(ImageView imageView) {
+ Drawable drawable = imageView.getDrawable();
+ int drawableWidth = drawable.getIntrinsicWidth();
+ int drawableHeight = drawable.getIntrinsicHeight();
+ ImageView.ScaleType scaleType = imageView.getScaleType();
+ if (drawableWidth <= 0 || drawableHeight <= 0 || scaleType == ImageView.ScaleType.FIT_XY) {
+ return null;
+ }
+ return new Matrix(imageView.getImageMatrix());
+ }
+
+ private Rect findClip(ImageView imageView) {
+ if (imageView.getCropToPadding()) {
+ Rect clip = getClip(imageView);
+ clip.left += imageView.getPaddingLeft();
+ clip.right -= imageView.getPaddingRight();
+ clip.top += imageView.getPaddingTop();
+ clip.bottom -= imageView.getPaddingBottom();
+ return clip;
+ } else {
+ View view = imageView;
+ ViewParent viewParent;
+ while ((viewParent = view.getParent()) instanceof ViewGroup) {
+ ViewGroup viewGroup = (ViewGroup) viewParent;
+ if (viewGroup.getClipChildren()) {
+ Rect clip = getClip(view);
+ return clip;
+ }
+ view = viewGroup;
+ }
+ }
+ return null;
+ }
+
+ private Rect getClip(View clipView) {
+ Rect clipBounds = clipView.getClipBounds();
+ if (clipBounds == null) {
+ clipBounds = new Rect(clipView.getLeft(), clipView.getTop(),
+ clipView.getRight(), clipView.getBottom());
+ }
+
+ ViewParent parent = clipView.getParent();
+ if (parent instanceof ViewGroup) {
+ ViewGroup parentViewGroup = (ViewGroup) parent;
+ parentViewGroup.getLocationInWindow(mTempLoc);
+ clipBounds.offset(mTempLoc[0], mTempLoc[1]);
+ }
+
+ return clipBounds;
+ }
+
+ @Override
+ public Transition clone() {
+ MoveImage clone = (MoveImage) super.clone();
+ clone.mTempLoc = new int[2];
+ return clone;
+ }
+
+ private static class MatrixEvaluator implements TypeEvaluator<Matrix> {
+ static final Matrix sIdentity = new Matrix();
+ float[] mTempStartValues = new float[9];
+ float[] mTempEndValues = new float[9];
+ Matrix mTempMatrix = new Matrix();
+
+ @Override
+ public Matrix evaluate(float fraction, Matrix startValue, Matrix endValue) {
+ if (startValue == null && endValue == null) {
+ return null;
+ }
+ if (startValue == null) {
+ startValue = sIdentity;
+ } else if (endValue == null) {
+ endValue = sIdentity;
+ }
+ startValue.getValues(mTempStartValues);
+ endValue.getValues(mTempEndValues);
+ for (int i = 0; i < 9; i++) {
+ float diff = mTempEndValues[i] - mTempStartValues[i];
+ mTempEndValues[i] = mTempStartValues[i] + (fraction * diff);
+ }
+ mTempMatrix.setValues(mTempEndValues);
+ return mTempMatrix;
+ }
+ }
+}
diff --git a/core/java/android/transition/SidePropagation.java b/core/java/android/transition/SidePropagation.java
new file mode 100644
index 0000000..c331945
--- /dev/null
+++ b/core/java/android/transition/SidePropagation.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.transition;
+
+import android.graphics.Rect;
+import android.util.FloatMath;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A <code>TransitionPropagation</code> that propagates based on the distance to the side
+ * and, orthogonally, the distance to epicenter. If the transitioning View is visible in
+ * the start of the transition, then it will transition sooner when closer to the side and
+ * later when farther. If the view is not visible in the start of the transition, then
+ * it will transition later when closer to the side and sooner when farther from the edge.
+ * This is the default TransitionPropagation used with {@link android.transition.Slide}.
+ */
+public class SidePropagation extends VisibilityPropagation {
+ private static final String TAG = "SlidePropagation";
+
+ /**
+ * Transition propagates relative to the distance of the left side of the scene.
+ */
+ public static final int LEFT = Slide.LEFT;
+
+ /**
+ * Transition propagates relative to the distance of the top of the scene.
+ */
+ public static final int TOP = Slide.TOP;
+
+ /**
+ * Transition propagates relative to the distance of the right side of the scene.
+ */
+ public static final int RIGHT = Slide.RIGHT;
+
+ /**
+ * Transition propagates relative to the distance of the bottom of the scene.
+ */
+ public static final int BOTTOM = Slide.BOTTOM;
+
+ private float mPropagationSpeed = 4.0f;
+ private int mSide = BOTTOM;
+
+ /**
+ * Sets the side that is used to calculate the transition propagation. If the transitioning
+ * View is visible in the start of the transition, then it will transition sooner when
+ * closer to the side and later when farther. If the view is not visible in the start of
+ * the transition, then it will transition later when closer to the side and sooner when
+ * farther from the edge. The default is {@link #BOTTOM}.
+ *
+ * @param side The side that is used to calculate the transition propagation. Must be one of
+ * {@link #LEFT}, {@link #TOP}, {@link #RIGHT}, or {@link #BOTTOM}.
+ */
+ public void setSide(int side) {
+ mSide = side;
+ }
+
+ /**
+ * Sets the speed at which transition propagation happens, relative to the duration of the
+ * Transition. A <code>propagationSpeed</code> of 1 means that a View centered at the side
+ * set in {@link #setSide(int)} and View centered at the opposite edge will have a difference
+ * in start delay of approximately the duration of the Transition. A speed of 2 means the
+ * start delay difference will be approximately half of the duration of the transition. A
+ * value of 0 is illegal, but negative values will invert the propagation.
+ *
+ * @param propagationSpeed The speed at which propagation occurs, relative to the duration
+ * of the transition. A speed of 4 means it works 4 times as fast
+ * as the duration of the transition. May not be 0.
+ */
+ public void setPropagationSpeed(float propagationSpeed) {
+ if (propagationSpeed == 0) {
+ throw new IllegalArgumentException("propagationSpeed may not be 0");
+ }
+ mPropagationSpeed = propagationSpeed;
+ }
+
+ @Override
+ public long getStartDelay(ViewGroup sceneRoot, Transition transition,
+ TransitionValues startValues, TransitionValues endValues) {
+ if (startValues == null && endValues == null) {
+ return 0;
+ }
+ int directionMultiplier = 1;
+ Rect epicenter = transition.getEpicenter();
+ TransitionValues positionValues;
+ if (endValues == null || getViewVisibility(startValues) == View.VISIBLE) {
+ positionValues = startValues;
+ directionMultiplier = -1;
+ } else {
+ positionValues = endValues;
+ }
+
+ int viewCenterX = getViewX(positionValues);
+ int viewCenterY = getViewY(positionValues);
+
+ int[] loc = new int[2];
+ sceneRoot.getLocationOnScreen(loc);
+ int left = loc[0] + Math.round(sceneRoot.getTranslationX());
+ int top = loc[1] + Math.round(sceneRoot.getTranslationY());
+ int right = left + sceneRoot.getWidth();
+ int bottom = top + sceneRoot.getHeight();
+
+ int epicenterX;
+ int epicenterY;
+ if (epicenter != null) {
+ epicenterX = epicenter.centerX();
+ epicenterY = epicenter.centerY();
+ } else {
+ epicenterX = (left + right) / 2;
+ epicenterY = (top + bottom) / 2;
+ }
+
+ float distance = distance(viewCenterX, viewCenterY, epicenterX, epicenterY,
+ left, top, right, bottom);
+ float maxDistance = getMaxDistance(sceneRoot);
+ float distanceFraction = distance/maxDistance;
+
+ return Math.round(transition.getDuration() * directionMultiplier / mPropagationSpeed
+ * distanceFraction);
+ }
+
+ private int distance(int viewX, int viewY, int epicenterX, int epicenterY,
+ int left, int top, int right, int bottom) {
+ int distance = 0;
+ switch (mSide) {
+ case LEFT:
+ distance = right - viewX + Math.abs(epicenterY - viewY);
+ break;
+ case TOP:
+ distance = bottom - viewY + Math.abs(epicenterX - viewX);
+ break;
+ case RIGHT:
+ distance = viewX - left + Math.abs(epicenterY - viewY);
+ break;
+ case BOTTOM:
+ distance = viewY - top + Math.abs(epicenterX - viewX);
+ break;
+ }
+ return distance;
+ }
+
+ private int getMaxDistance(ViewGroup sceneRoot) {
+ switch (mSide) {
+ case LEFT:
+ case RIGHT:
+ return sceneRoot.getWidth();
+ default:
+ return sceneRoot.getHeight();
+ }
+ }
+}
diff --git a/core/java/android/transition/Slide.java b/core/java/android/transition/Slide.java
index b38973c..0ff8ddd 100644
--- a/core/java/android/transition/Slide.java
+++ b/core/java/android/transition/Slide.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,53 +13,240 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package android.transition;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.graphics.Rect;
+import android.util.Log;
+import android.util.Property;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
/**
- * This transition captures the visibility of target objects before and
- * after a scene change and animates any changes by sliding the target
- * objects into or out of place.
- *
- * @hide
+ * This transition tracks changes to the visibility of target views in the
+ * start and end scenes and moves views in or out from one of the edges of the
+ * scene. Visibility is determined by both the
+ * {@link View#setVisibility(int)} state of the view as well as whether it
+ * is parented in the current view hierarchy. Disappearing Views are
+ * limited as described in {@link Visibility#onDisappear(android.view.ViewGroup,
+ * TransitionValues, int, TransitionValues, int)}.
*/
public class Slide extends Visibility {
+ private static final String TAG = "Slide";
- // TODO: Add parameter for sliding factor - it's hard-coded below
+ /**
+ * Move Views in or out of the left edge of the scene.
+ * @see #setSlideEdge(int)
+ */
+ public static final int LEFT = 0;
- private static final TimeInterpolator sAccelerator = new AccelerateInterpolator();
- private static final TimeInterpolator sDecelerator = new DecelerateInterpolator();
+ /**
+ * Move Views in or out of the top edge of the scene.
+ * @see #setSlideEdge(int)
+ */
+ public static final int TOP = 1;
- @Override
- public Animator onAppear(ViewGroup sceneRoot,
- TransitionValues startValues, int startVisibility,
- TransitionValues endValues, int endVisibility) {
- View endView = (endValues != null) ? endValues.view : null;
- endView.setTranslationY(-2 * endView.getHeight());
- ObjectAnimator anim = ObjectAnimator.ofFloat(endView, View.TRANSLATION_Y,
- -2 * endView.getHeight(), 0);
- anim.setInterpolator(sDecelerator);
+ /**
+ * Move Views in or out of the right edge of the scene.
+ * @see #setSlideEdge(int)
+ */
+ public static final int RIGHT = 2;
+
+ /**
+ * Move Views in or out of the bottom edge of the scene. This is the
+ * default slide direction.
+ * @see #setSlideEdge(int)
+ */
+ public static final int BOTTOM = 3;
+
+ private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
+ private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
+
+ private int[] mTempLoc = new int[2];
+ private CalculateSlide mSlideCalculator = sCalculateBottom;
+
+ private interface CalculateSlide {
+ /** Returns the translation value for view when it out of the scene */
+ float getGone(ViewGroup sceneRoot, View view);
+
+ /** Returns the translation value for view when it is in the scene */
+ float getHere(View view);
+
+ /** Returns the property to animate translation */
+ Property<View, Float> getProperty();
+ }
+
+ private static abstract class CalculateSlideHorizontal implements CalculateSlide {
+ @Override
+ public float getHere(View view) {
+ return view.getTranslationX();
+ }
+
+ @Override
+ public Property<View, Float> getProperty() {
+ return View.TRANSLATION_X;
+ }
+ }
+
+ private static abstract class CalculateSlideVertical implements CalculateSlide {
+ @Override
+ public float getHere(View view) {
+ return view.getTranslationY();
+ }
+
+ @Override
+ public Property<View, Float> getProperty() {
+ return View.TRANSLATION_Y;
+ }
+ }
+
+ private static final CalculateSlide sCalculateLeft = new CalculateSlideHorizontal() {
+ @Override
+ public float getGone(ViewGroup sceneRoot, View view) {
+ return view.getTranslationX() - sceneRoot.getWidth();
+ }
+ };
+
+ private static final CalculateSlide sCalculateTop = new CalculateSlideVertical() {
+ @Override
+ public float getGone(ViewGroup sceneRoot, View view) {
+ return view.getTranslationY() - sceneRoot.getHeight();
+ }
+ };
+
+ private static final CalculateSlide sCalculateRight = new CalculateSlideHorizontal() {
+ @Override
+ public float getGone(ViewGroup sceneRoot, View view) {
+ return view.getTranslationX() + sceneRoot.getWidth();
+ }
+ };
+
+ private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() {
+ @Override
+ public float getGone(ViewGroup sceneRoot, View view) {
+ return view.getTranslationY() + sceneRoot.getHeight();
+ }
+ };
+
+ /**
+ * Constructor using the default {@link android.transition.Slide#BOTTOM}
+ * slide edge direction.
+ */
+ public Slide() {
+ setSlideEdge(BOTTOM);
+ }
+
+ /**
+ * Constructor using the provided slide edge direction.
+ */
+ public Slide(int slideEdge) {
+ setSlideEdge(slideEdge);
+ }
+
+ /**
+ * Change the edge that Views appear and disappear from.
+ * @param slideEdge The edge of the scene to use for Views appearing and disappearing.
+ */
+ public void setSlideEdge(int slideEdge) {
+ switch (slideEdge) {
+ case LEFT:
+ mSlideCalculator = sCalculateLeft;
+ break;
+ case TOP:
+ mSlideCalculator = sCalculateTop;
+ break;
+ case RIGHT:
+ mSlideCalculator = sCalculateRight;
+ break;
+ case BOTTOM:
+ mSlideCalculator = sCalculateBottom;
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid slide direction");
+ }
+ SidePropagation propagation = new SidePropagation();
+ propagation.setSide(slideEdge);
+ setPropagation(propagation);
+ }
+
+ private Animator createAnimation(final View view, Property<View, Float> property,
+ float start, float end, float terminalValue, TimeInterpolator interpolator) {
+ view.setTranslationY(start);
+ if (start == end) {
+ return null;
+ }
+ final ObjectAnimator anim = ObjectAnimator.ofFloat(view, property, start, end);
+
+ SlideAnimatorListener listener = new SlideAnimatorListener(view, terminalValue, end);
+ anim.addListener(listener);
+ anim.addPauseListener(listener);
+ anim.setInterpolator(interpolator);
return anim;
}
@Override
- public Animator onDisappear(ViewGroup sceneRoot,
- TransitionValues startValues, int startVisibility,
- TransitionValues endValues, int endVisibility) {
- View startView = (startValues != null) ? startValues.view : null;
- startView.setTranslationY(0);
- ObjectAnimator anim = ObjectAnimator.ofFloat(startView, View.TRANSLATION_Y, 0,
- -2 * startView.getHeight());
- anim.setInterpolator(sAccelerator);
- return anim;
+ public Animator onAppear(ViewGroup sceneRoot, View view,
+ TransitionValues startValues, TransitionValues endValues) {
+ if (endValues == null) {
+ return null;
+ }
+ float end = mSlideCalculator.getHere(view);
+ float start = mSlideCalculator.getGone(sceneRoot, view);
+ return createAnimation(view, mSlideCalculator.getProperty(), start, end, end, sDecelerate);
}
+ @Override
+ public Animator onDisappear(ViewGroup sceneRoot, View view,
+ TransitionValues startValues, TransitionValues endValues) {
+ float start = mSlideCalculator.getHere(view);
+ float end = mSlideCalculator.getGone(sceneRoot, view);
+
+ return createAnimation(view, mSlideCalculator.getProperty(), start, end, start,
+ sAccelerate);
+ }
+
+ private static class SlideAnimatorListener extends AnimatorListenerAdapter {
+ private boolean mCanceled = false;
+ private float mPausedY;
+ private final View mView;
+ private final float mEndY;
+ private final float mTerminalY;
+
+ public SlideAnimatorListener(View view, float terminalY, float endY) {
+ mView = view;
+ mTerminalY = terminalY;
+ mEndY = endY;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ mView.setTranslationY(mTerminalY);
+ mCanceled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ if (!mCanceled) {
+ mView.setTranslationY(mTerminalY);
+ }
+ }
+
+ @Override
+ public void onAnimationPause(Animator animator) {
+ mPausedY = mView.getTranslationY();
+ mView.setTranslationY(mEndY);
+ }
+
+ @Override
+ public void onAnimationResume(Animator animator) {
+ mView.setTranslationY(mPausedY);
+ }
+ }
}
diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java
index c88b4c0..b7ae31e 100644
--- a/core/java/android/transition/Transition.java
+++ b/core/java/android/transition/Transition.java
@@ -19,10 +19,12 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeInterpolator;
+import android.graphics.Rect;
import android.util.ArrayMap;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.SparseArray;
+import android.util.SparseLongArray;
import android.view.SurfaceView;
import android.view.TextureView;
import android.view.View;
@@ -60,10 +62,18 @@
* <p>Transitions can be declared in XML resource files inside the <code>res/transition</code>
* directory. Transition resources consist of a tag name for one of the Transition
* subclasses along with attributes to define some of the attributes of that transition.
- * For example, here is a minimal resource file that declares a {@link ChangeBounds} transition:</p>
+ * For example, here is a minimal resource file that declares a {@link ChangeBounds} transition:
*
* {@sample development/samples/ApiDemos/res/transition/changebounds.xml ChangeBounds}
*
+ * <p>{@link android.transition.Explode} transition:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/transition/explode.xml Explode}
+ *
+ * <p>{@link android.transition.MoveImage} transition:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/transition/move_image.xml MoveImage}
+ *
* <p>Note that attributes for the transition are not required, just as they are
* optional when declared in code; Transitions created from XML resources will use
* the same defaults as their code-created equivalents. Here is a slightly more
@@ -87,7 +97,8 @@
*
* Further information on XML resource descriptions for transitions can be found for
* {@link android.R.styleable#Transition}, {@link android.R.styleable#TransitionSet},
- * {@link android.R.styleable#TransitionTarget}, and {@link android.R.styleable#Fade}.
+ * {@link android.R.styleable#TransitionTarget}, {@link android.R.styleable#Fade}, and
+ * {@link android.R.styleable#Slide}.
*
*/
public abstract class Transition implements Cloneable {
@@ -149,6 +160,13 @@
// to be run in runAnimators()
ArrayList<Animator> mAnimators = new ArrayList<Animator>();
+ // The function for calculating the Animation start delay.
+ TransitionPropagation mPropagation;
+
+ // The rectangular region for Transitions like Explode and TransitionPropagations
+ // like CircularPropagation
+ EpicenterCallback mEpicenterCallback;
+
/**
* Constructs a Transition object with no target objects. A transition with
* no targets defaults to running on all target objects in the scene hierarchy
@@ -435,6 +453,9 @@
endValuesList.add(end);
}
ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
+ long minStartDelay = Long.MAX_VALUE;
+ int minAnimator = mAnimators.size();
+ SparseLongArray startDelays = new SparseLongArray();
for (int i = 0; i < startValuesList.size(); ++i) {
TransitionValues start = startValuesList.get(i);
TransitionValues end = endValuesList.get(i);
@@ -497,6 +518,12 @@
view = (start != null) ? start.view : null;
}
if (animator != null) {
+ if (mPropagation != null) {
+ long delay = mPropagation
+ .getStartDelay(sceneRoot, this, start, end);
+ startDelays.put(mAnimators.size(), delay);
+ minStartDelay = Math.min(delay, minStartDelay);
+ }
AnimationInfo info = new AnimationInfo(view, getName(),
sceneRoot.getWindowId(), infoValues);
runningAnimators.put(animator, info);
@@ -506,6 +533,14 @@
}
}
}
+ if (minStartDelay != 0) {
+ for (int i = 0; i < startDelays.size(); i++) {
+ int index = startDelays.keyAt(i);
+ Animator animator = mAnimators.get(index);
+ long delay = startDelays.valueAt(i) - minStartDelay + animator.getStartDelay();
+ animator.setStartDelay(delay);
+ }
+ }
}
/**
@@ -565,7 +600,7 @@
/**
* This is called internally once all animations have been set up by the
- * transition hierarchy. \
+ * transition hierarchy.
*
* @hide
*/
@@ -1010,6 +1045,7 @@
} else {
captureEndValues(values);
}
+ capturePropagationValues(values);
if (start) {
mStartValues.viewValues.put(view, values);
if (id >= 0) {
@@ -1035,6 +1071,7 @@
} else {
captureEndValues(values);
}
+ capturePropagationValues(values);
if (start) {
mStartValues.viewValues.put(view, values);
} else {
@@ -1122,6 +1159,7 @@
} else {
captureEndValues(values);
}
+ capturePropagationValues(values);
if (start) {
if (!isListViewItem) {
mStartValues.viewValues.put(view, values);
@@ -1340,7 +1378,7 @@
animator.setDuration(getDuration());
}
if (getStartDelay() >= 0) {
- animator.setStartDelay(getStartDelay());
+ animator.setStartDelay(getStartDelay() + animator.getStartDelay());
}
if (getInterpolator() != null) {
animator.setInterpolator(getInterpolator());
@@ -1473,6 +1511,98 @@
return this;
}
+ /**
+ * Sets the callback to use to find the epicenter of a Transition. A null value indicates
+ * that there is no epicenter in the Transition and getEpicenter() will return null.
+ * Transitions like {@link android.transition.Explode} use a point or Rect to orient
+ * the direction of travel. This is called the epicenter of the Transition and is
+ * typically centered on a touched View. The
+ * {@link android.transition.Transition.EpicenterCallback} allows a Transition to
+ * dynamically retrieve the epicenter during a Transition.
+ * @param epicenterCallback The callback to use to find the epicenter of the Transition.
+ */
+ public void setEpicenterCallback(EpicenterCallback epicenterCallback) {
+ mEpicenterCallback = epicenterCallback;
+ }
+
+ /**
+ * Returns the callback used to find the epicenter of the Transition.
+ * Transitions like {@link android.transition.Explode} use a point or Rect to orient
+ * the direction of travel. This is called the epicenter of the Transition and is
+ * typically centered on a touched View. The
+ * {@link android.transition.Transition.EpicenterCallback} allows a Transition to
+ * dynamically retrieve the epicenter during a Transition.
+ * @return the callback used to find the epicenter of the Transition.
+ */
+ public EpicenterCallback getEpicenterCallback() {
+ return mEpicenterCallback;
+ }
+
+ /**
+ * Returns the epicenter as specified by the
+ * {@link android.transition.Transition.EpicenterCallback} or null if no callback exists.
+ * @return the epicenter as specified by the
+ * {@link android.transition.Transition.EpicenterCallback} or null if no callback exists.
+ * @see #setEpicenterCallback(android.transition.Transition.EpicenterCallback)
+ */
+ public Rect getEpicenter() {
+ if (mEpicenterCallback == null) {
+ return null;
+ }
+ return mEpicenterCallback.getEpicenter(this);
+ }
+
+ /**
+ * Sets the method for determining Animator start delays.
+ * When a Transition affects several Views like {@link android.transition.Explode} or
+ * {@link android.transition.Slide}, there may be a desire to have a "wave-front" effect
+ * such that the Animator start delay depends on position of the View. The
+ * TransitionPropagation specifies how the start delays are calculated.
+ * @param transitionPropagation The class used to determine the start delay of
+ * Animators created by this Transition. A null value
+ * indicates that no delay should be used.
+ */
+ public void setPropagation(TransitionPropagation transitionPropagation) {
+ mPropagation = transitionPropagation;
+ }
+
+ /**
+ * Returns the {@link android.transition.TransitionPropagation} used to calculate Animator start
+ * delays.
+ * When a Transition affects several Views like {@link android.transition.Explode} or
+ * {@link android.transition.Slide}, there may be a desire to have a "wave-front" effect
+ * such that the Animator start delay depends on position of the View. The
+ * TransitionPropagation specifies how the start delays are calculated.
+ * @return the {@link android.transition.TransitionPropagation} used to calculate Animator start
+ * delays. This is null by default.
+ */
+ public TransitionPropagation getPropagation() {
+ return mPropagation;
+ }
+
+ /**
+ * Captures TransitionPropagation values for the given view and the
+ * hierarchy underneath it.
+ */
+ void capturePropagationValues(TransitionValues transitionValues) {
+ if (mPropagation != null) {
+ String[] propertyNames = mPropagation.getPropagationProperties();
+ if (propertyNames == null) {
+ return;
+ }
+ boolean containsAll = true;
+ for (int i = 0; i < propertyNames.length; i++) {
+ if (!transitionValues.values.containsKey(propertyNames[i])) {
+ containsAll = false;
+ break;
+ }
+ }
+ if (!containsAll) {
+ mPropagation.captureValues(transitionValues);
+ }
+ }
+ }
+
Transition setSceneRoot(ViewGroup sceneRoot) {
mSceneRoot = sceneRoot;
return this;
@@ -1710,4 +1840,28 @@
}
}
+ /**
+ * Class to get the epicenter of Transition. Use
+ * {@link #setEpicenterCallback(android.transition.Transition.EpicenterCallback)} to
+ * set the callback used to calculate the epicenter of the Transition. Override
+ * {@link #getEpicenter()} to return the rectangular region in screen coordinates of
+ * the epicenter of the transition.
+ * @see #setEpicenterCallback(android.transition.Transition.EpicenterCallback)
+ */
+ public static abstract class EpicenterCallback {
+
+ /**
+ * Implementers must override to return the epicenter of the Transition in screen
+ * coordinates. Transitions like {@link android.transition.Explode} depend upon
+ * an epicenter for the Transition. In Explode, Views move toward or away from the
+ * center of the epicenter Rect along the vector between the epicenter and the center
+ * of the View appearing and disappearing. Some Transitions, such as
+ * {@link android.transition.Fade} pay no attention to the epicenter.
+ *
+ * @param transition The transition for which the epicenter applies.
+ * @return The Rect region of the epicenter of <code>transition</code> or null if
+ * there is no epicenter.
+ */
+ public abstract Rect getEpicenter(Transition transition);
+ }
}
diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java
index 912f2ed..f675c6a 100644
--- a/core/java/android/transition/TransitionInflater.java
+++ b/core/java/android/transition/TransitionInflater.java
@@ -20,7 +20,6 @@
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;
@@ -146,7 +145,13 @@
transition = new ChangeBounds();
newTransition = true;
} else if ("slide".equals(name)) {
- transition = new Slide();
+ transition = createSlideTransition(attrs);
+ newTransition = true;
+ } else if ("explode".equals(name)) {
+ transition = new Explode();
+ newTransition = true;
+ } else if ("moveImage".equals(name)) {
+ transition = new MoveImage();
newTransition = true;
} else if ("autoTransition".equals(name)) {
transition = new AutoTransition();
@@ -189,6 +194,15 @@
return transition;
}
+ private Slide createSlideTransition(AttributeSet attrs) {
+ TypedArray a = mContext.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.Slide);
+ int edge = a.getInt(com.android.internal.R.styleable.Slide_slideEdge, Slide.BOTTOM);
+ Slide slide = new Slide(edge);
+ a.recycle();
+ return slide;
+ }
+
private void getTargetIds(XmlPullParser parser,
AttributeSet attrs, Transition transition) throws XmlPullParserException, IOException {
diff --git a/core/java/android/transition/TransitionPropagation.java b/core/java/android/transition/TransitionPropagation.java
new file mode 100644
index 0000000..9a481c2
--- /dev/null
+++ b/core/java/android/transition/TransitionPropagation.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.transition;
+
+import android.graphics.Rect;
+import android.view.ViewGroup;
+
+/**
+ * Extend <code>TransitionPropagation</code> to customize start delays for Animators created
+ * in {@link android.transition.Transition#createAnimator(ViewGroup,
+ * TransitionValues, TransitionValues)}. A Transition such as {@link android.transition.Explode}
+ * defaults to using {@link android.transition.CircularPropagation} and Views closer to the
+ * epicenter will move out of the scene later and into the scene sooner than Views farther
+ * from the epicenter, giving the appearance of inertia. With no TransitionPropagation, all
+ * Views will react simultaneously to the start of the transition.
+ *
+ * @see Transition#setPropagation(TransitionPropagation)
+ * @see Transition#getEpicenter()
+ */
+public abstract class TransitionPropagation {
+ /**
+ * Called by Transition to alter the Animator start delay. All start delays will be adjusted
+ * such that the minimum becomes zero.
+ * @param sceneRoot The root of the View hierarchy running the transition.
+ * @param transition The transition that created the Animator
+ * @param startValues The values for a specific target in the start scene.
+ * @param endValues The values for the target in the end scene.
+ * @return A start delay to use with the Animator created by <code>transition</code>. The
+ * delay will be offset by the minimum delay of all <code>TransitionPropagation</code>s
+ * used in the Transition so that the smallest delay will be 0. Returned values may be
+ * negative.
+ */
+ public abstract long getStartDelay(ViewGroup sceneRoot, Transition transition,
+ TransitionValues startValues, TransitionValues endValues);
+
+ /**
+ * Captures the values in the start or end scene for the properties that this
+ * transition propagation monitors. These values are then passed as the startValues
+ * or endValues structure in a later call to
+ * {@link #getStartDelay(ViewGroup, Transition, TransitionValues, TransitionValues)}.
+ * The main concern for an implementation is what the
+ * properties are that the transition cares about and what the values are
+ * for all of those properties. The start and end values will be compared
+ * later during the
+ * {@link #getStartDelay(ViewGroup, Transition, TransitionValues, TransitionValues)}.
+ * method to determine the start delay.
+ *
+ * <p>Subclasses must implement this method. The method should only be called by the
+ * transition system; it is not intended to be called from external classes.</p>
+ *
+ * @param transitionValues The holder for any values that the Transition
+ * wishes to store. Values are stored in the <code>values</code> field
+ * of this TransitionValues object and are keyed from
+ * a String value. For example, to store a view's rotation value,
+ * a transition might call
+ * <code>transitionValues.values.put("appname:transitionname:rotation",
+ * view.getRotation())</code>. The target view will already be stored in
+ * the transitionValues structure when this method is called.
+ */
+ public abstract void captureValues(TransitionValues transitionValues);
+
+ /**
+ * Returns the set of property names stored in the {@link TransitionValues}
+ * object passed into {@link #captureValues(TransitionValues)} that
+ * this transition propagation cares about for the purposes of preventing
+ * duplicate capturing of property values.
+
+ * <p>A <code>TransitionPropagation</code> must override this method to prevent
+ * duplicate capturing of values and must contain at least one </p>
+ *
+ * @return An array of property names as described in the class documentation for
+ * {@link TransitionValues}.
+ */
+ public abstract String[] getPropagationProperties() ;
+}
diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java
index 19d6b3d..966b24d 100644
--- a/core/java/android/transition/TransitionSet.java
+++ b/core/java/android/transition/TransitionSet.java
@@ -17,6 +17,7 @@
package android.transition;
import android.animation.TimeInterpolator;
+import android.graphics.Rect;
import android.util.AndroidRuntimeException;
import android.view.View;
import android.view.ViewGroup;
@@ -315,6 +316,15 @@
}
}
+ @Override
+ void capturePropagationValues(TransitionValues transitionValues) {
+ super.capturePropagationValues(transitionValues);
+ int numTransitions = mTransitions.size();
+ for (int i = 0; i < numTransitions; ++i) {
+ mTransitions.get(i).capturePropagationValues(transitionValues);
+ }
+ }
+
/** @hide */
@Override
public void pause(View sceneRoot) {
@@ -365,6 +375,24 @@
}
@Override
+ public void setPropagation(TransitionPropagation propagation) {
+ super.setPropagation(propagation);
+ int numTransitions = mTransitions.size();
+ for (int i = 0; i < numTransitions; ++i) {
+ mTransitions.get(i).setPropagation(propagation);
+ }
+ }
+
+ @Override
+ public void setEpicenterCallback(EpicenterCallback epicenterCallback) {
+ super.setEpicenterCallback(epicenterCallback);
+ int numTransitions = mTransitions.size();
+ for (int i = 0; i < numTransitions; ++i) {
+ mTransitions.get(i).setEpicenterCallback(epicenterCallback);
+ }
+ }
+
+ @Override
String toString(String indent) {
String result = super.toString(indent);
for (int i = 0; i < mTransitions.size(); ++i) {
@@ -383,5 +411,4 @@
}
return clone;
}
-
}
diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java
index 44f92cd..7783b6f 100644
--- a/core/java/android/transition/Visibility.java
+++ b/core/java/android/transition/Visibility.java
@@ -17,6 +17,7 @@
package android.transition;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.view.View;
import android.view.ViewGroup;
@@ -29,15 +30,20 @@
* information to determine the specific animations to run when visibility
* changes occur. Subclasses should implement one or both of the methods
* {@link #onAppear(ViewGroup, TransitionValues, int, TransitionValues, int)},
- * {@link #onDisappear(ViewGroup, TransitionValues, int, TransitionValues, int)},
+ * {@link #onDisappear(ViewGroup, TransitionValues, int, TransitionValues, int)} or
+ * {@link #onAppear(ViewGroup, View, TransitionValues, TransitionValues)},
+ * {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)}.
*/
public abstract class Visibility extends Transition {
private static final String PROPNAME_VISIBILITY = "android:visibility:visibility";
private static final String PROPNAME_PARENT = "android:visibility:parent";
+ private static final String PROPNAME_SCREEN_LOCATION = "android:visibility:screenLocation";
+
private static final String[] sTransitionProperties = {
PROPNAME_VISIBILITY,
PROPNAME_PARENT,
+ PROPNAME_SCREEN_LOCATION,
};
private static class VisibilityInfo {
@@ -58,6 +64,9 @@
int visibility = transitionValues.view.getVisibility();
transitionValues.values.put(PROPNAME_VISIBILITY, visibility);
transitionValues.values.put(PROPNAME_PARENT, transitionValues.view.getParent());
+ int[] loc = new int[2];
+ transitionValues.view.getLocationOnScreen(loc);
+ transitionValues.values.put(PROPNAME_SCREEN_LOCATION, loc);
}
@Override
@@ -179,8 +188,11 @@
}
/**
- * The default implementation of this method does nothing. Subclasses
- * should override if they need to create an Animator when targets appear.
+ * The default implementation of this method calls
+ * {@link #onAppear(ViewGroup, View, TransitionValues, TransitionValues)}.
+ * Subclasses should override this method or
+ * {@link #onAppear(ViewGroup, View, TransitionValues, TransitionValues)}.
+ * if they need to create an Animator when targets appear.
* The method should only be called by the Visibility class; it is
* not intended to be called from external classes.
*
@@ -196,15 +208,53 @@
public Animator onAppear(ViewGroup sceneRoot,
TransitionValues startValues, int startVisibility,
TransitionValues endValues, int endVisibility) {
+ return onAppear(sceneRoot, endValues.view, startValues, endValues);
+ }
+
+ /**
+ * The default implementation of this method returns a null Animator. Subclasses should
+ * override this method to make targets appear with the desired transition. The
+ * method should only be called from
+ * {@link #onAppear(ViewGroup, TransitionValues, int, TransitionValues, int)}.
+ *
+ * @param sceneRoot The root of the transition hierarchy
+ * @param view The View to make appear. This will be in the target scene's View hierarchy and
+ * will be VISIBLE.
+ * @param startValues The target values in the start scene
+ * @param endValues The target values in the end scene
+ * @return An Animator to be started at the appropriate time in the
+ * overall transition for this scene change. A null value means no animation
+ * should be run.
+ */
+ public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues,
+ TransitionValues endValues) {
return null;
}
/**
- * The default implementation of this method does nothing. Subclasses
- * should override if they need to create an Animator when targets disappear.
+ * Subclasses should override this method or
+ * {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)}
+ * if they need to create an Animator when targets disappear.
* The method should only be called by the Visibility class; it is
* not intended to be called from external classes.
- *
+ * <p>
+ * The default implementation of this method attempts to find a View to use to call
+ * {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)},
+ * based on the situation of the View in the View hierarchy. For example,
+ * if a View was simply removed from its parent, then the View will be added
+ * into a {@link android.view.ViewGroupOverlay} and passed as the <code>view</code>
+ * parameter in {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)}.
+ * If a visible View is changed to be {@link View#GONE} or {@link View#INVISIBLE},
+ * then it can be used as the <code>view</code> and the visibility will be changed
+ * to {@link View#VISIBLE} for the duration of the animation. However, if a View
+ * is in a hierarchy which is also altering its visibility, the situation can be
+ * more complicated. In general, if a view that is no longer in the hierarchy in
+ * the end scene still has a parent (so its parent hierarchy was removed, but it
+ * was not removed from its parent), then it will be left alone to avoid side-effects from
+ * improperly removing it from its parent. The only exception to this is if
+ * the previous {@link Scene} was {@link Scene#getSceneForLayout(ViewGroup, int,
+ * android.content.Context) created from a layout resource file}, then it is considered
+ * safe to un-parent the starting scene view in order to make it disappear.</p>
*
* @param sceneRoot The root of the transition hierarchy
* @param startValues The target values in the start scene
@@ -218,6 +268,144 @@
public Animator onDisappear(ViewGroup sceneRoot,
TransitionValues startValues, int startVisibility,
TransitionValues endValues, int endVisibility) {
+ View startView = (startValues != null) ? startValues.view : null;
+ View endView = (endValues != null) ? endValues.view : null;
+ View overlayView = null;
+ View viewToKeep = null;
+ if (endView == null || endView.getParent() == null) {
+ if (endView != null) {
+ // endView was removed from its parent - add it to the overlay
+ overlayView = endView;
+ } else if (startView != null) {
+ // endView does not exist. Use startView only under certain
+ // conditions, because placing a view in an overlay necessitates
+ // it being removed from its current parent
+ if (startView.getParent() == null) {
+ // no parent - safe to use
+ overlayView = startView;
+ } else if (startView.getParent() instanceof View &&
+ startView.getParent().getParent() == null) {
+ View startParent = (View) startView.getParent();
+ int id = startParent.getId();
+ if (id != View.NO_ID && sceneRoot.findViewById(id) != null && mCanRemoveViews) {
+ // no parent, but its parent is unparented but the parent
+ // hierarchy has been replaced by a new hierarchy with the same id
+ // and it is safe to un-parent startView
+ overlayView = startView;
+ }
+ }
+ }
+ } else {
+ // visibility change
+ if (endVisibility == View.INVISIBLE) {
+ viewToKeep = endView;
+ } else {
+ // Becoming GONE
+ if (startView == endView) {
+ viewToKeep = endView;
+ } else {
+ overlayView = startView;
+ }
+ }
+ }
+ final int finalVisibility = endVisibility;
+ final ViewGroup finalSceneRoot = sceneRoot;
+
+ if (overlayView != null) {
+ // TODO: Need to do this for general case of adding to overlay
+ int[] screenLoc = (int[]) startValues.values.get(PROPNAME_SCREEN_LOCATION);
+ int screenX = screenLoc[0];
+ int screenY = screenLoc[1];
+ int[] loc = new int[2];
+ sceneRoot.getLocationOnScreen(loc);
+ overlayView.offsetLeftAndRight((screenX - loc[0]) - overlayView.getLeft());
+ overlayView.offsetTopAndBottom((screenY - loc[1]) - overlayView.getTop());
+ sceneRoot.getOverlay().add(overlayView);
+ Animator animator = onDisappear(sceneRoot, overlayView, startValues, endValues);
+ if (animator == null) {
+ sceneRoot.getOverlay().remove(overlayView);
+ } else {
+ final View finalOverlayView = overlayView;
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finalSceneRoot.getOverlay().remove(finalOverlayView);
+ }
+
+ @Override
+ public void onAnimationPause(Animator animation) {
+ finalSceneRoot.getOverlay().remove(finalOverlayView);
+ }
+
+ @Override
+ public void onAnimationResume(Animator animation) {
+ finalSceneRoot.getOverlay().add(finalOverlayView);
+ }
+ });
+ }
+ return animator;
+ }
+
+ if (viewToKeep != null) {
+ viewToKeep.setVisibility(View.VISIBLE);
+ Animator animator = onDisappear(sceneRoot, viewToKeep, startValues, endValues);
+ if (animator == null) {
+ viewToKeep.setVisibility(finalVisibility);
+ } else {
+ final View finalViewToKeep = viewToKeep;
+ animator.addListener(new AnimatorListenerAdapter() {
+ boolean mCanceled = false;
+
+ @Override
+ public void onAnimationPause(Animator animation) {
+ if (!mCanceled) {
+ finalViewToKeep.setVisibility(finalVisibility);
+ }
+ }
+
+ @Override
+ public void onAnimationResume(Animator animation) {
+ if (!mCanceled) {
+ finalViewToKeep.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCanceled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!mCanceled) {
+ finalViewToKeep.setVisibility(finalVisibility);
+ }
+ }
+ });
+ }
+ return animator;
+ }
+ return null;
+ }
+
+ /**
+ * The default implementation of this method returns a null Animator. Subclasses should
+ * override this method to make targets disappear with the desired transition. The
+ * method should only be called from
+ * {@link #onDisappear(ViewGroup, TransitionValues, int, TransitionValues, int)}.
+ *
+ * @param sceneRoot The root of the transition hierarchy
+ * @param view The View to make disappear. This will be in the target scene's View
+ * hierarchy or in an {@link android.view.ViewGroupOverlay} and will be
+ * VISIBLE.
+ * @param startValues The target values in the start scene
+ * @param endValues The target values in the end scene
+ * @return An Animator to be started at the appropriate time in the
+ * overall transition for this scene change. A null value means no animation
+ * should be run.
+ */
+ public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues,
+ TransitionValues endValues) {
return null;
}
}
diff --git a/core/java/android/transition/VisibilityPropagation.java b/core/java/android/transition/VisibilityPropagation.java
new file mode 100644
index 0000000..0326d47
--- /dev/null
+++ b/core/java/android/transition/VisibilityPropagation.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.transition;
+
+import android.view.View;
+
+/**
+ * Base class for <code>TransitionPropagation</code>s that care about
+ * View Visibility and the center position of the View.
+ */
+public abstract class VisibilityPropagation extends TransitionPropagation {
+
+ /**
+ * The property key used for {@link android.view.View#getVisibility()}.
+ */
+ private static final String PROPNAME_VISIBILITY = "android:visibilityPropagation:visibility";
+
+ /**
+ * The property key used for the center of the View in screen coordinates. This is an
+ * int[2] with the index 0 taking the x coordinate and index 1 taking the y coordinate.
+ */
+ private static final String PROPNAME_VIEW_CENTER = "android:visibilityPropagation:center";
+
+ private static final String[] VISIBILITY_PROPAGATION_VALUES = {
+ PROPNAME_VISIBILITY,
+ PROPNAME_VIEW_CENTER,
+ };
+
+ @Override
+ public void captureValues(TransitionValues values) {
+ View view = values.view;
+ values.values.put(PROPNAME_VISIBILITY, view.getVisibility());
+ int[] loc = new int[2];
+ view.getLocationOnScreen(loc);
+ loc[0] += Math.round(view.getTranslationX());
+ loc[0] += view.getWidth() / 2;
+ loc[1] += Math.round(view.getTranslationY());
+ loc[1] += view.getHeight() / 2;
+ values.values.put(PROPNAME_VIEW_CENTER, loc);
+ }
+
+ @Override
+ public String[] getPropagationProperties() {
+ return VISIBILITY_PROPAGATION_VALUES;
+ }
+
+ /**
+ * Returns {@link android.view.View#getVisibility()} for the View at the time the values
+ * were captured.
+ * @param values The TransitionValues captured at the start or end of the Transition.
+ * @return {@link android.view.View#getVisibility()} for the View at the time the values
+ * were captured.
+ */
+ public int getViewVisibility(TransitionValues values) {
+ if (values == null) {
+ return View.GONE;
+ }
+ Integer visibility = (Integer) values.values.get(PROPNAME_VISIBILITY);
+ if (visibility == null) {
+ return View.GONE;
+ }
+ return visibility;
+ }
+
+ /**
+ * Returns the View's center x coordinate, relative to the screen, at the time the values
+ * were captured.
+ * @param values The TransitionValues captured at the start or end of the Transition.
+ * @return the View's center x coordinate, relative to the screen, at the time the values
+ * were captured.
+ */
+ public int getViewX(TransitionValues values) {
+ return getViewCoordinate(values, 0);
+ }
+
+ /**
+ * Returns the View's center y coordinate, relative to the screen, at the time the values
+ * were captured.
+ * @param values The TransitionValues captured at the start or end of the Transition.
+ * @return the View's center y coordinate, relative to the screen, at the time the values
+ * were captured.
+ */
+ public int getViewY(TransitionValues values) {
+ return getViewCoordinate(values, 1);
+ }
+
+ private static int getViewCoordinate(TransitionValues values, int coordinateIndex) {
+ if (values == null) {
+ return -1;
+ }
+
+ int[] coordinates = (int[]) values.values.get(PROPNAME_VIEW_CENTER);
+ if (coordinates == null) {
+ return -1;
+ }
+
+ return coordinates[coordinateIndex];
+ }
+}
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 0cd6325..7bd1f56 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityOptions;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
@@ -1390,11 +1389,11 @@
* @param options Options to set or null for none
* @hide
*/
- public void setTransitionOptions(ActivityOptions options, SceneTransitionListener listener) {
+ public void setTransitionOptions(Bundle options, SceneTransitionListener listener) {
}
/**
- * A callback for Activity transitions to be told when the shared element is ready to be shown
+ * A callback for Window transitions to be told when the shared element is ready to be shown
* and start the transition to its target location.
* @hide
*/
@@ -1407,27 +1406,57 @@
}
/**
- * Controls when the Activity enter scene is triggered and the background is faded in. If
- * triggerEarly is true, the enter scene will begin as soon as possible and the background
- * will fade in when all shared elements are ready to begin transitioning. If triggerEarly is
- * false, the Activity enter scene and background fade will be triggered when the calling
- * Activity's exit transition completes.
- *
- * @param triggerEarly Set to true to have the Activity enter scene transition in as early as
- * possible or set to false to wait for the calling Activity to exit first.
+ * Controls how the Activity's start Scene is faded in and when the enter scene
+ * is triggered to start.
+ * <p>When allow is true, the enter Scene will begin as soon as possible
+ * and the background will fade in when all shared elements are ready to begin
+ * transitioning. If allow is false, the Activity enter Scene and
+ * background fade will be triggered when the calling Activity's exit transition
+ * completes.</p>
+ * @param allow Set to true to have the Activity enter scene transition in
+ * as early as possible or set to false to wait for the calling
+ * Activity to exit first. The default value is true.
*/
- public void setTriggerEarlyEnterTransition(boolean triggerEarly) {
+ public void setAllowOverlappingEnterTransition(boolean allow) {
+ }
+
+ /**
+ * Controls how the Activity's Scene fades out and when the calling Activity's
+ * enter scene is triggered when finishing to return to a calling Activity.
+ * <p>When allow is true, the Scene will fade out quickly
+ * and inform the calling Activity to transition in when the fade completes.
+ * When allow is false, the calling Activity will transition in after
+ * the Activity's Scene has exited.
+ * </p>
+ * @param allow Set to true to have the Activity fade out as soon as possible
+ * and transition in the calling Activity. The default value is
+ * true.
+ */
+ public void setAllowOverlappingExitTransition(boolean allow) {
}
/**
* Start the exit transition.
* @hide
*/
- public Bundle startExitTransition(ActivityOptions options) {
+ public Bundle startExitTransitionToCallee(Bundle options) {
return null;
}
/**
+ * Starts the transition back to the calling Activity.
+ * onTransitionEnd will be called on the current thread if there is no exit transition.
+ * @hide
+ */
+ public void startExitTransitionToCaller(Runnable onTransitionEnd) {
+ onTransitionEnd.run();
+ }
+
+ /** @hide */
+ public void restoreViewVisibilityAfterTransitionToCallee() {
+ }
+
+ /**
* On entering Activity Scene transitions, shared element names may be mapped from a
* source Activity's specified name to a unique shared element name in the View hierarchy.
* Under most circumstances, mapping is not necessary - a single View will have the
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 0fc198e2..362182e 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4891,6 +4891,24 @@
</attr>
</declare-styleable>
+ <!-- Use <code>slide</code>as the root tag of the XML resource that
+ describes a {@link android.transition.Slide Slide} transition.
+ The attributes of the {@link android.R.styleable#Transition Transition}
+ resource are available in addition to the specific attributes of Slide
+ described here. -->
+ <declare-styleable name="Slide">
+ <attr name="slideEdge">
+ <!-- Slide to and from the bottom edge of the Scene. -->
+ <enum name="left" value="0" />
+ <!-- Slide to and from the bottom edge of the Scene. -->
+ <enum name="top" value="1" />
+ <!-- Slide to and from the bottom edge of the Scene. -->
+ <enum name="right" value="2" />
+ <!-- Slide to and from the bottom edge of the Scene. -->
+ <enum name="bottom" value="3" />
+ </attr>
+ </declare-styleable>
+
<!-- Use <code>target</code> as the root tag of the XML resource that
describes a {@link android.transition.Transition#addTarget(int)
targetId} of a transition. There can be one or more targets inside
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 76a3bd7..1e7ff60 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2145,6 +2145,7 @@
<public type="attr" name="persistable" />
<public type="attr" name="titleTextAppearance" />
<public type="attr" name="subtitleTextAppearance" />
+ <public type="attr" name="slideEdge" />
<public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
diff --git a/docs/html/training/game-controllers/controller-input.jd b/docs/html/training/game-controllers/controller-input.jd
index 2c50ae1..c9517ba 100644
--- a/docs/html/training/game-controllers/controller-input.jd
+++ b/docs/html/training/game-controllers/controller-input.jd
@@ -236,26 +236,33 @@
</tr>
<tr>
<td>Start game in main menu, or pause/unpause during game</td>
- <td>{@link android.view.KeyEvent#KEYCODE_BUTTON_START BUTTON_START}</td>
+ <td>{@link android.view.KeyEvent#KEYCODE_BUTTON_START BUTTON_START}<sup>*</sup></td>
</tr>
<tr>
<td>Display menu</td>
- <td>{@link android.view.KeyEvent#KEYCODE_BUTTON_SELECT BUTTON_SELECT} and
-{@link android.view.KeyEvent#KEYCODE_MENU}</td>
+ <td>{@link android.view.KeyEvent#KEYCODE_BUTTON_SELECT BUTTON_SELECT}<sup>*</sup>
+ and {@link android.view.KeyEvent#KEYCODE_MENU}<sup>*</sup></td>
</tr>
<tr>
<td>Same as Android <em>Back</em></td>
- <td>{@link android.view.KeyEvent#KEYCODE_BUTTON_B BUTTON_B}<sup>*</sup> and
-{@link android.view.KeyEvent#KEYCODE_BACK KEYCODE_BACK}</td>
+ <td>{@link android.view.KeyEvent#KEYCODE_BACK KEYCODE_BACK}</td>
+ </tr>
+ <tr>
+ <td>Navigate back to a previous item in a menu</td>
+ <td>{@link android.view.KeyEvent#KEYCODE_BUTTON_B BUTTON_B}<sup>**</sup></td>
</tr>
<tr>
<td>Confirm selection, or perform primary game action</td>
- <td>{@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A}<sup>*</sup> and
+ <td>{@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A}<sup>**</sup> and
{@link android.view.KeyEvent#KEYCODE_DPAD_CENTER DPAD_CENTER}</td>
</tr>
</table>
<p>
-<em>* This could be the opposite button (A/B), depending on the locale that
+<em>* Your game should not rely on the presence of the Start, Select, or Menu
+ buttons.</em>
+</p>
+<p>
+<em>** This could be the opposite button (A/B), depending on the locale that
you are supporting.</em>
</p>
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java b/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
index ab2ad96..eb09335 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
@@ -156,7 +156,7 @@
// Create an TaskDescription, returning null if the title or icon is null
TaskDescription createTaskDescription(int taskId, int persistentTaskId, Intent baseIntent,
- ComponentName origActivity, CharSequence description) {
+ ComponentName origActivity, CharSequence description, int userId) {
Intent intent = new Intent(baseIntent);
if (origActivity != null) {
intent.setComponent(origActivity);
@@ -175,7 +175,7 @@
TaskDescription item = new TaskDescription(taskId,
persistentTaskId, resolveInfo, baseIntent, info.packageName,
- description);
+ description, userId);
item.setLabel(title);
return item;
@@ -391,7 +391,8 @@
item = createTaskDescription(recentInfo.id,
recentInfo.persistentId, recentInfo.baseIntent,
- recentInfo.origActivity, recentInfo.description);
+ recentInfo.origActivity, recentInfo.description,
+ recentInfo.userId);
if (item != null) {
loadThumbnailAndIcon(item);
}
@@ -474,7 +475,8 @@
TaskDescription item = createTaskDescription(recentInfo.id,
recentInfo.persistentId, recentInfo.baseIntent,
- recentInfo.origActivity, recentInfo.description);
+ recentInfo.origActivity, recentInfo.description,
+ recentInfo.userId);
if (item != null) {
while (true) {
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
index ef56044..98bdee0 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
@@ -689,7 +689,7 @@
if (DEBUG) Log.v(TAG, "Starting activity " + intent);
try {
context.startActivityAsUser(intent, opts,
- new UserHandle(UserHandle.USER_CURRENT));
+ new UserHandle(ad.userId));
} catch (SecurityException e) {
Log.e(TAG, "Recents does not have the permission to launch " + intent, e);
} catch (ActivityNotFoundException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java b/packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java
index 2e0ee36..5ad965f 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java
@@ -16,6 +16,7 @@
package com.android.systemui.recent;
+import android.os.UserHandle;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
@@ -27,6 +28,7 @@
final Intent intent; // launch intent for application
final String packageName; // used to override animations (see onClick())
final CharSequence description;
+ final int userId;
private Drawable mThumbnail; // generated by Activity.onCreateThumbnail()
private Drawable mIcon; // application package icon
@@ -35,7 +37,7 @@
public TaskDescription(int _taskId, int _persistentTaskId,
ResolveInfo _resolveInfo, Intent _intent,
- String _packageName, CharSequence _description) {
+ String _packageName, CharSequence _description, int _userId) {
resolveInfo = _resolveInfo;
intent = _intent;
taskId = _taskId;
@@ -43,6 +45,7 @@
description = _description;
packageName = _packageName;
+ userId = _userId;
}
public TaskDescription() {
@@ -53,6 +56,7 @@
description = null;
packageName = null;
+ userId = UserHandle.USER_NULL;
}
public void setLoaded(boolean loaded) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
index e193a95..d661f287 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
@@ -414,7 +414,7 @@
// Create a new task
Task task = new Task(t.persistentId, (t.id > -1), t.baseIntent, activityLabel,
- activityIcon);
+ activityIcon, t.userId);
// Preload the specified number of apps
if (i >= (taskCount - preloadCount)) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index ed2ab2a..a0ff3b7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -65,15 +65,17 @@
public Bitmap activityIcon;
public Bitmap thumbnail;
public boolean isActive;
+ public int userId;
TaskCallbacks mCb;
public Task(int id, boolean isActive, Intent intent, String activityTitle,
- Bitmap activityIcon) {
+ Bitmap activityIcon, int userId) {
this.key = new TaskKey(id, intent);
this.activityLabel = activityTitle;
this.activityIcon = activityIcon;
this.isActive = isActive;
+ this.userId = userId;
}
/** Set the callbacks */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index cb52794..1ebe231 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -257,9 +257,9 @@
try {
if (opts != null) {
getContext().startActivityAsUser(i, opts.toBundle(),
- UserHandle.CURRENT);
+ new UserHandle(task.userId));
} else {
- getContext().startActivityAsUser(i, UserHandle.CURRENT);
+ getContext().startActivityAsUser(i, new UserHandle(task.userId));
}
} catch (ActivityNotFoundException anfe) {
Console.logError(getContext(), "Could not start Activity");
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index f1db904..79ed866 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -25,6 +25,8 @@
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.app.ActivityOptions;
+import android.os.Looper;
+import android.transition.Fade;
import android.transition.Scene;
import android.transition.Transition;
import android.transition.TransitionInflater;
@@ -109,7 +111,6 @@
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.List;
import java.util.Map;
/**
@@ -253,8 +254,10 @@
private ActivityOptions mActivityOptions;
private SceneTransitionListener mSceneTransitionListener;
- private boolean mTriggerEarly = true;
+ private boolean mAllowEnterOverlap = true;
+ private boolean mAllowExitOverlap = true;
private Map<String, String> mSharedElementsMap;
+ private ArrayList<View> mTransitioningViews;
static class WindowManagerHolder {
static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface(
@@ -4081,14 +4084,26 @@
}
@Override
- public void setTransitionOptions(ActivityOptions options, SceneTransitionListener listener) {
+ public void setTransitionOptions(Bundle options, SceneTransitionListener listener) {
mSceneTransitionListener = listener;
- mActivityOptions = options;
+ ActivityOptions activityOptions = null;
+ if (options != null) {
+ activityOptions = new ActivityOptions(options);
+ if (activityOptions.getAnimationType() != ActivityOptions.ANIM_SCENE_TRANSITION) {
+ activityOptions = null;
+ }
+ }
+ mActivityOptions = activityOptions;
}
@Override
- public void setTriggerEarlyEnterTransition(boolean triggerEarly) {
- mTriggerEarly = triggerEarly;
+ public void setAllowOverlappingEnterTransition(boolean allow) {
+ mAllowEnterOverlap = allow;
+ }
+
+ @Override
+ public void setAllowOverlappingExitTransition(boolean allow) {
+ mAllowExitOverlap = allow;
}
@Override
@@ -4097,7 +4112,50 @@
}
@Override
- public Bundle startExitTransition(ActivityOptions activityOptions) {
+ public void restoreViewVisibilityAfterTransitionToCallee() {
+ if (mTransitioningViews != null) {
+ setViewVisibility(mTransitioningViews, View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void startExitTransitionToCaller(final Runnable onTransitionEnd) {
+ Transition transition;
+ if (mContentScene == null || mTransitionManager == null || mActivityOptions == null
+ || (transition = mTransitionManager.getEnterTransition(mContentScene)) == null) {
+ onTransitionEnd.run();
+ return;
+ }
+ if (mAllowExitOverlap) {
+ TransitionSet transitionSet = new TransitionSet();
+ transitionSet.addTransition(transition);
+ Fade fade = new Fade();
+ transitionSet.addTransition(fade);
+ transition = transitionSet;
+ }
+
+ final ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
+ mapSharedElements(sharedElements);
+ final Bundle sharedElementArgs = new Bundle();
+ captureTerminalSharedElementState(sharedElements, sharedElementArgs);
+
+ final ArrayList<View> transitioningViews = new ArrayList<View>();
+ mDecor.captureTransitioningViews(transitioningViews);
+ transitioningViews.removeAll(sharedElements.values());
+
+ mSceneTransitionListener.convertToTranslucent();
+ transition = transition.clone();
+ Rect epicenter = calcEpicenter(sharedElements, mActivityOptions.getSharedElementNames());
+ transition.setEpicenterCallback(new FixedEpicenterCallback(epicenter));
+ ExitSceneBack exitScene =
+ new ExitSceneBack(onTransitionEnd, sharedElementArgs, sharedElements.values());
+ exitScene.start(transition);
+ mTransitionManager.beginDelayedTransition(mDecor, transition);
+ setViewVisibility(transitioningViews, View.INVISIBLE);
+ }
+
+ @Override
+ public Bundle startExitTransitionToCallee(Bundle options) {
if (mContentScene == null) {
return null;
}
@@ -4106,16 +4164,15 @@
return null;
}
+ ActivityOptions activityOptions = new ActivityOptions(options);
ArrayMap<String, View> sharedElements = findSharedElements(activityOptions);
// Find exiting Views and shared elements
- final ArrayList<View> transitioningViews = new ArrayList<View>();
- mDecor.captureTransitioningViews(transitioningViews);
- transitioningViews.removeAll(sharedElements.values());
+ ArrayList<View> transitioningViews = captureTransitioningViews(sharedElements.values());
- Transition exitTransition = cloneAndSetTransitionTargets(transition,
+ Transition exitTransition = addTransitionTargets(transition,
transitioningViews, true);
- Transition sharedElementTransition = cloneAndSetTransitionTargets(transition,
+ Transition sharedElementTransition = addTransitionTargets(transition,
transitioningViews, false);
// transitionSet is the total exit transition, including hero animation.
@@ -4123,8 +4180,12 @@
transitionSet.addTransition(exitTransition);
transitionSet.addTransition(sharedElementTransition);
+ Rect epicenter = calcEpicenter(sharedElements, activityOptions.getSharedElementNames());
+ FixedEpicenterCallback epicenterCallback = new FixedEpicenterCallback(epicenter);
+ transitionSet.setEpicenterCallback(epicenterCallback);
+
updateExitActivityOptions(activityOptions, sharedElements,
- sharedElementTransition, exitTransition);
+ sharedElementTransition, transitioningViews, exitTransition, epicenterCallback);
// Start exiting the Views that need to exit
TransitionManager.beginDelayedTransition(mDecor, transitionSet);
@@ -4133,6 +4194,14 @@
return activityOptions.toBundle();
}
+ private ArrayList<View> captureTransitioningViews(Collection<View> sharedElements) {
+ mTransitioningViews = new ArrayList<View>();
+ mDecor.captureTransitioningViews(mTransitioningViews);
+ ArrayList<View> transitioningViews = (ArrayList<View>) mTransitioningViews.clone();
+ transitioningViews.removeAll(sharedElements);
+ return transitioningViews;
+ }
+
private ArrayMap<String, View> findSharedElements(ActivityOptions activityOptions) {
ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
mDecor.findSharedElements(sharedElements);
@@ -4149,9 +4218,18 @@
return sharedElements;
}
+ private static void runOnUiThread(Handler handler, Runnable runnable) {
+ if (handler.getLooper() != Looper.myLooper()) {
+ handler.post(runnable);
+ } else {
+ runnable.run();
+ }
+ }
+
private void updateExitActivityOptions(ActivityOptions activityOptions,
final Map<String, View> sharedElements, Transition sharedElementTransition,
- Transition exitTransition) {
+ final ArrayList<View> transitioningViews, Transition exitTransition,
+ final Transition.EpicenterCallback epicenterCallback) {
// Schedule capturing of the shared element state
final Bundle sharedElementArgs = new Bundle();
@@ -4159,30 +4237,88 @@
ActivityOptions.SharedElementSource sharedElementSource
= new ActivityOptions.SharedElementSource() {
+ private Handler mHandler = new Handler();
+
@Override
public Bundle getSharedElementExitState() {
return sharedElementArgs;
}
@Override
- public void acceptedSharedElements(ArrayList<String> sharedElementNames) {
+ public void acceptedSharedElements(final ArrayList<String> sharedElementNames) {
if (sharedElementNames.size() == sharedElements.size()) {
return; // They were all accepted
}
- Transition transition = mTransitionManager.getExitTransition(mContentScene).clone();
- TransitionManager.beginDelayedTransition(mDecor, transition);
- for (String name: sharedElements.keySet()) {
- if (!sharedElementNames.contains(name)) {
- sharedElements.get(name).setVisibility(View.INVISIBLE);
+ runOnUiThread(mHandler, new Runnable() {
+ @Override
+ public void run() {
+ Transition transition = mTransitionManager.getExitTransition(mContentScene);
+ transition = transition.clone();
+ transition.setEpicenterCallback(epicenterCallback);
+ TransitionManager.beginDelayedTransition(mDecor, transition);
+ for (String name : sharedElements.keySet()) {
+ if (!sharedElementNames.contains(name)) {
+ sharedElements.get(name).setVisibility(View.INVISIBLE);
+ }
+ }
+ sharedElements.keySet().retainAll(sharedElementNames);
}
- }
- sharedElements.keySet().retainAll(sharedElementNames);
+ });
}
@Override
public void hideSharedElements() {
if (sharedElements != null) {
- setViewVisibility(sharedElements.values(), View.INVISIBLE);
+ runOnUiThread(mHandler, new Runnable() {
+ @Override
+ public void run() {
+ setViewVisibility(sharedElements.values(), View.INVISIBLE);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void restore(final Bundle sharedElementState) {
+ runOnUiThread(mHandler, new Runnable() {
+ @Override
+ public void run() {
+ mTransitioningViews = null;
+ Transition transition = mTransitionManager.getExitTransition(mContentScene);
+ transition = transition.clone();
+ transition.setEpicenterCallback(epicenterCallback);
+ setSharedElementState(sharedElements, sharedElementState);
+ setViewVisibility(sharedElements.values(), View.VISIBLE);
+ if (mSceneTransitionListener != null) {
+ mSceneTransitionListener.sharedElementStart(transition);
+ mDecor.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ mDecor.getViewTreeObserver().removeOnPreDrawListener(this);
+ mSceneTransitionListener.sharedElementEnd();
+ return true;
+ }
+ });
+ }
+ TransitionManager.beginDelayedTransition(mDecor, transition);
+ setViewVisibility(transitioningViews, View.VISIBLE);
+ for (View sharedElement: sharedElements.values()) {
+ sharedElement.requestLayout();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void prepareForRestore() {
+ if (mTransitioningViews != null) {
+ runOnUiThread(mHandler, new Runnable() {
+ @Override
+ public void run() {
+ setViewVisibility(mTransitioningViews, View.INVISIBLE);
+ }
+ });
}
}
};
@@ -4207,22 +4343,18 @@
});
}
- private static Transition cloneAndSetTransitionTargets(Transition transition,
- List<View> views, boolean add) {
- transition = transition.clone();
- if (!transition.getTargetIds().isEmpty() || !transition.getTargets().isEmpty()) {
- TransitionSet set = new TransitionSet();
- set.addTransition(transition);
- transition = set;
- }
+ private static Transition addTransitionTargets(Transition transition, Collection<View> views,
+ boolean add) {
+ TransitionSet set = new TransitionSet();
+ set.addTransition(transition.clone());
for (View view: views) {
if (add) {
- transition.addTarget(view);
+ set.addTarget(view);
} else {
- transition.excludeTarget(view, true);
+ set.excludeTarget(view, true);
}
}
- return transition;
+ return set;
}
private static void setViewVisibility(Collection<View> views, int visibility) {
@@ -4231,6 +4363,14 @@
}
}
+ private static void setSharedElementState(Map<String, View> sharedElements,
+ Bundle sharedElementState) {
+ int[] tempLoc = new int[2];
+ for (Map.Entry<String, View> entry: sharedElements.entrySet()) {
+ setSharedElementState(entry.getValue(), entry.getKey(), sharedElementState, tempLoc);
+ }
+ }
+
/**
* Sets the captured values from a previous
* {@link #captureSharedElementState(android.view.View, String, android.os.Bundle, int[])}
@@ -4247,23 +4387,27 @@
return;
}
- int x = sharedElementBundle.getInt(KEY_SCREEN_X);
- view.getLocationOnScreen(tempLoc);
- int offsetX = x - tempLoc[0];
- view.offsetLeftAndRight(offsetX);
-
- int width = sharedElementBundle.getInt(KEY_WIDTH);
- view.setRight(view.getLeft() + width);
-
- int y = sharedElementBundle.getInt(KEY_SCREEN_Y);
- int offsetY = y - tempLoc[1];
- view.offsetTopAndBottom(offsetY);
-
- int height = sharedElementBundle.getInt(KEY_HEIGHT);
- view.setBottom(view.getTop() + height);
-
float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z);
view.setTranslationZ(z);
+
+ int x = sharedElementBundle.getInt(KEY_SCREEN_X);
+ int y = sharedElementBundle.getInt(KEY_SCREEN_Y);
+ int width = sharedElementBundle.getInt(KEY_WIDTH);
+ int height = sharedElementBundle.getInt(KEY_HEIGHT);
+
+ int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
+ int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
+ view.measure(widthSpec, heightSpec);
+
+ ViewGroup parent = (ViewGroup) view.getParent();
+ parent.getLocationOnScreen(tempLoc);
+ int left = x - tempLoc[0];
+ int top = y - tempLoc[1];
+ int right = left + width;
+ int bottom = top + height;
+ view.layout(left, top, right, bottom);
+
+ view.requestLayout();
}
/**
@@ -4297,6 +4441,124 @@
transitionArgs.putBundle(name, sharedElementBundle);
}
+ private void mapSharedElements(ArrayMap<String, View> sharedElements) {
+ ArrayList<String> sharedElementNames = mActivityOptions.getSharedElementNames();
+ if (sharedElementNames != null) {
+ mDecor.findSharedElements(sharedElements);
+ if (mSharedElementsMap != null) {
+ for (Map.Entry<String, String> entry : mSharedElementsMap.entrySet()) {
+ View sharedElement = sharedElements.remove(entry.getValue());
+ if (sharedElement != null) {
+ sharedElements.put(entry.getKey(), sharedElement);
+ }
+ }
+ }
+ sharedElements.keySet().retainAll(sharedElementNames);
+ }
+ }
+
+ private static Rect calcEpicenter(ArrayMap<String, View> sharedElements,
+ ArrayList<String> sharedElementNames) {
+ if (sharedElementNames != null) {
+ for (String name: sharedElementNames) {
+ if (name.startsWith("android:")) {
+ return null;
+ }
+ View view = sharedElements.get(name);
+ if (view != null) {
+ int[] loc = new int[2];
+ view.getLocationOnScreen(loc);
+ int left = loc[0] + Math.round(view.getTranslationX());
+ int top = loc[1] + Math.round(view.getTranslationY());
+ int right = left + view.getWidth();
+ int bottom = top + view.getHeight();
+ return new Rect(left, top, right, bottom);
+ }
+ }
+ }
+ return null;
+ }
+
+ private class ExitSceneBack extends Transition.TransitionListenerAdapter implements
+ Animator.AnimatorListener {
+ private boolean mExitTransitionComplete;
+ private boolean mBackgroundFadeComplete;
+ private boolean mOnCompleteExecuted;
+ private boolean mSharedElementTransitioned;
+ private Runnable mOnComplete;
+ private Bundle mSharedElementArgs;
+ private Collection<View> mSharedElements;
+
+ public ExitSceneBack(Runnable onComplete, Bundle sharedElementArgs,
+ Collection<View> sharedElements) {
+ mOnComplete = onComplete;
+ mSharedElementArgs = sharedElementArgs;
+ mSharedElements = sharedElements;
+ }
+
+ public void start(Transition exitTransition) {
+ if (mActivityOptions != null) {
+ mActivityOptions.dispatchPrepareRestore();
+ }
+ exitTransition.addListener(this);
+ Drawable background = mDecor.getBackground();
+ if (background != null) {
+ ObjectAnimator animator = ObjectAnimator.ofInt(background, "alpha", 0);
+ animator.addListener(this);
+ animator.start();
+ } else {
+ mBackgroundFadeComplete = true;
+ startCalledActivityEnter();
+ }
+ }
+
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ transition.removeListener(this);
+ mExitTransitionComplete = true;
+ notifyComplete();
+ if (!mAllowExitOverlap) {
+ startCalledActivityEnter();
+ }
+ }
+
+ private void notifyComplete() {
+ if (mExitTransitionComplete && mBackgroundFadeComplete
+ && mSharedElementTransitioned && !mOnCompleteExecuted) {
+ mOnComplete.run();
+ mSceneTransitionListener.nullPendingTransition();
+ mOnCompleteExecuted = true;
+ }
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mBackgroundFadeComplete = true;
+ if (mAllowExitOverlap) {
+ startCalledActivityEnter();
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ }
+
+ private void startCalledActivityEnter() {
+ mActivityOptions.dispatchRestore(mSharedElementArgs);
+ setViewVisibility(mSharedElements, View.INVISIBLE);
+ mSharedElementTransitioned = true;
+ notifyComplete();
+ }
+ }
+
/**
* Provides code for handling the Activity transition entering scene.
* When the first scene is laid out (onPreDraw), it makes views invisible.
@@ -4317,6 +4579,7 @@
private boolean mEnterTransitionStarted;
private ArrayMap<String, View> mSharedElementTargets = new ArrayMap<String, View>();
private ArrayList<View> mEnteringViews = new ArrayList<View>();
+ private Transition.EpicenterCallback mEpicenterCallback;
public EnterScene() {
mSceneTransitionListener.nullPendingTransition();
@@ -4335,33 +4598,25 @@
if (!mEnterTransitionStarted && mSceneTransitionListener != null) {
mEnterTransitionStarted = true;
mDecor.captureTransitioningViews(mEnteringViews);
- ArrayList<String> sharedElementNames = mActivityOptions.getSharedElementNames();
- if (sharedElementNames != null) {
- mDecor.findSharedElements(mSharedElementTargets);
- if (mSharedElementsMap != null) {
- for (Map.Entry<String, String> entry : mSharedElementsMap.entrySet()) {
- View sharedElement = mSharedElementTargets.remove(entry.getValue());
- if (sharedElement != null) {
- mSharedElementTargets.put(entry.getKey(), sharedElement);
- }
- }
- }
- mSharedElementTargets.keySet().retainAll(sharedElementNames);
- mEnteringViews.removeAll(mSharedElementTargets.values());
- }
+ mapSharedElements(mSharedElementTargets);
+ mEnteringViews.removeAll(mSharedElementTargets.values());
+ Rect epicenter = calcEpicenter(mSharedElementTargets,
+ mActivityOptions.getSharedElementNames());
+ mEpicenterCallback = new FixedEpicenterCallback(epicenter);
setViewVisibility(mEnteringViews, View.INVISIBLE);
setViewVisibility(mSharedElementTargets.values(), View.INVISIBLE);
- if (mTriggerEarly) {
+ if (mAllowEnterOverlap) {
beginEnterScene();
}
observer.addOnPreDrawListener(this);
+ return false;
} else {
mHandler.postDelayed(this, MAX_TRANSITION_START_WAIT);
mActivityOptions.dispatchSceneTransitionStarted(this,
new ArrayList<String>(mSharedElementTargets.keySet()));
+ return !mSharedElementReadyReceived;
}
- return true;
}
public void start() {
@@ -4375,43 +4630,48 @@
}
@Override
- public void sharedElementTransitionComplete(Bundle transitionArgs) {
+ public void sharedElementTransitionComplete(final Bundle transitionArgs) {
if (!mSharedElementReadyReceived) {
mSharedElementReadyReceived = true;
mHandler.removeCallbacks(this);
mHandler.postDelayed(this, MAX_TRANSITION_FINISH_WAIT);
if (!mSharedElementTargets.isEmpty()) {
- Transition transition = getTransitionManager().getEnterTransition(
- mContentScene);
- if (transition == null) {
- transition = TransitionManager.getDefaultTransition();
- }
- transition = transition.clone();
- if (transitionArgs == null) {
- TransitionManager.beginDelayedTransition(mDecor, transition);
- setViewVisibility(mSharedElementTargets.values(), View.VISIBLE);
- } else {
- int[] tempLoc = new int[2];
- for (Map.Entry<String, View> entry: mSharedElementTargets.entrySet()) {
- setSharedElementState(entry.getValue(), entry.getKey(), transitionArgs,
- tempLoc);
+ runOnUiThread(mHandler, new Runnable() {
+ @Override
+ public void run() {
+ Transition transition = getTransitionManager().getEnterTransition(
+ mContentScene);
+ if (transition == null) {
+ transition = TransitionManager.getDefaultTransition();
+ }
+ transition = addTransitionTargets(transition,
+ mSharedElementTargets.values(),
+ true);
+ transition.setEpicenterCallback(mEpicenterCallback);
+ if (transitionArgs == null) {
+ TransitionManager.beginDelayedTransition(mDecor, transition);
+ setViewVisibility(mSharedElementTargets.values(), View.VISIBLE);
+ } else {
+ mSceneTransitionListener.sharedElementStart(transition);
+ setSharedElementState(mSharedElementTargets, transitionArgs);
+ setViewVisibility(mSharedElementTargets.values(), View.VISIBLE);
+ mDecor.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ mDecor.getViewTreeObserver()
+ .removeOnPreDrawListener(this);
+ mSceneTransitionListener.sharedElementEnd();
+ mActivityOptions.dispatchSharedElementsReady();
+ return true;
+ }
+ });
+ TransitionManager.beginDelayedTransition(mDecor, transition);
+ }
}
- setViewVisibility(mSharedElementTargets.values(), View.VISIBLE);
- mSceneTransitionListener.sharedElementStart(transition);
- mDecor.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- mDecor.getViewTreeObserver().removeOnPreDrawListener(this);
- mSceneTransitionListener.sharedElementEnd();
- mActivityOptions.dispatchSharedElementsReady();
- return true;
- }
- });
- TransitionManager.beginDelayedTransition(mDecor, transition);
- }
+ });
}
- if (mTriggerEarly) {
+ if (mAllowEnterOverlap) {
fadeInBackground();
}
}
@@ -4436,9 +4696,14 @@
mAllDone = true;
sharedElementTransitionComplete(null);
mHandler.removeCallbacks(this);
- if (!mTriggerEarly) {
- beginEnterScene();
- fadeInBackground();
+ if (!mAllowEnterOverlap) {
+ runOnUiThread(mHandler, new Runnable() {
+ @Override
+ public void run() {
+ beginEnterScene();
+ fadeInBackground();
+ }
+ });
}
}
@@ -4462,10 +4727,25 @@
private void beginEnterScene() {
Transition transition = getTransitionManager().getEnterTransition(mContentScene);
if (transition == null) {
- transition = TransitionManager.getDefaultTransition().clone();
+ transition = TransitionManager.getDefaultTransition();
}
+ transition = addTransitionTargets(transition, mEnteringViews, true);
+ transition.setEpicenterCallback(mEpicenterCallback);
TransitionManager.beginDelayedTransition(mDecor, transition);
setViewVisibility(mEnteringViews, View.VISIBLE);
}
}
+
+ private static class FixedEpicenterCallback extends Transition.EpicenterCallback {
+ private Rect mEpicenter;
+
+ public FixedEpicenterCallback(Rect epicenter) {
+ mEpicenter = epicenter;
+ }
+
+ @Override
+ public Rect getEpicenter(Transition transition) {
+ return mEpicenter;
+ }
+ };
}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 57c2f92..0b688b6 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -4690,7 +4690,11 @@
// ----- Restore handling -----
- private boolean signaturesMatch(Signature[] storedSigs, PackageInfo target) {
+ static boolean signaturesMatch(Signature[] storedSigs, PackageInfo target) {
+ if (target == null) {
+ return false;
+ }
+
// If the target resides on the system partition, we allow it to restore
// data from the like-named package in a restore set even if the signatures
// do not match. (Unlike general applications, those flashed to the system
diff --git a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
index 495da88..39f2441 100644
--- a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
+++ b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
@@ -19,15 +19,19 @@
import android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
+import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.util.Slog;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
@@ -42,6 +46,8 @@
import java.util.List;
import java.util.Set;
+import java.util.Objects;
+
/**
* We back up the signatures of each package so that during a system restore,
* we can verify that the app whose data we think we have matches the app
@@ -58,6 +64,9 @@
// is stored using the package name as a key)
private static final String GLOBAL_METADATA_KEY = "@meta@";
+ // key under which we store the identity of the user's chosen default home app
+ private static final String DEFAULT_HOME_KEY = "@home@";
+
private List<PackageInfo> mAllPackages;
private PackageManager mPackageManager;
// version & signature info of each app in a restore set
@@ -68,7 +77,15 @@
private final HashSet<String> mExisting = new HashSet<String>();
private int mStoredSdkVersion;
private String mStoredIncrementalVersion;
+ private ComponentName mStoredHomeComponent;
+ private long mStoredHomeVersion;
+ private Signature[] mStoredHomeSigs;
+
private boolean mHasMetadata;
+ private ComponentName mRestoredHome;
+ private long mRestoredHomeVersion;
+ private String mRestoredHomeInstaller;
+ private Signature[] mRestoredHomeSignatures;
public class Metadata {
public int versionCode;
@@ -136,7 +153,50 @@
mExisting.clear();
}
+ long homeVersion = 0;
+ Signature[] homeSigs = null;
+ PackageInfo homeInfo = null;
+ String homeInstaller = null;
+ ComponentName home = getPreferredHomeComponent();
+ if (home != null) {
+ try {
+ homeInfo = mPackageManager.getPackageInfo(home.getPackageName(),
+ PackageManager.GET_SIGNATURES);
+ homeInstaller = mPackageManager.getInstallerPackageName(home.getPackageName());
+ homeVersion = homeInfo.versionCode;
+ homeSigs = homeInfo.signatures;
+ } catch (NameNotFoundException e) {
+ Slog.w(TAG, "Can't access preferred home info");
+ // proceed as though there were no preferred home set
+ home = null;
+ }
+ }
+
try {
+ // We need to push a new preferred-home-app record if:
+ // 1. the version of the home app has changed since our last backup;
+ // 2. the home app [or absence] we now use differs from the prior state,
+ // OR 3. it looks like we use the same home app + version as before, but
+ // the signatures don't match so we treat them as different apps.
+ final boolean needHomeBackup = (homeVersion != mStoredHomeVersion)
+ || Objects.equals(home, mStoredHomeComponent)
+ || (home != null
+ && !BackupManagerService.signaturesMatch(mStoredHomeSigs, homeInfo));
+ if (needHomeBackup) {
+ if (DEBUG) {
+ Slog.i(TAG, "Home preference changed; backing up new state " + home);
+ }
+ if (home != null) {
+ outputBufferStream.writeUTF(home.flattenToString());
+ outputBufferStream.writeLong(homeVersion);
+ outputBufferStream.writeUTF(homeInstaller);
+ writeSignatureArray(outputBufferStream, homeSigs);
+ writeEntity(data, DEFAULT_HOME_KEY, outputBuffer.toByteArray());
+ } else {
+ data.writeEntityHeader(DEFAULT_HOME_KEY, -1);
+ }
+ }
+
/*
* Global metadata:
*
@@ -146,6 +206,7 @@
* String incremental -- the incremental release name of the OS stored in
* the backup set.
*/
+ outputBuffer.reset();
if (!mExisting.contains(GLOBAL_METADATA_KEY)) {
if (DEBUG) Slog.v(TAG, "Storing global metadata key");
outputBufferStream.writeInt(Build.VERSION.SDK_INT);
@@ -238,7 +299,7 @@
}
// Finally, write the new state blob -- just the list of all apps we handled
- writeStateFile(mAllPackages, newState);
+ writeStateFile(mAllPackages, home, homeVersion, homeSigs, newState);
}
private static void writeEntity(BackupDataOutput data, String key, byte[] bytes)
@@ -286,6 +347,19 @@
+ " (" + mStoredIncrementalVersion + " vs "
+ Build.VERSION.INCREMENTAL + ")");
}
+ } else if (key.equals(DEFAULT_HOME_KEY)) {
+ String cn = inputBufferStream.readUTF();
+ mRestoredHome = ComponentName.unflattenFromString(cn);
+ mRestoredHomeVersion = inputBufferStream.readLong();
+ mRestoredHomeInstaller = inputBufferStream.readUTF();
+ mRestoredHomeSignatures = readSignatureArray(inputBufferStream);
+ if (DEBUG) {
+ Slog.i(TAG, " read preferred home app " + mRestoredHome
+ + " version=" + mRestoredHomeVersion
+ + " installer=" + mRestoredHomeVersion
+ + " sig=" + mRestoredHomeVersion);
+ }
+
} else {
// it's a file metadata record
int versionCode = inputBufferStream.readInt();
@@ -365,18 +439,34 @@
mStateVersions.clear();
mStoredSdkVersion = 0;
mStoredIncrementalVersion = null;
+ mStoredHomeComponent = null;
+ mStoredHomeVersion = 0;
+ mStoredHomeSigs = null;
// The state file is just the list of app names we have stored signatures for
// with the exception of the metadata block, to which is also appended the
// version numbers corresponding with the last time we wrote this PM block.
// If they mismatch the current system, we'll re-store the metadata key.
FileInputStream instream = new FileInputStream(stateFile.getFileDescriptor());
- DataInputStream in = new DataInputStream(instream);
+ BufferedInputStream inbuffer = new BufferedInputStream(instream);
+ DataInputStream in = new DataInputStream(inbuffer);
- int bufSize = 256;
- byte[] buf = new byte[bufSize];
try {
String pkg = in.readUTF();
+
+ // First comes the preferred home app data, if any, headed by the DEFAULT_HOME_KEY tag
+ if (pkg.equals(DEFAULT_HOME_KEY)) {
+ // flattened component name, version, signature of the home app
+ mStoredHomeComponent = ComponentName.unflattenFromString(in.readUTF());
+ mStoredHomeVersion = in.readLong();
+ mStoredHomeSigs = readSignatureArray(in);
+
+ pkg = in.readUTF(); // set up for the next block of state
+ } else {
+ // else no preferred home app on the ancestral device - fall through to the rest
+ }
+
+ // After (possible) home app data comes the global metadata block
if (pkg.equals(GLOBAL_METADATA_KEY)) {
mStoredSdkVersion = in.readInt();
mStoredIncrementalVersion = in.readUTF();
@@ -386,7 +476,7 @@
return;
}
- // The global metadata was first; now read all the apps
+ // The global metadata was last; now read all the apps
while (true) {
pkg = in.readUTF();
int versionCode = in.readInt();
@@ -401,13 +491,28 @@
}
}
- // Util: write out our new backup state file
- private void writeStateFile(List<PackageInfo> pkgs, ParcelFileDescriptor stateFile) {
- FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
- DataOutputStream out = new DataOutputStream(outstream);
+ private ComponentName getPreferredHomeComponent() {
+ return mPackageManager.getHomeActivities(new ArrayList<ResolveInfo>());
+ }
+ // Util: write out our new backup state file
+ private void writeStateFile(List<PackageInfo> pkgs, ComponentName preferredHome,
+ long homeVersion, Signature[] homeSignatures, ParcelFileDescriptor stateFile) {
+ FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
+ BufferedOutputStream outbuf = new BufferedOutputStream(outstream);
+ DataOutputStream out = new DataOutputStream(outbuf);
+
+ // by the time we get here we know we've done all our backing up
try {
- // by the time we get here we know we've stored the global metadata record
+ // If we remembered a preferred home app, record that
+ if (preferredHome != null) {
+ out.writeUTF(DEFAULT_HOME_KEY);
+ out.writeUTF(preferredHome.flattenToString());
+ out.writeLong(homeVersion);
+ writeSignatureArray(out, homeSignatures);
+ }
+
+ // Conclude with the metadata block
out.writeUTF(GLOBAL_METADATA_KEY);
out.writeInt(Build.VERSION.SDK_INT);
out.writeUTF(Build.VERSION.INCREMENTAL);
@@ -417,9 +522,10 @@
out.writeUTF(pkg.packageName);
out.writeInt(pkg.versionCode);
}
+
+ out.flush();
} catch (IOException e) {
Slog.e(TAG, "Unable to write package manager state file!");
- return;
}
}
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
index 3414b3e..b9f2ed9 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
@@ -51,6 +51,11 @@
}
@Override
+ public void powerHint(int hintId, int data) {
+ // pass for now.
+ }
+
+ @Override
public void crash(String arg0) throws RemoteException {
// pass for now.
}