Merge "Make FrameInfoVisualizer use an IRenderPipeline to draw"
diff --git a/api/current.txt b/api/current.txt
index a3d8131..564653a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4489,6 +4489,7 @@
method public void onTrimMemory(int);
method public void onViewCreated(android.view.View, android.os.Bundle);
method public void onViewStateRestored(android.os.Bundle);
+ method public void postponeEnterTransition();
method public void registerForContextMenu(android.view.View);
method public final void requestPermissions(java.lang.String[], int);
method public void setAllowEnterTransitionOverlap(boolean);
@@ -4514,6 +4515,7 @@
method public void startActivityForResult(android.content.Intent, int);
method public void startActivityForResult(android.content.Intent, int, android.os.Bundle);
method public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
+ method public void startPostponedEnterTransition();
method public void unregisterForContextMenu(android.view.View);
}
@@ -44025,8 +44027,8 @@
field public static final int ROTATION_ANIMATION_CHANGED = 4096; // 0x1000
field public static final int ROTATION_ANIMATION_CROSSFADE = 1; // 0x1
field public static final int ROTATION_ANIMATION_JUMPCUT = 2; // 0x2
- field public static final int ROTATION_ANIMATION_SEAMLESS = 3; // 0x3
field public static final int ROTATION_ANIMATION_ROTATE = 0; // 0x0
+ field public static final int ROTATION_ANIMATION_SEAMLESS = 3; // 0x3
field public static final int SCREEN_BRIGHTNESS_CHANGED = 2048; // 0x800
field public static final int SCREEN_ORIENTATION_CHANGED = 1024; // 0x400
field public static final int SOFT_INPUT_ADJUST_NOTHING = 48; // 0x30
diff --git a/api/system-current.txt b/api/system-current.txt
index b2f72fc..fd51a6a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -103,7 +103,6 @@
field public static final java.lang.String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS";
field public static final java.lang.String GET_ACCOUNTS_PRIVILEGED = "android.permission.GET_ACCOUNTS_PRIVILEGED";
field public static final java.lang.String GET_APP_OPS_STATS = "android.permission.GET_APP_OPS_STATS";
- field public static final java.lang.String GET_PACKAGE_IMPORTANCE = "android.permission.GET_PACKAGE_IMPORTANCE";
field public static final java.lang.String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE";
field public static final java.lang.String GET_PROCESS_STATE_AND_OOM_SCORE = "android.permission.GET_PROCESS_STATE_AND_OOM_SCORE";
field public static final deprecated java.lang.String GET_TASKS = "android.permission.GET_TASKS";
@@ -4634,6 +4633,7 @@
method public void onTrimMemory(int);
method public void onViewCreated(android.view.View, android.os.Bundle);
method public void onViewStateRestored(android.os.Bundle);
+ method public void postponeEnterTransition();
method public void registerForContextMenu(android.view.View);
method public final void requestPermissions(java.lang.String[], int);
method public void setAllowEnterTransitionOverlap(boolean);
@@ -4659,6 +4659,7 @@
method public void startActivityForResult(android.content.Intent, int);
method public void startActivityForResult(android.content.Intent, int, android.os.Bundle);
method public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
+ method public void startPostponedEnterTransition();
method public void unregisterForContextMenu(android.view.View);
}
@@ -47207,8 +47208,8 @@
field public static final int ROTATION_ANIMATION_CHANGED = 4096; // 0x1000
field public static final int ROTATION_ANIMATION_CROSSFADE = 1; // 0x1
field public static final int ROTATION_ANIMATION_JUMPCUT = 2; // 0x2
- field public static final int ROTATION_ANIMATION_SEAMLESS = 3; // 0x3
field public static final int ROTATION_ANIMATION_ROTATE = 0; // 0x0
+ field public static final int ROTATION_ANIMATION_SEAMLESS = 3; // 0x3
field public static final int SCREEN_BRIGHTNESS_CHANGED = 2048; // 0x800
field public static final int SCREEN_ORIENTATION_CHANGED = 1024; // 0x400
field public static final int SOFT_INPUT_ADJUST_NOTHING = 48; // 0x30
diff --git a/api/test-current.txt b/api/test-current.txt
index 22fd955..150fbfe 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -4492,6 +4492,7 @@
method public void onTrimMemory(int);
method public void onViewCreated(android.view.View, android.os.Bundle);
method public void onViewStateRestored(android.os.Bundle);
+ method public void postponeEnterTransition();
method public void registerForContextMenu(android.view.View);
method public final void requestPermissions(java.lang.String[], int);
method public void setAllowEnterTransitionOverlap(boolean);
@@ -4517,6 +4518,7 @@
method public void startActivityForResult(android.content.Intent, int);
method public void startActivityForResult(android.content.Intent, int, android.os.Bundle);
method public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
+ method public void startPostponedEnterTransition();
method public void unregisterForContextMenu(android.view.View);
}
@@ -44261,8 +44263,8 @@
field public static final int ROTATION_ANIMATION_CHANGED = 4096; // 0x1000
field public static final int ROTATION_ANIMATION_CROSSFADE = 1; // 0x1
field public static final int ROTATION_ANIMATION_JUMPCUT = 2; // 0x2
- field public static final int ROTATION_ANIMATION_SEAMLESS = 3; // 0x3
field public static final int ROTATION_ANIMATION_ROTATE = 0; // 0x0
+ field public static final int ROTATION_ANIMATION_SEAMLESS = 3; // 0x3
field public static final int SCREEN_BRIGHTNESS_CHANGED = 2048; // 0x800
field public static final int SCREEN_ORIENTATION_CHANGED = 1024; // 0x400
field public static final int SOFT_INPUT_ADJUST_NOTHING = 48; // 0x30
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 0d9be5f..4066f1c 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3045,6 +3045,7 @@
* @hide
*/
@SystemApi
+ @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
public int getPackageImportance(String packageName) {
try {
int procState = ActivityManagerNative.getDefault().getPackageProcessState(packageName,
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 4de5b42..4df1325 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1235,6 +1235,16 @@
return true;
}
+ case UPDATE_DISPLAY_OVERRIDE_CONFIGURATION_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ final Configuration config = Configuration.CREATOR.createFromParcel(data);
+ final int displayId = data.readInt();
+ final boolean updated = updateDisplayOverrideConfiguration(config, displayId);
+ reply.writeNoException();
+ reply.writeInt(updated ? 1 : 0);
+ return true;
+ }
+
case SET_REQUESTED_ORIENTATION_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
@@ -4608,8 +4618,7 @@
data.recycle();
return res;
}
- public boolean updateConfiguration(Configuration values) throws RemoteException
- {
+ public boolean updateConfiguration(Configuration values) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -4621,6 +4630,20 @@
reply.recycle();
return updated;
}
+ public boolean updateDisplayOverrideConfiguration(Configuration values, int displayId)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ values.writeToParcel(data, 0);
+ data.writeInt(displayId);
+ mRemote.transact(UPDATE_DISPLAY_OVERRIDE_CONFIGURATION_TRANSACTION, data, reply, 0);
+ reply.readException();
+ boolean updated = reply.readInt() == 1;
+ data.recycle();
+ reply.recycle();
+ return updated;
+ }
public void setRequestedOrientation(IBinder token, int requestedOrientation)
throws RemoteException {
Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index fbbfec3..e9a200f 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -978,6 +978,10 @@
sendMessage(H.DUMP_HEAP, dhd, managed ? 1 : 0, 0, true /*async*/);
}
+ public void attachAgent(String agent) {
+ sendMessage(H.ATTACH_AGENT, agent);
+ }
+
public void setSchedulingGroup(int group) {
// Note: do this immediately, since going into the foreground
// should happen regardless of what pending work we have to do
@@ -1429,6 +1433,7 @@
public static final int MULTI_WINDOW_MODE_CHANGED = 152;
public static final int PICTURE_IN_PICTURE_MODE_CHANGED = 153;
public static final int LOCAL_VOICE_INTERACTION_STARTED = 154;
+ public static final int ATTACH_AGENT = 155;
String codeToString(int code) {
if (DEBUG_MESSAGES) {
@@ -1485,6 +1490,7 @@
case MULTI_WINDOW_MODE_CHANGED: return "MULTI_WINDOW_MODE_CHANGED";
case PICTURE_IN_PICTURE_MODE_CHANGED: return "PICTURE_IN_PICTURE_MODE_CHANGED";
case LOCAL_VOICE_INTERACTION_STARTED: return "LOCAL_VOICE_INTERACTION_STARTED";
+ case ATTACH_AGENT: return "ATTACH_AGENT";
}
}
return Integer.toString(code);
@@ -1739,6 +1745,8 @@
case LOCAL_VOICE_INTERACTION_STARTED:
handleLocalVoiceInteractionStarted((IBinder) ((SomeArgs) msg.obj).arg1,
(IVoiceInteractor) ((SomeArgs) msg.obj).arg2);
+ case ATTACH_AGENT:
+ handleAttachAgent((String) msg.obj);
break;
}
Object obj = msg.obj;
@@ -3008,6 +3016,14 @@
}
}
+ static final void handleAttachAgent(String agent) {
+ try {
+ VMDebug.attachAgent(agent);
+ } catch (IOException e) {
+ Slog.e(TAG, "Attaching agent failed: " + agent);
+ }
+ }
+
private static final ThreadLocal<Intent> sCurrentBroadcastIntent = new ThreadLocal<Intent>();
/**
diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java
index c589466..cf794c5 100644
--- a/core/java/android/app/BackStackRecord.java
+++ b/core/java/android/app/BackStackRecord.java
@@ -16,22 +16,13 @@
package android.app;
-import android.content.Context;
-import android.graphics.Rect;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
-import android.transition.Transition;
-import android.transition.TransitionManager;
-import android.transition.TransitionSet;
-import android.util.ArrayMap;
import android.util.Log;
import android.util.LogWriter;
-import android.util.SparseArray;
import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
import com.android.internal.util.FastPrintWriter;
@@ -39,7 +30,6 @@
import java.io.PrintWriter;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
-import java.util.List;
final class BackStackState implements Parcelable {
final int[] mOps;
@@ -669,7 +659,7 @@
}
/**
- * Implementation of {@link FragmentManagerImpl.android.app.FragmentManagerImpl.OpGenerator}.
+ * Implementation of {@link android.app.FragmentManagerImpl.OpGenerator}.
* This operation is added to the list of pending actions during {@link #commit()}, and
* will be executed on the UI thread to run this FragmentTransaction.
*
@@ -691,6 +681,43 @@
return true;
}
+ boolean interactsWith(int containerId) {
+ final int numOps = mOps.size();
+ for (int opNum = 0; opNum < numOps; opNum++) {
+ final Op op = mOps.get(opNum);
+ if (op.fragment.mContainerId == containerId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean interactsWith(ArrayList<BackStackRecord> records, int startIndex, int endIndex) {
+ if (endIndex == startIndex) {
+ return false;
+ }
+ final int numOps = mOps.size();
+ int lastContainer = -1;
+ for (int opNum = 0; opNum < numOps; opNum++) {
+ final Op op = mOps.get(opNum);
+ final int container = op.fragment.mContainerId;
+ if (container != 0 && container != lastContainer) {
+ lastContainer = container;
+ for (int i = startIndex; i < endIndex; i++) {
+ BackStackRecord record = records.get(i);
+ final int numThoseOps = record.mOps.size();
+ for (int thoseOpIndex = 0; thoseOpIndex < numThoseOps; thoseOpIndex++) {
+ final Op thatOp = record.mOps.get(thoseOpIndex);
+ if (thatOp.fragment.mContainerId == container) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
/**
* Executes the operations contained within this transaction. The Fragment states will only
* be modified if optimizations are not allowed.
@@ -700,31 +727,30 @@
for (int opNum = 0; opNum < numOps; opNum++) {
final Op op = mOps.get(opNum);
final Fragment f = op.fragment;
- f.mNextTransition = mTransition;
- f.mNextTransitionStyle = mTransitionStyle;
+ f.setNextTransition(mTransition, mTransitionStyle);
switch (op.cmd) {
case OP_ADD:
- f.mNextAnim = op.enterAnim;
+ f.setNextAnim(op.enterAnim);
mManager.addFragment(f, false);
break;
case OP_REMOVE:
- f.mNextAnim = op.exitAnim;
+ f.setNextAnim(op.exitAnim);
mManager.removeFragment(f);
break;
case OP_HIDE:
- f.mNextAnim = op.exitAnim;
+ f.setNextAnim(op.exitAnim);
mManager.hideFragment(f);
break;
case OP_SHOW:
- f.mNextAnim = op.enterAnim;
+ f.setNextAnim(op.enterAnim);
mManager.showFragment(f);
break;
case OP_DETACH:
- f.mNextAnim = op.exitAnim;
+ f.setNextAnim(op.exitAnim);
mManager.detachFragment(f);
break;
case OP_ATTACH:
- f.mNextAnim = op.enterAnim;
+ f.setNextAnim(op.enterAnim);
mManager.attachFragment(f);
break;
default:
@@ -748,31 +774,30 @@
for (int opNum = mOps.size() - 1; opNum >= 0; opNum--) {
final Op op = mOps.get(opNum);
Fragment f = op.fragment;
- f.mNextTransition = FragmentManagerImpl.reverseTransit(mTransition);
- f.mNextTransitionStyle = mTransitionStyle;
+ f.setNextTransition(FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
switch (op.cmd) {
case OP_ADD:
- f.mNextAnim = op.popExitAnim;
+ f.setNextAnim(op.popExitAnim);
mManager.removeFragment(f);
break;
case OP_REMOVE:
- f.mNextAnim = op.popEnterAnim;
+ f.setNextAnim(op.popEnterAnim);
mManager.addFragment(f, false);
break;
case OP_HIDE:
- f.mNextAnim = op.popEnterAnim;
+ f.setNextAnim(op.popEnterAnim);
mManager.showFragment(f);
break;
case OP_SHOW:
- f.mNextAnim = op.popExitAnim;
+ f.setNextAnim(op.popExitAnim);
mManager.hideFragment(f);
break;
case OP_DETACH:
- f.mNextAnim = op.popEnterAnim;
+ f.setNextAnim(op.popEnterAnim);
mManager.attachFragment(f);
break;
case OP_ATTACH:
- f.mNextAnim = op.popExitAnim;
+ f.setNextAnim(op.popExitAnim);
mManager.detachFragment(f);
break;
default:
@@ -803,7 +828,7 @@
case OP_ADD:
case OP_ATTACH:
added.add(op.fragment);
- break;
+ break;
case OP_REMOVE:
case OP_DETACH:
added.remove(op.fragment);
@@ -844,846 +869,29 @@
}
}
- private static void setFirstOut(SparseArray<FragmentContainerTransition> transitioningFragments,
- Fragment fragment, boolean isPop) {
- if (fragment != null) {
- int containerId = fragment.mContainerId;
- if (containerId != 0 && !fragment.isHidden()) {
- FragmentContainerTransition fragments = transitioningFragments.get(containerId);
- if (fragment.isAdded() && fragment.getView() != null && (fragments == null ||
- fragments.firstOut == null)) {
- if (fragments == null) {
- fragments = new FragmentContainerTransition();
- transitioningFragments.put(containerId, fragments);
- }
- fragments.firstOut = fragment;
- fragments.firstOutIsPop = isPop;
- }
- if (fragments != null && fragments.lastIn == fragment) {
- fragments.lastIn = null;
- }
- }
- }
- }
-
- private void setLastIn(SparseArray<FragmentContainerTransition> transitioningFragments,
- Fragment fragment, boolean isPop) {
- if (fragment != null) {
- int containerId = fragment.mContainerId;
- if (containerId != 0) {
- FragmentContainerTransition fragments = transitioningFragments.get(containerId);
- if (!fragment.isAdded()) {
- if (fragments == null) {
- fragments = new FragmentContainerTransition();
- transitioningFragments.put(containerId, fragments);
- }
- fragments.lastIn = fragment;
- fragments.lastInIsPop = isPop;
- }
- if (fragments != null && fragments.firstOut == fragment) {
- fragments.firstOut = null;
- }
- }
-
- /**
- * Ensure that fragments that are entering are at least at the CREATED state
- * so that they may load Transitions using TransitionInflater.
- */
- if (fragment.mState < Fragment.CREATED && mManager.mCurState >= Fragment.CREATED &&
- mManager.mHost.getContext().getApplicationInfo().targetSdkVersion >=
- Build.VERSION_CODES.N && !mAllowOptimization) {
- mManager.makeActive(fragment);
- mManager.moveToState(fragment, Fragment.CREATED, 0, 0, false);
- }
- }
- }
-
- /**
- * Finds the first removed fragment and last added fragments when going forward.
- * If none of the fragments have transitions, then both lists will be empty.
- *
- * @param transitioningFragments Keyed on the container ID, the first fragments to be removed,
- * and last fragments to be added. This will be modified by
- * this method.
- */
- public void calculateFragments(
- SparseArray<FragmentContainerTransition> transitioningFragments) {
- if (!mManager.mContainer.onHasView()) {
- return; // nothing to see, so no transitions
- }
- final int numOps = mOps.size();
- for (int opNum = 0; opNum < numOps; opNum++) {
+ boolean isPostponed() {
+ for (int opNum = 0; opNum < mOps.size(); opNum++) {
final Op op = mOps.get(opNum);
- switch (op.cmd) {
- case OP_ADD:
- case OP_SHOW:
- case OP_ATTACH:
- setLastIn(transitioningFragments, op.fragment, false);
- break;
- case OP_REMOVE:
- case OP_HIDE:
- case OP_DETACH:
- setFirstOut(transitioningFragments, op.fragment, false);
- break;
- }
- }
- }
-
- /**
- * Finds the first removed fragment and last added fragments when popping the back stack.
- * If none of the fragments have transitions, then both lists will be empty.
- *
- * @param transitioningFragments Keyed on the container ID, the first fragments to be removed,
- * and last fragments to be added. This will be modified by
- * this method.
- */
- public void calculatePopFragments(
- SparseArray<FragmentContainerTransition> transitioningFragments) {
- if (!mManager.mContainer.onHasView()) {
- return; // nothing to see, so no transitions
- }
- final int numOps = mOps.size();
- for (int opNum = numOps - 1; opNum >= 0; opNum--) {
- final Op op = mOps.get(opNum);
- switch (op.cmd) {
- case OP_ADD:
- case OP_SHOW:
- case OP_ATTACH:
- setFirstOut(transitioningFragments, op.fragment, true);
- break;
- case OP_REMOVE:
- case OP_HIDE:
- case OP_DETACH:
- setLastIn(transitioningFragments, op.fragment, true);
- break;
- }
- }
- }
-
- /**
- * When custom fragment transitions are used, this sets up the state for each transition
- * and begins the transition. A different transition is started for each fragment container
- * and consists of up to 3 different transitions: the exit transition, a shared element
- * transition and an enter transition.
- *
- * <p>The exit transition operates against the leaf nodes of the first fragment
- * with a view that was removed. If no such fragment was removed, then no exit
- * transition is executed. The exit transition comes from the outgoing fragment.</p>
- *
- * <p>The enter transition operates against the last fragment that was added. If
- * that fragment does not have a view or no fragment was added, then no enter
- * transition is executed. The enter transition comes from the incoming fragment.</p>
- *
- * <p>The shared element transition operates against all views and comes either
- * from the outgoing fragment or the incoming fragment, depending on whether this
- * is going forward or popping the back stack. When going forward, the incoming
- * fragment's enter shared element transition is used, but when going back, the
- * outgoing fragment's return shared element transition is used. Shared element
- * transitions only operate if there is both an incoming and outgoing fragment.</p>
- *
- * @param containers The first in and last out fragments that are transitioning.
- * @return The TransitionState used to complete the operation of the transition
- * in {@link #setNameOverrides(android.app.BackStackRecord.TransitionState, java.util.ArrayList,
- * java.util.ArrayList)}.
- */
- TransitionState beginTransition(SparseArray<FragmentContainerTransition> containers) {
- TransitionState state = new TransitionState();
-
- // Adding a non-existent target view makes sure that the transitions don't target
- // any views by default. They'll only target the views we tell add. If we don't
- // add any, then no views will be targeted.
- state.nonExistentView = new View(mManager.mHost.getContext());
-
- final int numContainers = containers.size();
- for (int i = 0; i < numContainers; i++) {
- int containerId = containers.keyAt(i);
- FragmentContainerTransition containerTransition = containers.valueAt(i);
- configureTransitions(containerId, state, containerTransition);
- }
- return state;
- }
-
- private static Transition cloneTransition(Transition transition) {
- if (transition != null) {
- transition = transition.clone();
- }
- return transition;
- }
-
- private static Transition getEnterTransition(Fragment inFragment, boolean isPop) {
- if (inFragment == null) {
- return null;
- }
- return cloneTransition(isPop ? inFragment.getReenterTransition() :
- inFragment.getEnterTransition());
- }
-
- private static Transition getExitTransition(Fragment outFragment, boolean isPop) {
- if (outFragment == null) {
- return null;
- }
- return cloneTransition(isPop ? outFragment.getReturnTransition() :
- outFragment.getExitTransition());
- }
-
- private static TransitionSet getSharedElementTransition(Fragment inFragment,
- Fragment outFragment, boolean isPop) {
- if (inFragment == null || outFragment == null) {
- return null;
- }
- Transition transition = cloneTransition(isPop
- ? outFragment.getSharedElementReturnTransition()
- : inFragment.getSharedElementEnterTransition());
- if (transition == null) {
- return null;
- }
- TransitionSet transitionSet = new TransitionSet();
- transitionSet.addTransition(transition);
- return transitionSet;
- }
-
- private static ArrayList<View> captureExitingViews(Transition exitTransition,
- Fragment outFragment, ArrayMap<String, View> namedViews, View nonExistentView) {
- ArrayList<View> viewList = null;
- if (exitTransition != null) {
- viewList = new ArrayList<View>();
- View root = outFragment.getView();
- root.captureTransitioningViews(viewList);
- if (namedViews != null) {
- viewList.removeAll(namedViews.values());
- }
- if (!viewList.isEmpty()) {
- viewList.add(nonExistentView);
- addTargets(exitTransition, viewList);
- }
- }
- return viewList;
- }
-
- private ArrayMap<String, View> remapSharedElements(TransitionState state, Fragment outFragment,
- boolean isPop) {
- ArrayMap<String, View> namedViews = new ArrayMap<String, View>();
- if (mSharedElementSourceNames != null) {
- outFragment.getView().findNamedViews(namedViews);
- if (isPop) {
- namedViews.retainAll(mSharedElementTargetNames);
- } else {
- namedViews = remapNames(mSharedElementSourceNames, mSharedElementTargetNames,
- namedViews);
- }
- }
-
- if (isPop) {
- outFragment.mEnterTransitionCallback.onMapSharedElements(
- mSharedElementTargetNames, namedViews);
- setBackNameOverrides(state, namedViews, false);
- } else {
- outFragment.mExitTransitionCallback.onMapSharedElements(
- mSharedElementTargetNames, namedViews);
- setNameOverrides(state, namedViews, false);
- }
-
- return namedViews;
- }
-
- /**
- * Prepares the enter transition by adding a non-existent view to the transition's target list
- * and setting it epicenter callback. By adding a non-existent view to the target list,
- * we can prevent any view from being targeted at the beginning of the transition.
- * We will add to the views before the end state of the transition is captured so that the
- * views will appear. At the start of the transition, we clear the list of targets so that
- * we can restore the state of the transition and use it again.
- *
- * <p>The shared element transition maps its shared elements immediately prior to
- * capturing the final state of the Transition.</p>
- */
- private ArrayList<View> addTransitionTargets(final TransitionState state,
- final Transition enterTransition, final TransitionSet sharedElementTransition,
- final Transition exitTransition, final Transition overallTransition,
- final View container, final Fragment inFragment, final Fragment outFragment,
- final ArrayList<View> hiddenFragmentViews, final boolean isPop,
- final ArrayList<View> sharedElementTargets) {
- if (enterTransition == null && sharedElementTransition == null &&
- overallTransition == null) {
- return null;
- }
- final ArrayList<View> enteringViews = new ArrayList<View>();
- container.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- container.getViewTreeObserver().removeOnPreDrawListener(this);
-
- // Don't include any newly-hidden fragments in the transition.
- if (inFragment != null) {
- excludeHiddenFragments(hiddenFragmentViews, inFragment.mContainerId,
- overallTransition);
- }
-
- ArrayMap<String, View> namedViews = null;
- if (sharedElementTransition != null) {
- namedViews = mapSharedElementsIn(state, isPop, inFragment);
- removeTargets(sharedElementTransition, sharedElementTargets);
- // keep the nonExistentView as excluded so the list doesn't get emptied
- sharedElementTargets.remove(state.nonExistentView);
- excludeViews(exitTransition, sharedElementTransition,
- sharedElementTargets, false);
- excludeViews(enterTransition, sharedElementTransition,
- sharedElementTargets, false);
-
- setSharedElementTargets(sharedElementTransition,
- state.nonExistentView, namedViews, sharedElementTargets);
-
- setEpicenterIn(namedViews, state);
-
- callSharedElementEnd(state, inFragment, outFragment, isPop,
- namedViews);
- }
-
- if (enterTransition != null) {
- enterTransition.removeTarget(state.nonExistentView);
- View view = inFragment.getView();
- if (view != null) {
- view.captureTransitioningViews(enteringViews);
- if (namedViews != null) {
- enteringViews.removeAll(namedViews.values());
- }
- enteringViews.add(state.nonExistentView);
- // We added this earlier to prevent any views being targeted.
- addTargets(enterTransition, enteringViews);
- }
- setSharedElementEpicenter(enterTransition, state);
- }
-
- excludeViews(exitTransition, enterTransition, enteringViews, true);
- excludeViews(exitTransition, sharedElementTransition, sharedElementTargets,
- true);
- excludeViews(enterTransition, sharedElementTransition, sharedElementTargets,
- true);
- return true;
- }
- });
- return enteringViews;
- }
-
- private void callSharedElementEnd(TransitionState state, Fragment inFragment,
- Fragment outFragment, boolean isPop, ArrayMap<String, View> namedViews) {
- SharedElementCallback sharedElementCallback = isPop ?
- outFragment.mEnterTransitionCallback :
- inFragment.mEnterTransitionCallback;
- ArrayList<String> names = new ArrayList<String>(namedViews.keySet());
- ArrayList<View> views = new ArrayList<View>(namedViews.values());
- sharedElementCallback.onSharedElementEnd(names, views, null);
- }
-
- private void setEpicenterIn(ArrayMap<String, View> namedViews, TransitionState state) {
- if (mSharedElementTargetNames != null && !namedViews.isEmpty()) {
- // now we know the epicenter of the entering transition.
- View epicenter = namedViews
- .get(mSharedElementTargetNames.get(0));
- if (epicenter != null) {
- state.enteringEpicenterView = epicenter;
- }
- }
- }
-
- private ArrayMap<String, View> mapSharedElementsIn(TransitionState state,
- boolean isPop, Fragment inFragment) {
- // Now map the shared elements in the incoming fragment
- ArrayMap<String, View> namedViews = mapEnteringSharedElements(state, inFragment, isPop);
-
- // remap shared elements and set the name mapping used
- // in the shared element transition.
- if (isPop) {
- inFragment.mExitTransitionCallback.onMapSharedElements(
- mSharedElementTargetNames, namedViews);
- setBackNameOverrides(state, namedViews, true);
- } else {
- inFragment.mEnterTransitionCallback.onMapSharedElements(
- mSharedElementTargetNames, namedViews);
- setNameOverrides(state, namedViews, true);
- }
- return namedViews;
- }
-
- private static Transition mergeTransitions(Transition enterTransition,
- Transition exitTransition, Transition sharedElementTransition, Fragment inFragment,
- boolean isPop) {
- boolean overlap = true;
- if (enterTransition != null && exitTransition != null && inFragment != null) {
- overlap = isPop ? inFragment.getAllowReturnTransitionOverlap() :
- inFragment.getAllowEnterTransitionOverlap();
- }
-
- // Wrap the transitions. Explicit targets like in enter and exit will cause the
- // views to be targeted regardless of excluded views. If that happens, then the
- // excluded fragments views (hidden fragments) will still be in the transition.
-
- Transition transition;
- if (overlap) {
- // Regular transition -- do it all together
- TransitionSet transitionSet = new TransitionSet();
- if (enterTransition != null) {
- transitionSet.addTransition(enterTransition);
- }
- if (exitTransition != null) {
- transitionSet.addTransition(exitTransition);
- }
- if (sharedElementTransition != null) {
- transitionSet.addTransition(sharedElementTransition);
- }
- transition = transitionSet;
- } else {
- // First do exit, then enter, but allow shared element transition to happen
- // during both.
- Transition staggered = null;
- if (exitTransition != null && enterTransition != null) {
- staggered = new TransitionSet()
- .addTransition(exitTransition)
- .addTransition(enterTransition)
- .setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
- } else if (exitTransition != null) {
- staggered = exitTransition;
- } else if (enterTransition != null) {
- staggered = enterTransition;
- }
- if (sharedElementTransition != null) {
- TransitionSet together = new TransitionSet();
- if (staggered != null) {
- together.addTransition(staggered);
- }
- together.addTransition(sharedElementTransition);
- transition = together;
- } else {
- transition = staggered;
- }
- }
- return transition;
- }
-
- /**
- * Configures custom transitions for a specific fragment container.
- *
- * @param containerId The container ID of the fragments to configure the transition for.
- * @param state The Transition State keeping track of the executing transitions.
- * @param transitioningFragments The first out and last in fragments for the fragment container.
- */
- private void configureTransitions(int containerId, TransitionState state,
- FragmentContainerTransition transitioningFragments) {
- ViewGroup sceneRoot = (ViewGroup) mManager.mContainer.onFindViewById(containerId);
- if (sceneRoot != null) {
- final Fragment inFragment = transitioningFragments.lastIn;
- final Fragment outFragment = transitioningFragments.firstOut;
-
- Transition enterTransition =
- getEnterTransition(inFragment, transitioningFragments.lastInIsPop);
- TransitionSet sharedElementTransition = getSharedElementTransition(inFragment,
- outFragment, transitioningFragments.lastInIsPop);
- Transition exitTransition =
- getExitTransition(outFragment, transitioningFragments.firstOutIsPop);
-
- if (enterTransition == null && sharedElementTransition == null &&
- exitTransition == null) {
- return; // no transitions!
- }
- if (enterTransition != null) {
- enterTransition.addTarget(state.nonExistentView);
- }
- ArrayMap<String, View> namedViews = null;
- ArrayList<View> sharedElementTargets = new ArrayList<View>();
- if (sharedElementTransition != null) {
- namedViews = remapSharedElements(state, outFragment,
- transitioningFragments.firstOutIsPop);
- setSharedElementTargets(sharedElementTransition,
- state.nonExistentView, namedViews, sharedElementTargets);
-
- // Notify the start of the transition.
- SharedElementCallback callback = transitioningFragments.lastInIsPop ?
- outFragment.mEnterTransitionCallback :
- inFragment.mEnterTransitionCallback;
- ArrayList<String> names = new ArrayList<String>(namedViews.keySet());
- ArrayList<View> views = new ArrayList<View>(namedViews.values());
- callback.onSharedElementStart(names, views, null);
- }
-
- ArrayList<View> exitingViews = captureExitingViews(exitTransition, outFragment,
- namedViews, state.nonExistentView);
- if (exitingViews == null || exitingViews.isEmpty()) {
- exitTransition = null;
- }
- excludeViews(enterTransition, exitTransition, exitingViews, true);
- excludeViews(enterTransition, sharedElementTransition, sharedElementTargets, true);
- excludeViews(exitTransition, sharedElementTransition, sharedElementTargets, true);
-
- // Set the epicenter of the exit transition
- if (mSharedElementTargetNames != null && namedViews != null) {
- View epicenterView = namedViews.get(mSharedElementTargetNames.get(0));
- if (epicenterView != null) {
- if (exitTransition != null) {
- setEpicenter(exitTransition, epicenterView);
- }
- if (sharedElementTransition != null) {
- setEpicenter(sharedElementTransition, epicenterView);
- }
- }
- }
-
- Transition transition = mergeTransitions(enterTransition, exitTransition,
- sharedElementTransition, inFragment, transitioningFragments.lastInIsPop);
-
- if (transition != null) {
- ArrayList<View> hiddenFragments = new ArrayList<View>();
- ArrayList<View> enteringViews = addTransitionTargets(state, enterTransition,
- sharedElementTransition, exitTransition, transition, sceneRoot, inFragment,
- outFragment, hiddenFragments, transitioningFragments.lastInIsPop,
- sharedElementTargets);
-
- transition.setNameOverrides(state.nameOverrides);
- // We want to exclude hidden views later, so we need a non-null list in the
- // transition now.
- transition.excludeTarget(state.nonExistentView, true);
- // Now exclude all currently hidden fragments.
- excludeHiddenFragments(hiddenFragments, containerId, transition);
- TransitionManager.beginDelayedTransition(sceneRoot, transition);
- // Remove the view targeting after the transition starts
- removeTargetedViewsFromTransitions(sceneRoot, state.nonExistentView,
- enterTransition, enteringViews, exitTransition, exitingViews,
- sharedElementTransition, sharedElementTargets, transition,
- hiddenFragments);
- }
- }
- }
-
- /**
- * Finds all children of the shared elements and sets the wrapping TransitionSet
- * targets to point to those. It also limits transitions that have no targets to the
- * specific shared elements. This allows developers to target child views of the
- * shared elements specifically, but this doesn't happen by default.
- */
- private static void setSharedElementTargets(TransitionSet transition,
- View nonExistentView, ArrayMap<String, View> namedViews,
- ArrayList<View> sharedElementTargets) {
- sharedElementTargets.clear();
- sharedElementTargets.addAll(namedViews.values());
-
- final List<View> views = transition.getTargets();
- views.clear();
- final int count = sharedElementTargets.size();
- for (int i = 0; i < count; i++) {
- final View view = sharedElementTargets.get(i);
- bfsAddViewChildren(views, view);
- }
- sharedElementTargets.add(nonExistentView);
- addTargets(transition, sharedElementTargets);
- }
-
- /**
- * Uses a breadth-first scheme to add startView and all of its children to views.
- * It won't add a child if it is already in views.
- */
- private static void bfsAddViewChildren(final List<View> views, final View startView) {
- final int startIndex = views.size();
- if (containedBeforeIndex(views, startView, startIndex)) {
- return; // This child is already in the list, so all its children are also.
- }
- views.add(startView);
- for (int index = startIndex; index < views.size(); index++) {
- final View view = views.get(index);
- if (view instanceof ViewGroup) {
- ViewGroup viewGroup = (ViewGroup) view;
- final int childCount = viewGroup.getChildCount();
- for (int childIndex = 0; childIndex < childCount; childIndex++) {
- final View child = viewGroup.getChildAt(childIndex);
- if (!containedBeforeIndex(views, child, startIndex)) {
- views.add(child);
- }
- }
- }
- }
- }
-
- /**
- * Does a linear search through views for view, limited to maxIndex.
- */
- private static boolean containedBeforeIndex(final List<View> views, final View view,
- final int maxIndex) {
- for (int i = 0; i < maxIndex; i++) {
- if (views.get(i) == view) {
+ if (isFragmentPostponed(op)) {
return true;
}
}
return false;
}
- private static void excludeViews(Transition transition, Transition fromTransition,
- ArrayList<View> views, boolean exclude) {
- if (transition != null) {
- final int viewCount = fromTransition == null ? 0 : views.size();
- for (int i = 0; i < viewCount; i++) {
- transition.excludeTarget(views.get(i), exclude);
+ void setOnStartPostponedListener(Fragment.OnStartEnterTransitionListener listener) {
+ for (int opNum = 0; opNum < mOps.size(); opNum++) {
+ final Op op = mOps.get(opNum);
+ if (isFragmentPostponed(op)) {
+ op.fragment.setOnStartEnterTransitionListener(listener);
}
}
}
- /**
- * After the transition has started, remove all targets that we added to the transitions
- * so that the transitions are left in a clean state.
- */
- private void removeTargetedViewsFromTransitions(
- final ViewGroup sceneRoot, final View nonExistingView,
- final Transition enterTransition, final ArrayList<View> enteringViews,
- final Transition exitTransition, final ArrayList<View> exitingViews,
- final Transition sharedElementTransition, final ArrayList<View> sharedElementTargets,
- final Transition overallTransition, final ArrayList<View> hiddenViews) {
- if (overallTransition != null) {
- sceneRoot.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
- if (enterTransition != null) {
- removeTargets(enterTransition, enteringViews);
- excludeViews(enterTransition, exitTransition, exitingViews, false);
- excludeViews(enterTransition, sharedElementTransition, sharedElementTargets,
- false);
- }
- if (exitTransition != null) {
- removeTargets(exitTransition, exitingViews);
- excludeViews(exitTransition, enterTransition, enteringViews, false);
- excludeViews(exitTransition, sharedElementTransition, sharedElementTargets,
- false);
- }
- if (sharedElementTransition != null) {
- removeTargets(sharedElementTransition, sharedElementTargets);
- }
- int numViews = hiddenViews.size();
- for (int i = 0; i < numViews; i++) {
- overallTransition.excludeTarget(hiddenViews.get(i), false);
- }
- overallTransition.excludeTarget(nonExistingView, false);
- return true;
- }
- });
- }
- }
-
- /**
- * This method removes the views from transitions that target ONLY those views.
- * The views list should match those added in addTargets and should contain
- * one view that is not in the view hierarchy (state.nonExistentView).
- */
- public static void removeTargets(Transition transition, ArrayList<View> views) {
- if (transition instanceof TransitionSet) {
- TransitionSet set = (TransitionSet) transition;
- int numTransitions = set.getTransitionCount();
- for (int i = 0; i < numTransitions; i++) {
- Transition child = set.getTransitionAt(i);
- removeTargets(child, views);
- }
- } else if (!hasSimpleTarget(transition)) {
- List<View> targets = transition.getTargets();
- if (targets != null && targets.size() == views.size() &&
- targets.containsAll(views)) {
- // We have an exact match. We must have added these earlier in addTargets
- for (int i = views.size() - 1; i >= 0; i--) {
- transition.removeTarget(views.get(i));
- }
- }
- }
- }
-
- /**
- * This method adds views as targets to the transition, but only if the transition
- * doesn't already have a target. It is best for views to contain one View object
- * that does not exist in the view hierarchy (state.nonExistentView) so that
- * when they are removed later, a list match will suffice to remove the targets.
- * Otherwise, if you happened to have targeted the exact views for the transition,
- * the removeTargets call will remove them unexpectedly.
- */
- public static void addTargets(Transition transition, ArrayList<View> views) {
- if (transition instanceof TransitionSet) {
- TransitionSet set = (TransitionSet) transition;
- int numTransitions = set.getTransitionCount();
- for (int i = 0; i < numTransitions; i++) {
- Transition child = set.getTransitionAt(i);
- addTargets(child, views);
- }
- } else if (!hasSimpleTarget(transition)) {
- List<View> targets = transition.getTargets();
- if (isNullOrEmpty(targets)) {
- // We can just add the target views
- int numViews = views.size();
- for (int i = 0; i < numViews; i++) {
- transition.addTarget(views.get(i));
- }
- }
- }
- }
-
- private static boolean hasSimpleTarget(Transition transition) {
- return !isNullOrEmpty(transition.getTargetIds()) ||
- !isNullOrEmpty(transition.getTargetNames()) ||
- !isNullOrEmpty(transition.getTargetTypes());
- }
-
- private static boolean isNullOrEmpty(List list) {
- return list == null || list.isEmpty();
- }
-
- /**
- * Remaps a name-to-View map, substituting different names for keys.
- *
- * @param inMap A list of keys found in the map, in the order in toGoInMap
- * @param toGoInMap A list of keys to use for the new map, in the order of inMap
- * @param namedViews The current mapping
- * @return a new Map after it has been mapped with the new names as keys.
- */
- private static ArrayMap<String, View> remapNames(ArrayList<String> inMap,
- ArrayList<String> toGoInMap, ArrayMap<String, View> namedViews) {
- ArrayMap<String, View> remappedViews = new ArrayMap<String, View>();
- if (!namedViews.isEmpty()) {
- int numKeys = inMap.size();
- for (int i = 0; i < numKeys; i++) {
- View view = namedViews.get(inMap.get(i));
-
- if (view != null) {
- remappedViews.put(toGoInMap.get(i), view);
- }
- }
- }
- return remappedViews;
- }
-
- /**
- * Maps shared elements to views in the entering fragment.
- *
- * @param state The transition State as returned from {@link #beginTransition(
- * android.util.SparseArray, android.util.SparseArray, boolean)}.
- * @param inFragment The last fragment to be added.
- * @param isPop true if this is popping the back stack or false if this is a
- * forward operation.
- */
- private ArrayMap<String, View> mapEnteringSharedElements(TransitionState state,
- Fragment inFragment, boolean isPop) {
- ArrayMap<String, View> namedViews = new ArrayMap<String, View>();
- View root = inFragment.getView();
- if (root != null) {
- if (mSharedElementSourceNames != null) {
- root.findNamedViews(namedViews);
- if (isPop) {
- namedViews = remapNames(mSharedElementSourceNames,
- mSharedElementTargetNames, namedViews);
- } else {
- namedViews.retainAll(mSharedElementTargetNames);
- }
- }
- }
- return namedViews;
- }
-
- private void excludeHiddenFragments(final ArrayList<View> hiddenFragmentViews, int containerId,
- Transition transition) {
- if (mManager.mAdded != null) {
- for (int i = 0; i < mManager.mAdded.size(); i++) {
- Fragment fragment = mManager.mAdded.get(i);
- if (fragment.mView != null && fragment.mContainer != null &&
- fragment.mContainerId == containerId) {
- if (fragment.mHidden) {
- if (!hiddenFragmentViews.contains(fragment.mView)) {
- transition.excludeTarget(fragment.mView, true);
- hiddenFragmentViews.add(fragment.mView);
- }
- } else {
- transition.excludeTarget(fragment.mView, false);
- hiddenFragmentViews.remove(fragment.mView);
- }
- }
- }
- }
- }
-
- private static void setEpicenter(Transition transition, View view) {
- final Rect epicenter = new Rect();
- view.getBoundsOnScreen(epicenter);
-
- transition.setEpicenterCallback(new Transition.EpicenterCallback() {
- @Override
- public Rect onGetEpicenter(Transition transition) {
- return epicenter;
- }
- });
- }
-
- private void setSharedElementEpicenter(Transition transition, final TransitionState state) {
- transition.setEpicenterCallback(new Transition.EpicenterCallback() {
- private Rect mEpicenter;
-
- @Override
- public Rect onGetEpicenter(Transition transition) {
- if (mEpicenter == null && state.enteringEpicenterView != null) {
- mEpicenter = new Rect();
- state.enteringEpicenterView.getBoundsOnScreen(mEpicenter);
- }
- return mEpicenter;
- }
- });
- }
-
- private static void setNameOverride(ArrayMap<String, String> overrides,
- String source, String target) {
- if (source != null && target != null && !source.equals(target)) {
- for (int index = 0; index < overrides.size(); index++) {
- if (source.equals(overrides.valueAt(index))) {
- overrides.setValueAt(index, target);
- return;
- }
- }
- overrides.put(source, target);
- }
- }
-
- static void setNameOverrides(TransitionState state, ArrayList<String> sourceNames,
- ArrayList<String> targetNames) {
- if (sourceNames != null && targetNames != null) {
- for (int i = 0; i < sourceNames.size(); i++) {
- String source = sourceNames.get(i);
- String target = targetNames.get(i);
- setNameOverride(state.nameOverrides, source, target);
- }
- }
- }
-
- private void setBackNameOverrides(TransitionState state, ArrayMap<String, View> namedViews,
- boolean isEnd) {
- int targetCount = mSharedElementTargetNames == null ? 0 : mSharedElementTargetNames.size();
- int sourceCount = mSharedElementSourceNames == null ? 0 : mSharedElementSourceNames.size();
- final int count = Math.min(targetCount, sourceCount);
- for (int i = 0; i < count; i++) {
- String source = mSharedElementSourceNames.get(i);
- String originalTarget = mSharedElementTargetNames.get(i);
- View view = namedViews.get(originalTarget);
- if (view != null) {
- String target = view.getTransitionName();
- if (isEnd) {
- setNameOverride(state.nameOverrides, source, target);
- } else {
- setNameOverride(state.nameOverrides, target, source);
- }
- }
- }
- }
-
- private void setNameOverrides(TransitionState state, ArrayMap<String, View> namedViews,
- boolean isEnd) {
- int count = namedViews == null ? 0 : namedViews.size();
- for (int i = 0; i < count; i++) {
- String source = namedViews.keyAt(i);
- String target = namedViews.valueAt(i).getTransitionName();
- if (isEnd) {
- setNameOverride(state.nameOverrides, source, target);
- } else {
- setNameOverride(state.nameOverrides, target, source);
- }
- }
+ private static boolean isFragmentPostponed(Op op) {
+ final Fragment fragment = op.fragment;
+ return (fragment.mAdded && fragment.mView != null && !fragment.mDetached &&
+ !fragment.mHidden && fragment.isPostponed());
}
public String getName() {
@@ -1701,36 +909,4 @@
public boolean isEmpty() {
return mOps.isEmpty();
}
-
- public class TransitionState {
- public ArrayMap<String, String> nameOverrides = new ArrayMap<String, String>();
- public View enteringEpicenterView;
- public View nonExistentView;
- }
-
- /**
- * Tracks the last fragment added and first fragment removed for fragment transitions.
- * This also tracks which fragments are changed by push or pop transactions.
- */
- public static class FragmentContainerTransition {
- /**
- * The last fragment added/attached/shown in its container
- */
- public Fragment lastIn;
-
- /**
- * true when lastIn was added during a pop transaction or false if added with a push
- */
- public boolean lastInIsPop;
-
- /**
- * The first fragment with a View that was removed/detached/hidden in its container.
- */
- public Fragment firstOut;
-
- /**
- * true when firstOut was removed during a pop transaction or false otherwise
- */
- public boolean firstOutIsPop;
- }
}
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index b72b960..5d1cd3b 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -31,6 +31,8 @@
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
import android.transition.Transition;
@@ -375,15 +377,6 @@
int mState = INITIALIZING;
- // Non-null if the fragment's view hierarchy is currently animating away,
- // meaning we need to wait a bit on completely destroying it. This is the
- // animation that is running.
- Animator mAnimatingAway;
-
- // If mAnimatingAway != null, this is the state we should move to once the
- // animation is done.
- int mStateAfterAnimating;
-
// When instantiated from saved state, this is the saved state.
Bundle mSavedFragmentState;
SparseArray<Parcelable> mSavedViewState;
@@ -478,15 +471,6 @@
// Used to verify that subclasses call through to super class.
boolean mCalled;
- // If app has requested a specific animation, this is the one to use.
- int mNextAnim;
-
- // If app has requested a specific transition, this is the one to use.
- int mNextTransition;
-
- // If app has requested a specific transition style, this is the one to use.
- int mNextTransitionStyle;
-
// The parent container of the fragment after dynamically added to UI.
ViewGroup mContainer;
@@ -504,17 +488,15 @@
boolean mLoadersStarted;
boolean mCheckedForLoaderManager;
- private Transition mEnterTransition = null;
- private Transition mReturnTransition = USE_DEFAULT_TRANSITION;
- private Transition mExitTransition = null;
- private Transition mReenterTransition = USE_DEFAULT_TRANSITION;
- private Transition mSharedElementEnterTransition = null;
- private Transition mSharedElementReturnTransition = USE_DEFAULT_TRANSITION;
- private Boolean mAllowReturnTransitionOverlap;
- private Boolean mAllowEnterTransitionOverlap;
+ // The animation and transition information for the fragment. This will be null
+ // unless the elements are explicitly accessed and should remain null for Fragments
+ // without Views.
+ AnimationInfo mAnimationInfo;
- SharedElementCallback mEnterTransitionCallback = SharedElementCallback.NULL_CALLBACK;
- SharedElementCallback mExitTransitionCallback = SharedElementCallback.NULL_CALLBACK;
+ // True if the View was added, and its animation has yet to be run. This could
+ // also indicate that the fragment view hasn't been made visible, even if there is no
+ // animation for this fragment.
+ boolean mIsNewlyAdded;
// True if mHidden has been changed and the animation should be scheduled.
boolean mHiddenChanged;
@@ -1399,26 +1381,41 @@
TypedArray a = context.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.Fragment);
- mEnterTransition = loadTransition(context, a, mEnterTransition, null,
- com.android.internal.R.styleable.Fragment_fragmentEnterTransition);
- mReturnTransition = loadTransition(context, a, mReturnTransition, USE_DEFAULT_TRANSITION,
- com.android.internal.R.styleable.Fragment_fragmentReturnTransition);
- mExitTransition = loadTransition(context, a, mExitTransition, null,
- com.android.internal.R.styleable.Fragment_fragmentExitTransition);
- mReenterTransition = loadTransition(context, a, mReenterTransition, USE_DEFAULT_TRANSITION,
- com.android.internal.R.styleable.Fragment_fragmentReenterTransition);
- mSharedElementEnterTransition = loadTransition(context, a, mSharedElementEnterTransition,
- null, com.android.internal.R.styleable.Fragment_fragmentSharedElementEnterTransition);
- mSharedElementReturnTransition = loadTransition(context, a, mSharedElementReturnTransition,
+ setEnterTransition(loadTransition(context, a, getEnterTransition(), null,
+ com.android.internal.R.styleable.Fragment_fragmentEnterTransition));
+ setReturnTransition(loadTransition(context, a, getReturnTransition(),
USE_DEFAULT_TRANSITION,
- com.android.internal.R.styleable.Fragment_fragmentSharedElementReturnTransition);
- if (mAllowEnterTransitionOverlap == null) {
- mAllowEnterTransitionOverlap = a.getBoolean(
- com.android.internal.R.styleable.Fragment_fragmentAllowEnterTransitionOverlap, true);
+ com.android.internal.R.styleable.Fragment_fragmentReturnTransition));
+ setExitTransition(loadTransition(context, a, getExitTransition(), null,
+ com.android.internal.R.styleable.Fragment_fragmentExitTransition));
+
+ setReenterTransition(loadTransition(context, a, getReenterTransition(),
+ USE_DEFAULT_TRANSITION,
+ com.android.internal.R.styleable.Fragment_fragmentReenterTransition));
+ setSharedElementEnterTransition(loadTransition(context, a,
+ getSharedElementEnterTransition(), null,
+ com.android.internal.R.styleable.Fragment_fragmentSharedElementEnterTransition));
+ setSharedElementReturnTransition(loadTransition(context, a,
+ getSharedElementReturnTransition(), USE_DEFAULT_TRANSITION,
+ com.android.internal.R.styleable.Fragment_fragmentSharedElementReturnTransition));
+ boolean isEnterSet;
+ boolean isReturnSet;
+ if (mAnimationInfo == null) {
+ isEnterSet = false;
+ isReturnSet = false;
+ } else {
+ isEnterSet = mAnimationInfo.mAllowEnterTransitionOverlap != null;
+ isReturnSet = mAnimationInfo.mAllowReturnTransitionOverlap != null;
}
- if (mAllowReturnTransitionOverlap == null) {
- mAllowReturnTransitionOverlap = a.getBoolean(
- com.android.internal.R.styleable.Fragment_fragmentAllowReturnTransitionOverlap, true);
+ if (!isEnterSet) {
+ setAllowEnterTransitionOverlap(a.getBoolean(
+ com.android.internal.R.styleable.Fragment_fragmentAllowEnterTransitionOverlap,
+ true));
+ }
+ if (!isReturnSet) {
+ setAllowReturnTransitionOverlap(a.getBoolean(
+ com.android.internal.R.styleable.Fragment_fragmentAllowReturnTransitionOverlap,
+ true));
}
a.recycle();
@@ -1943,16 +1940,12 @@
*/
public void setEnterSharedElementCallback(SharedElementCallback callback) {
if (callback == null) {
+ if (mAnimationInfo == null) {
+ return; // already a null callback
+ }
callback = SharedElementCallback.NULL_CALLBACK;
}
- mEnterTransitionCallback = callback;
- }
-
- /**
- * @hide
- */
- public void setEnterSharedElementTransitionCallback(SharedElementCallback callback) {
- setEnterSharedElementCallback(callback);
+ ensureAnimationInfo().mEnterTransitionCallback = callback;
}
/**
@@ -1964,16 +1957,12 @@
*/
public void setExitSharedElementCallback(SharedElementCallback callback) {
if (callback == null) {
+ if (mAnimationInfo == null) {
+ return; // already a null callback
+ }
callback = SharedElementCallback.NULL_CALLBACK;
}
- mExitTransitionCallback = callback;
- }
-
- /**
- * @hide
- */
- public void setExitSharedElementTransitionCallback(SharedElementCallback callback) {
- setExitSharedElementCallback(callback);
+ ensureAnimationInfo().mExitTransitionCallback = callback;
}
/**
@@ -1988,7 +1977,9 @@
* @attr ref android.R.styleable#Fragment_fragmentEnterTransition
*/
public void setEnterTransition(Transition transition) {
- mEnterTransition = transition;
+ if (shouldChangeTransition(transition, null)) {
+ ensureAnimationInfo().mEnterTransition = transition;
+ }
}
/**
@@ -2002,7 +1993,10 @@
* @attr ref android.R.styleable#Fragment_fragmentEnterTransition
*/
public Transition getEnterTransition() {
- return mEnterTransition;
+ if (mAnimationInfo == null) {
+ return null;
+ }
+ return mAnimationInfo.mEnterTransition;
}
/**
@@ -2020,7 +2014,9 @@
* @attr ref android.R.styleable#Fragment_fragmentExitTransition
*/
public void setReturnTransition(Transition transition) {
- mReturnTransition = transition;
+ if (shouldChangeTransition(transition, USE_DEFAULT_TRANSITION)) {
+ ensureAnimationInfo().mReturnTransition = transition;
+ }
}
/**
@@ -2037,8 +2033,11 @@
* @attr ref android.R.styleable#Fragment_fragmentExitTransition
*/
public Transition getReturnTransition() {
- return mReturnTransition == USE_DEFAULT_TRANSITION ? getEnterTransition()
- : mReturnTransition;
+ if (mAnimationInfo == null) {
+ return null;
+ }
+ return mAnimationInfo.mReturnTransition == USE_DEFAULT_TRANSITION ? getEnterTransition()
+ : mAnimationInfo.mReturnTransition;
}
/**
@@ -2055,7 +2054,9 @@
* @attr ref android.R.styleable#Fragment_fragmentExitTransition
*/
public void setExitTransition(Transition transition) {
- mExitTransition = transition;
+ if (shouldChangeTransition(transition, null)) {
+ ensureAnimationInfo().mExitTransition = transition;
+ }
}
/**
@@ -2072,7 +2073,10 @@
* @attr ref android.R.styleable#Fragment_fragmentExitTransition
*/
public Transition getExitTransition() {
- return mExitTransition;
+ if (mAnimationInfo == null) {
+ return null;
+ }
+ return mAnimationInfo.mExitTransition;
}
/**
@@ -2089,7 +2093,9 @@
* @attr ref android.R.styleable#Fragment_fragmentReenterTransition
*/
public void setReenterTransition(Transition transition) {
- mReenterTransition = transition;
+ if (shouldChangeTransition(transition, USE_DEFAULT_TRANSITION)) {
+ ensureAnimationInfo().mReenterTransition = transition;
+ }
}
/**
@@ -2106,8 +2112,11 @@
* @attr ref android.R.styleable#Fragment_fragmentReenterTransition
*/
public Transition getReenterTransition() {
- return mReenterTransition == USE_DEFAULT_TRANSITION ? getExitTransition()
- : mReenterTransition;
+ if (mAnimationInfo == null) {
+ return null;
+ }
+ return mAnimationInfo.mReenterTransition == USE_DEFAULT_TRANSITION ? getExitTransition()
+ : mAnimationInfo.mReenterTransition;
}
/**
@@ -2121,7 +2130,9 @@
* @attr ref android.R.styleable#Fragment_fragmentSharedElementEnterTransition
*/
public void setSharedElementEnterTransition(Transition transition) {
- mSharedElementEnterTransition = transition;
+ if (shouldChangeTransition(transition, null)) {
+ ensureAnimationInfo().mSharedElementEnterTransition = transition;
+ }
}
/**
@@ -2135,7 +2146,10 @@
* @attr ref android.R.styleable#Fragment_fragmentSharedElementEnterTransition
*/
public Transition getSharedElementEnterTransition() {
- return mSharedElementEnterTransition;
+ if (mAnimationInfo == null) {
+ return null;
+ }
+ return mAnimationInfo.mSharedElementEnterTransition;
}
/**
@@ -2152,7 +2166,9 @@
* @attr ref android.R.styleable#Fragment_fragmentSharedElementReturnTransition
*/
public void setSharedElementReturnTransition(Transition transition) {
- mSharedElementReturnTransition = transition;
+ if (shouldChangeTransition(transition, USE_DEFAULT_TRANSITION)) {
+ ensureAnimationInfo().mSharedElementReturnTransition = transition;
+ }
}
/**
@@ -2169,8 +2185,12 @@
* @attr ref android.R.styleable#Fragment_fragmentSharedElementReturnTransition
*/
public Transition getSharedElementReturnTransition() {
- return mSharedElementReturnTransition == USE_DEFAULT_TRANSITION ?
- getSharedElementEnterTransition() : mSharedElementReturnTransition;
+ if (mAnimationInfo == null) {
+ return null;
+ }
+ return mAnimationInfo.mSharedElementReturnTransition == USE_DEFAULT_TRANSITION
+ ? getSharedElementEnterTransition()
+ : mAnimationInfo.mSharedElementReturnTransition;
}
/**
@@ -2183,7 +2203,7 @@
* @attr ref android.R.styleable#Fragment_fragmentAllowEnterTransitionOverlap
*/
public void setAllowEnterTransitionOverlap(boolean allow) {
- mAllowEnterTransitionOverlap = allow;
+ ensureAnimationInfo().mAllowEnterTransitionOverlap = allow;
}
/**
@@ -2196,7 +2216,8 @@
* @attr ref android.R.styleable#Fragment_fragmentAllowEnterTransitionOverlap
*/
public boolean getAllowEnterTransitionOverlap() {
- return (mAllowEnterTransitionOverlap == null) ? true : mAllowEnterTransitionOverlap;
+ return (mAnimationInfo == null || mAnimationInfo.mAllowEnterTransitionOverlap == null)
+ ? true : mAnimationInfo.mAllowEnterTransitionOverlap;
}
/**
@@ -2209,7 +2230,7 @@
* @attr ref android.R.styleable#Fragment_fragmentAllowReturnTransitionOverlap
*/
public void setAllowReturnTransitionOverlap(boolean allow) {
- mAllowReturnTransitionOverlap = allow;
+ ensureAnimationInfo().mAllowReturnTransitionOverlap = allow;
}
/**
@@ -2222,7 +2243,90 @@
* @attr ref android.R.styleable#Fragment_fragmentAllowReturnTransitionOverlap
*/
public boolean getAllowReturnTransitionOverlap() {
- return (mAllowReturnTransitionOverlap == null) ? true : mAllowReturnTransitionOverlap;
+ return (mAnimationInfo == null || mAnimationInfo.mAllowReturnTransitionOverlap == null)
+ ? true : mAnimationInfo.mAllowReturnTransitionOverlap;
+ }
+
+ /**
+ * Postpone the entering Fragment transition until {@link #startPostponedEnterTransition()}
+ * or {@link FragmentManager#executePendingTransactions()} has been called.
+ * <p>
+ * This method gives the Fragment the ability to delay Fragment animations
+ * until all data is loaded. Until then, the added, shown, and
+ * attached Fragments will be INVISIBLE and removed, hidden, and detached Fragments won't
+ * be have their Views removed. The transaction runs when all postponed added Fragments in the
+ * transaction have called {@link #startPostponedEnterTransition()}.
+ * <p>
+ * This method should be called before being added to the FragmentTransaction or
+ * in {@link #onCreate(Bundle), {@link #onAttach(Context)}, or
+ * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}}.
+ * {@link #startPostponedEnterTransition()} must be called to allow the Fragment to
+ * start the transitions.
+ * <p>
+ * When a FragmentTransaction is started that may affect a postponed FragmentTransaction,
+ * based on which containers are in their operations, the postponed FragmentTransaction
+ * will have its start triggered. The early triggering may result in faulty or nonexistent
+ * animations in the postponed transaction. FragmentTransactions that operate only on
+ * independent containers will not interfere with each other's postponement.
+ * <p>
+ * Calling postponeEnterTransition on Fragments with a null View will not postpone the
+ * transition. Likewise, postponement only works if FragmentTransaction optimizations are
+ * enabled.
+ *
+ * @see Activity#postponeEnterTransition()
+ * @see FragmentTransaction#setAllowOptimization(boolean)
+ */
+ public void postponeEnterTransition() {
+ ensureAnimationInfo().mEnterTransitionPostponed = true;
+ }
+
+ /**
+ * Begin postponed transitions after {@link #postponeEnterTransition()} was called.
+ * If postponeEnterTransition() was called, you must call startPostponedEnterTransition()
+ * or {@link FragmentManager#executePendingTransactions()} to complete the FragmentTransaction.
+ * If postponement was interrupted with {@link FragmentManager#executePendingTransactions()},
+ * before {@code startPostponedEnterTransition()}, animations may not run or may execute
+ * improperly.
+ *
+ * @see Activity#startPostponedEnterTransition()
+ */
+ public void startPostponedEnterTransition() {
+ if (mFragmentManager == null || mFragmentManager.mHost == null) {
+ ensureAnimationInfo().mEnterTransitionPostponed = false;
+ } else if (Looper.myLooper() != mFragmentManager.mHost.getHandler().getLooper()) {
+ mFragmentManager.mHost.getHandler().
+ postAtFrontOfQueue(this::callStartTransitionListener);
+ } else {
+ callStartTransitionListener();
+ }
+ }
+
+ /**
+ * Calls the start transition listener. This must be called on the UI thread.
+ */
+ private void callStartTransitionListener() {
+ final OnStartEnterTransitionListener listener;
+ if (mAnimationInfo == null) {
+ listener = null;
+ } else {
+ mAnimationInfo.mEnterTransitionPostponed = false;
+ listener = mAnimationInfo.mStartEnterTransitionListener;
+ mAnimationInfo.mStartEnterTransitionListener = null;
+ }
+ if (listener != null) {
+ listener.onStartEnterTransition();
+ }
+ }
+
+ /**
+ * Returns true if mAnimationInfo is not null or the transition differs from the default value.
+ * This is broken out to ensure mAnimationInfo is properly locked when checking.
+ */
+ private boolean shouldChangeTransition(Transition transition, Transition defaultValue) {
+ if (transition == defaultValue) {
+ return mAnimationInfo != null;
+ }
+ return true;
}
/**
@@ -2283,8 +2387,8 @@
writer.print(" mTargetRequestCode=");
writer.println(mTargetRequestCode);
}
- if (mNextAnim != 0) {
- writer.print(prefix); writer.print("mNextAnim="); writer.println(mNextAnim);
+ if (getNextAnim() != 0) {
+ writer.print(prefix); writer.print("mNextAnim="); writer.println(getNextAnim());
}
if (mContainer != null) {
writer.print(prefix); writer.print("mContainer="); writer.println(mContainer);
@@ -2292,10 +2396,11 @@
if (mView != null) {
writer.print(prefix); writer.print("mView="); writer.println(mView);
}
- if (mAnimatingAway != null) {
- writer.print(prefix); writer.print("mAnimatingAway="); writer.println(mAnimatingAway);
+ if (getAnimatingAway() != null) {
+ writer.print(prefix); writer.print("mAnimatingAway=");
+ writer.println(getAnimatingAway());
writer.print(prefix); writer.print("mStateAfterAnimating=");
- writer.println(mStateAfterAnimating);
+ writer.println(getStateAfterAnimating());
}
if (mLoaderManager != null) {
writer.print(prefix); writer.println("Loader Manager:");
@@ -2622,6 +2727,23 @@
}
}
+ void setOnStartEnterTransitionListener(OnStartEnterTransitionListener listener) {
+ ensureAnimationInfo();
+ if (listener == mAnimationInfo.mStartEnterTransitionListener) {
+ return;
+ }
+ if (listener != null && mAnimationInfo.mStartEnterTransitionListener != null) {
+ throw new IllegalStateException("Trying to set a replacement " +
+ "startPostponedEnterTransition on " + this);
+ }
+ if (mAnimationInfo.mEnterTransitionPostponed) {
+ mAnimationInfo.mStartEnterTransitionListener = listener;
+ }
+ if (listener != null) {
+ listener.startListening();
+ }
+ }
+
private static Transition loadTransition(Context context, TypedArray typedArray,
Transition currentValue, Transition defaultValue, int id) {
if (currentValue != defaultValue) {
@@ -2640,4 +2762,147 @@
return transition;
}
+ private AnimationInfo ensureAnimationInfo() {
+ if (mAnimationInfo == null) {
+ mAnimationInfo = new AnimationInfo();
+ }
+ return mAnimationInfo;
+ }
+
+ int getNextAnim() {
+ if (mAnimationInfo == null) {
+ return 0;
+ }
+ return mAnimationInfo.mNextAnim;
+ }
+
+ void setNextAnim(int animResourceId) {
+ if (mAnimationInfo == null && animResourceId == 0) {
+ return; // no change!
+ }
+ ensureAnimationInfo().mNextAnim = animResourceId;
+ }
+
+ int getNextTransition() {
+ if (mAnimationInfo == null) {
+ return 0;
+ }
+ return mAnimationInfo.mNextTransition;
+ }
+
+ void setNextTransition(int nextTransition, int nextTransitionStyle) {
+ if (mAnimationInfo == null && nextTransition == 0 && nextTransitionStyle == 0) {
+ return; // no change!
+ }
+ ensureAnimationInfo();
+ mAnimationInfo.mNextTransition = nextTransition;
+ mAnimationInfo.mNextTransitionStyle = nextTransitionStyle;
+ }
+
+ int getNextTransitionStyle() {
+ if (mAnimationInfo == null) {
+ return 0;
+ }
+ return mAnimationInfo.mNextTransitionStyle;
+ }
+
+ SharedElementCallback getEnterTransitionCallback() {
+ if (mAnimationInfo == null) {
+ return SharedElementCallback.NULL_CALLBACK;
+ }
+ return mAnimationInfo.mEnterTransitionCallback;
+ }
+
+ SharedElementCallback getExitTransitionCallback() {
+ if (mAnimationInfo == null) {
+ return SharedElementCallback.NULL_CALLBACK;
+ }
+ return mAnimationInfo.mExitTransitionCallback;
+ }
+
+ Animator getAnimatingAway() {
+ if (mAnimationInfo == null) {
+ return null;
+ }
+ return mAnimationInfo.mAnimatingAway;
+ }
+
+ void setAnimatingAway(Animator animator) {
+ ensureAnimationInfo().mAnimatingAway = animator;
+ }
+
+ int getStateAfterAnimating() {
+ if (mAnimationInfo == null) {
+ return 0;
+ }
+ return mAnimationInfo.mStateAfterAnimating;
+ }
+
+ void setStateAfterAnimating(int state) {
+ ensureAnimationInfo().mStateAfterAnimating = state;
+ }
+
+ boolean isPostponed() {
+ if (mAnimationInfo == null) {
+ return false;
+ }
+ return mAnimationInfo.mEnterTransitionPostponed;
+ }
+
+ /**
+ * Used internally to be notified when {@link #startPostponedEnterTransition()} has
+ * been called. This listener will only be called once and then be removed from the
+ * listeners.
+ */
+ interface OnStartEnterTransitionListener {
+ void onStartEnterTransition();
+ void startListening();
+ }
+
+ /**
+ * Contains all the animation and transition information for a fragment. This will only
+ * be instantiated for Fragments that have Views.
+ */
+ static class AnimationInfo {
+ // Non-null if the fragment's view hierarchy is currently animating away,
+ // meaning we need to wait a bit on completely destroying it. This is the
+ // animation that is running.
+ Animator mAnimatingAway;
+
+ // If mAnimatingAway != null, this is the state we should move to once the
+ // animation is done.
+ int mStateAfterAnimating;
+
+ // If app has requested a specific animation, this is the one to use.
+ int mNextAnim;
+
+ // If app has requested a specific transition, this is the one to use.
+ int mNextTransition;
+
+ // If app has requested a specific transition style, this is the one to use.
+ int mNextTransitionStyle;
+
+ private Transition mEnterTransition = null;
+ private Transition mReturnTransition = USE_DEFAULT_TRANSITION;
+ private Transition mExitTransition = null;
+ private Transition mReenterTransition = USE_DEFAULT_TRANSITION;
+ private Transition mSharedElementEnterTransition = null;
+ private Transition mSharedElementReturnTransition = USE_DEFAULT_TRANSITION;
+ private Boolean mAllowReturnTransitionOverlap;
+ private Boolean mAllowEnterTransitionOverlap;
+
+ SharedElementCallback mEnterTransitionCallback = SharedElementCallback.NULL_CALLBACK;
+ SharedElementCallback mExitTransitionCallback = SharedElementCallback.NULL_CALLBACK;
+
+ // True when postponeEnterTransition has been called and startPostponeEnterTransition
+ // hasn't been called yet.
+ boolean mEnterTransitionPostponed;
+
+ // Listener to wait for startPostponeEnterTransition. After being called, it will
+ // be set to null
+ OnStartEnterTransitionListener mStartEnterTransitionListener;
+
+ // True if the View was added, and its animation has yet to be run.
+ boolean mIsNewlyAdded;
+ }
}
diff --git a/core/java/android/app/FragmentHostCallback.java b/core/java/android/app/FragmentHostCallback.java
index d869168..7e415e9 100644
--- a/core/java/android/app/FragmentHostCallback.java
+++ b/core/java/android/app/FragmentHostCallback.java
@@ -54,7 +54,8 @@
private boolean mLoadersStarted;
public FragmentHostCallback(Context context, Handler handler, int windowAnimations) {
- this(null /*activity*/, context, handler, windowAnimations);
+ this((context instanceof Activity) ? (Activity)context : null, context,
+ chooseHandler(context, handler), windowAnimations);
}
FragmentHostCallback(Activity activity) {
@@ -70,6 +71,19 @@
}
/**
+ * Used internally in {@link #FragmentHostCallback(Context, Handler, int)} to choose
+ * the Activity's handler or the provided handler.
+ */
+ private static Handler chooseHandler(Context context, Handler handler) {
+ if (handler == null && context instanceof Activity) {
+ Activity activity = (Activity) context;
+ return activity.mHandler;
+ } else {
+ return handler;
+ }
+ }
+
+ /**
* Print internal state into the given stream.
*
* @param prefix Desired prefix to prepend at each line of output.
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index b2df1ac..9345a03 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -31,7 +31,6 @@
import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
-import android.transition.Transition;
import android.util.AttributeSet;
import android.util.DebugUtils;
import android.util.Log;
@@ -44,6 +43,7 @@
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+
import com.android.internal.util.FastPrintWriter;
import java.io.FileDescriptor;
@@ -160,6 +160,9 @@
* can call this function (only from the main thread) to do so. Note that
* all callbacks and other related behavior will be done from within this
* call, so be careful about where this is called from.
+ * <p>
+ * This also forces the start of any postponed Transactions where
+ * {@link Fragment#postponeEnterTransition()} has been called.
*
* @return Returns true if there were any pending transactions to be
* executed.
@@ -206,7 +209,7 @@
/**
* Like {@link #popBackStack()}, but performs the operation immediately
* inside of the call. This is like calling {@link #executePendingTransactions()}
- * afterwards.
+ * afterwards without forcing the start of postponed Transactions.
* @return Returns true if there was something popped, else false.
*/
public abstract boolean popBackStackImmediate();
@@ -229,7 +232,7 @@
/**
* Like {@link #popBackStack(String, int)}, but performs the operation immediately
* inside of the call. This is like calling {@link #executePendingTransactions()}
- * afterwards.
+ * afterwards without forcing the start of postponed Transactions.
* @return Returns true if there was something popped, else false.
*/
public abstract boolean popBackStackImmediate(String name, int flags);
@@ -253,7 +256,7 @@
/**
* Like {@link #popBackStack(int, int)}, but performs the operation immediately
* inside of the call. This is like calling {@link #executePendingTransactions()}
- * afterwards.
+ * afterwards without forcing the start of postponed Transactions.
* @return Returns true if there was something popped, else false.
*/
public abstract boolean popBackStackImmediate(int id, int flags);
@@ -474,13 +477,15 @@
// Temporary vars for optimizing execution of BackStackRecords:
ArrayList<BackStackRecord> mTmpRecords;
ArrayList<Boolean> mTmpIsPop;
- SparseArray<BackStackRecord.FragmentContainerTransition> mTmpFragmentsContainerTransitions;
ArrayList<Fragment> mTmpAddedFragments;
// Temporary vars for state save and restore.
Bundle mStateBundle = null;
SparseArray<Parcelable> mStateArray = null;
-
+
+ // Postponed transactions.
+ ArrayList<StartEnterTransitionListener> mPostponedTransactions;
+
Runnable mExecCommit = new Runnable() {
@Override
public void run() {
@@ -564,7 +569,9 @@
@Override
public boolean executePendingTransactions() {
- return execPendingActions();
+ boolean updates = execPendingActions();
+ forcePostponedTransactions();
+ return updates;
}
@Override
@@ -614,7 +621,7 @@
* @return true if the pop operation did anything or false otherwise.
*/
private boolean popBackStackImmediate(String name, int id, int flags) {
- executePendingTransactions();
+ execPendingActions();
ensureExecReady(true);
boolean executePop = popBackStackState(mTmpRecords, mTmpIsPop, name, id, flags);
@@ -831,14 +838,14 @@
Animator loadAnimator(Fragment fragment, int transit, boolean enter,
int transitionStyle) {
- Animator animObj = fragment.onCreateAnimator(transit, enter,
- fragment.mNextAnim);
+ Animator animObj = fragment.onCreateAnimator(transit, enter, fragment.getNextAnim());
if (animObj != null) {
return animObj;
}
- if (fragment.mNextAnim != 0) {
- Animator anim = AnimatorInflater.loadAnimator(mHost.getContext(), fragment.mNextAnim);
+ if (fragment.getNextAnim() != 0) {
+ Animator anim = AnimatorInflater.loadAnimator(mHost.getContext(),
+ fragment.getNextAnim());
if (anim != null) {
return anim;
}
@@ -914,13 +921,13 @@
if (f.mFromLayout && !f.mInLayout) {
return;
}
- if (f.mAnimatingAway != null) {
+ if (f.getAnimatingAway() != null) {
// The fragment is currently being animated... but! Now we
// want to move our state back up. Give up on waiting for the
// animation, move to whatever the final state should be once
// the animation is done, and then we can proceed from there.
- f.mAnimatingAway = null;
- moveToState(f, f.mStateAfterAnimating, 0, 0, true);
+ f.setAnimatingAway(null);
+ moveToState(f, f.getStateAfterAnimating(), 0, 0, true);
}
switch (f.mState) {
case Fragment.INITIALIZING:
@@ -1011,16 +1018,13 @@
if (f.mView != null) {
f.mView.setSaveFromParentEnabled(false);
if (container != null) {
- Animator anim = loadAnimator(f, transit, true,
- transitionStyle);
- if (anim != null) {
- anim.setTarget(f.mView);
- setHWLayerAnimListenerIfAlpha(f.mView, anim);
- anim.start();
- }
container.addView(f.mView);
+ f.mIsNewlyAdded = true;
}
- if (f.mHidden) f.mView.setVisibility(View.GONE);
+ if (f.mHidden) {
+ f.mView.setVisibility(View.GONE);
+ f.mIsNewlyAdded = false; // No animation required
+ }
f.onViewCreated(f.mView, f.mSavedFragmentState);
}
}
@@ -1075,7 +1079,8 @@
f.performDestroyView();
if (f.mView != null && f.mContainer != null) {
Animator anim = null;
- if (mCurState > Fragment.INITIALIZING && !mDestroyed) {
+ if (mCurState > Fragment.INITIALIZING && !mDestroyed &&
+ f.mView.getVisibility() == View.VISIBLE) {
anim = loadAnimator(f, transit, false,
transitionStyle);
}
@@ -1084,15 +1089,15 @@
final View view = f.mView;
final Fragment fragment = f;
container.startViewTransition(view);
- f.mAnimatingAway = anim;
- f.mStateAfterAnimating = newState;
+ f.setAnimatingAway(anim);
+ f.setStateAfterAnimating(newState);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator anim) {
container.endViewTransition(view);
- if (fragment.mAnimatingAway != null) {
- fragment.mAnimatingAway = null;
- moveToState(fragment, fragment.mStateAfterAnimating,
+ if (fragment.getAnimatingAway() != null) {
+ fragment.setAnimatingAway(null);
+ moveToState(fragment, fragment.getStateAfterAnimating(),
0, 0, false);
}
}
@@ -1110,24 +1115,24 @@
case Fragment.CREATED:
if (newState < Fragment.CREATED) {
if (mDestroyed) {
- if (f.mAnimatingAway != null) {
+ if (f.getAnimatingAway() != null) {
// The fragment's containing activity is
// being destroyed, but this fragment is
// currently animating away. Stop the
// animation right now -- it is not needed,
// and we can't wait any more on destroying
// the fragment.
- Animator anim = f.mAnimatingAway;
- f.mAnimatingAway = null;
+ Animator anim = f.getAnimatingAway();
+ f.setAnimatingAway(null);
anim.cancel();
}
}
- if (f.mAnimatingAway != null) {
+ if (f.getAnimatingAway() != null) {
// We are waiting for the fragment's view to finish
// animating away. Just make a note of the state
// the fragment now should move to once the animation
// is done.
- f.mStateAfterAnimating = newState;
+ f.setStateAfterAnimating(newState);
newState = Fragment.CREATED;
} else {
if (DEBUG) Log.v(TAG, "movefrom CREATED: " + f);
@@ -1175,8 +1180,8 @@
*/
void completeShowHideFragment(final Fragment fragment) {
if (fragment.mView != null) {
- Animator anim = loadAnimator(fragment, fragment.mNextTransition, !fragment.mHidden,
- fragment.mNextTransitionStyle);
+ Animator anim = loadAnimator(fragment, fragment.getNextTransition(), !fragment.mHidden,
+ fragment.getNextTransitionStyle());
if (anim != null) {
anim.setTarget(fragment.mView);
if (fragment.mHidden) {
@@ -1212,7 +1217,7 @@
*
* @param f The fragment to change.
*/
- void moveFragmentToExpectedState(Fragment f) {
+ void moveFragmentToExpectedState(final Fragment f) {
if (f == null) {
return;
}
@@ -1224,9 +1229,11 @@
nextState = Fragment.INITIALIZING;
}
}
- moveToState(f, nextState, f.mNextTransition, f.mNextTransitionStyle, false);
+
+ moveToState(f, nextState, f.getNextTransition(), f.getNextTransitionStyle(), false);
if (f.mView != null) {
+ // Move the view if it is out of order
Fragment underFragment = findFragmentUnder(f);
if (underFragment != null) {
final View underView = underFragment.mView;
@@ -1239,6 +1246,18 @@
container.addView(f.mView, underIndex);
}
}
+ if (f.mIsNewlyAdded && f.mContainer != null) {
+ // Make it visible and run the animations
+ f.mView.setVisibility(View.VISIBLE);
+ f.mIsNewlyAdded = false;
+ // run animations:
+ Animator anim = loadAnimator(f, f.getNextTransition(), true, f.getNextTransitionStyle());
+ if (anim != null) {
+ anim.setTarget(f.mView);
+ setHWLayerAnimListenerIfAlpha(f.mView, anim);
+ anim.start();
+ }
+ }
}
if (f.mHiddenChanged) {
completeShowHideFragment(f);
@@ -1270,7 +1289,7 @@
final int numActive = mActive.size();
for (int i = 0; i < numActive; i++) {
Fragment f = mActive.get(i);
- if (f != null && (f.mRemoving || f.mDetached)) {
+ if (f != null && (f.mRemoving || f.mDetached) && !f.mIsNewlyAdded) {
moveFragmentToExpectedState(f);
if (f.mLoaderManager != null) {
loadersRunning |= f.mLoaderManager.hasRunningLoaders();
@@ -1288,7 +1307,7 @@
}
}
}
-
+
void startPendingDeferredFragments() {
if (mActive == null) return;
@@ -1539,7 +1558,22 @@
mPendingActions = new ArrayList<>();
}
mPendingActions.add(action);
- if (mPendingActions.size() == 1) {
+ scheduleCommit();
+ }
+ }
+
+ /**
+ * Schedules the execution when one hasn't been scheduled already. This should happen
+ * the first time {@link #enqueueAction(OpGenerator, boolean)} is called or when
+ * a postponed transaction has been started with
+ * {@link Fragment#startPostponedEnterTransition()}
+ */
+ private void scheduleCommit() {
+ synchronized (this) {
+ boolean postponeReady =
+ mPostponedTransactions != null && !mPostponedTransactions.isEmpty();
+ boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1;
+ if (postponeReady || pendingReady) {
mHost.getHandler().removeCallbacks(mExecCommit);
mHost.getHandler().post(mExecCommit);
}
@@ -1625,6 +1659,7 @@
mTmpRecords = new ArrayList<>();
mTmpIsPop = new ArrayList<>();
}
+ executePostponedTransaction(null, null);
}
public void execSingleAction(OpGenerator action, boolean allowStateLoss) {
@@ -1674,6 +1709,40 @@
}
/**
+ * Complete the execution of transactions that have previously been postponed, but are
+ * now ready.
+ */
+ private void executePostponedTransaction(ArrayList<BackStackRecord> records,
+ ArrayList<Boolean> isRecordPop) {
+ int numPostponed = mPostponedTransactions == null ? 0 : mPostponedTransactions.size();
+ for (int i = 0; i < numPostponed; i++) {
+ StartEnterTransitionListener listener = mPostponedTransactions.get(i);
+ if (records != null && !listener.mIsBack) {
+ int index = records.indexOf(listener.mRecord);
+ if (index != -1 && isRecordPop.get(index)) {
+ listener.cancelTransaction();
+ continue;
+ }
+ }
+ if (listener.isReady() || (records != null &&
+ listener.mRecord.interactsWith(records, 0, records.size()))) {
+ mPostponedTransactions.remove(i);
+ i--;
+ numPostponed--;
+ int index;
+ if (records != null && !listener.mIsBack &&
+ (index = records.indexOf(listener.mRecord)) != -1 &&
+ isRecordPop.get(index)) {
+ // This is popping a postponed transaction
+ listener.cancelTransaction();
+ } else {
+ listener.completeTransaction();
+ }
+ }
+ }
+ }
+
+ /**
* Optimizes BackStackRecord operations. This method merges operations of proximate records
* that allow optimization. See {@link FragmentTransaction#setAllowOptimization(boolean)}.
* <p>
@@ -1696,6 +1765,9 @@
throw new IllegalStateException("Internal error with the back stack records");
}
+ // Force start of any postponed transactions that interact with scheduled transactions:
+ executePostponedTransaction(records, isRecordPop);
+
final int numRecords = records.size();
int startIndex = 0;
for (int recordNum = 0; recordNum < numRecords; recordNum++) {
@@ -1736,10 +1808,8 @@
boolean addToBackStack = false;
if (mTmpAddedFragments == null) {
mTmpAddedFragments = new ArrayList<>();
- mTmpFragmentsContainerTransitions = new SparseArray<>();
} else {
mTmpAddedFragments.clear();
- mTmpFragmentsContainerTransitions.clear();
}
if (mAdded != null) {
mTmpAddedFragments.addAll(mAdded);
@@ -1753,25 +1823,26 @@
final int bumpAmount = isPop ? -1 : 1;
record.bumpBackStackNesting(bumpAmount);
addToBackStack = addToBackStack || record.mAddToBackStack;
-
- if (mCurState >= Fragment.CREATED) {
- if (isPop) {
- record.calculatePopFragments(mTmpFragmentsContainerTransitions);
- } else {
- record.calculateFragments(mTmpFragmentsContainerTransitions);
- }
- }
}
mTmpAddedFragments.clear();
if (!allowOptimization) {
- startTransitions(records, isRecordPop, startIndex, endIndex);
+ FragmentTransition.startTransitions(this, records, isRecordPop, startIndex, endIndex,
+ false);
}
executeOps(records, isRecordPop, startIndex, endIndex);
+ int postponeIndex = endIndex;
if (allowOptimization) {
- moveFragmentsToAtLeastCreated();
- startTransitions(records, isRecordPop, startIndex, endIndex);
+ moveFragmentsToInvisible();
+ postponeIndex = postponePostponableTransactions(records, isRecordPop,
+ startIndex, endIndex);
+ }
+
+ if (postponeIndex != startIndex && allowOptimization) {
+ // need to run something now
+ FragmentTransition.startTransitions(this, records, isRecordPop, startIndex,
+ postponeIndex, true);
moveToState(mCurState);
}
@@ -1783,12 +1854,104 @@
record.mIndex = -1;
}
}
+
if (addToBackStack) {
reportBackStackChanged();
}
}
/**
+ * Examine all transactions and determine which ones are marked as postponed. Those will
+ * have their operations rolled back and moved to the end of the record list (up to endIndex).
+ * It will also add the postponed transaction to the queue.
+ *
+ * @param records A list of BackStackRecords that should be checked.
+ * @param isRecordPop The direction that these records are being run.
+ * @param startIndex The index of the first record in <code>records</code> to be checked
+ * @param endIndex One more than the final record index in <code>records</code> to be checked.
+ * @return The index of the first postponed transaction or endIndex if no transaction was
+ * postponed.
+ */
+ private int postponePostponableTransactions(ArrayList<BackStackRecord> records,
+ ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) {
+ int postponeIndex = endIndex;
+ for (int i = endIndex - 1; i >= startIndex; i--) {
+ final BackStackRecord record = records.get(i);
+ final boolean isPop = isRecordPop.get(i);
+ boolean isPostponed = record.isPostponed() &&
+ !record.interactsWith(records, i + 1, endIndex);
+ if (isPostponed) {
+ if (mPostponedTransactions == null) {
+ mPostponedTransactions = new ArrayList<>();
+ }
+ StartEnterTransitionListener listener =
+ new StartEnterTransitionListener(record, isPop);
+ mPostponedTransactions.add(listener);
+ record.setOnStartPostponedListener(listener);
+
+ // roll back the transaction
+ if (isPop) {
+ record.executeOps();
+ } else {
+ record.executePopOps();
+ }
+
+ // move to the end
+ postponeIndex--;
+ if (i != postponeIndex) {
+ records.remove(i);
+ records.add(postponeIndex, record);
+ }
+
+ // different views may be visible now
+ moveFragmentsToInvisible();
+ }
+ }
+ return postponeIndex;
+ }
+
+ /**
+ * When a postponed transaction is ready to be started, this completes the transaction,
+ * removing, hiding, or showing views as well as starting the animations and transitions.
+ * <p>
+ * {@code runtransitions} is set to false when the transaction postponement was interrupted
+ * abnormally -- normally by a new transaction being started that affects the postponed
+ * transaction.
+ *
+ * @param record The transaction to run
+ * @param isPop true if record is popping or false if it is adding
+ * @param runTransitions true if the fragment transition should be run or false otherwise.
+ * @param moveToState true if the state should be changed after executing the operations.
+ * This is false when the transaction is canceled when a postponed
+ * transaction is popped.
+ */
+ private void completeExecute(BackStackRecord record, boolean isPop, boolean runTransitions,
+ boolean moveToState) {
+ ArrayList<BackStackRecord> records = new ArrayList<>(1);
+ ArrayList<Boolean> isRecordPop = new ArrayList<>(1);
+ records.add(record);
+ isRecordPop.add(isPop);
+ executeOps(records, isRecordPop, 0, 1);
+ if (runTransitions) {
+ FragmentTransition.startTransitions(this, records, isRecordPop, 0, 1, true);
+ }
+ if (moveToState) {
+ moveToState(mCurState);
+ } else if (mActive != null) {
+ final int numActive = mActive.size();
+ for (int i = 0; i < numActive; i++) {
+ // Allow added fragments to be removed during the pop since we aren't going
+ // to move them to the final state with moveToState(mCurState).
+ Fragment fragment = mActive.get(i);
+ if (fragment.mView != null && fragment.mIsNewlyAdded &&
+ record.interactsWith(fragment.mContainerId)) {
+ fragment.mIsNewlyAdded = false;
+ }
+ }
+ }
+ }
+
+ /**
* Find a fragment within the fragment's container whose View should be below the passed
* fragment. {@code null} is returned when the fragment has no View or if there should be
* no fragment with a View below the given fragment.
@@ -1845,61 +2008,50 @@
}
/**
- * Prepares the fragments for Transitions and starts it. If the FragmentManager is not
- * at Fragment.CREATED, no transition will be run.
- *
- * This is explicitly for {@link Fragment#setEnterTransition(Transition)} and its siblings,
- * not for {@link FragmentTransaction#setTransition(int)}.
- *
- * @param records The entries to examine for transitions.
- * @param isRecordPop The direction that these records are being run.
- * @param startIndex The index of the first entry in records to run transitions for.
- * @param endIndex One past the index of the final entry in records to run transitions for.
+ * Ensure that fragments that are added are moved to at least the CREATED state.
+ * Any newly-added Views are made INVISIBLE so that the Transaction can be postponed
+ * with {@link Fragment#postponeEnterTransition()}.
*/
- private void startTransitions(ArrayList<BackStackRecord> records,
- ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) {
- if (mCurState >= Fragment.CREATED) {
- if (mTmpFragmentsContainerTransitions.size() != 0) {
- BackStackRecord record = records.get(0);
- BackStackRecord.TransitionState state =
- record.beginTransition(mTmpFragmentsContainerTransitions);
- if (state != null) {
- for (int i = startIndex + 1; i < endIndex - 1; i++) {
- final BackStackRecord nameRecord = records.get(i);
- final boolean isPop = isRecordPop.get(i);
- ArrayList<String> sourceNames = isPop
- ? nameRecord.mSharedElementTargetNames
- : record.mSharedElementSourceNames;
- ArrayList<String> targetNames = isPop
- ? nameRecord.mSharedElementSourceNames
- : record.mSharedElementTargetNames;
- BackStackRecord.setNameOverrides(state, sourceNames, targetNames);
- }
+ private void moveFragmentsToInvisible() {
+ if (mCurState < Fragment.CREATED) {
+ return;
+ }
+ // We want to leave the fragment in the started state
+ final int state = Math.min(mCurState, Fragment.STARTED);
+ final int numAdded = mAdded == null ? 0 : mAdded.size();
+ for (int i = 0; i < numAdded; i++) {
+ Fragment fragment = mAdded.get(i);
+ if (fragment.mState < state) {
+ moveToState(fragment, state, fragment.getNextAnim(), fragment.getNextTransition(), false);
+ if (fragment.mView != null && !fragment.mHidden && fragment.mIsNewlyAdded) {
+ fragment.mView.setVisibility(View.INVISIBLE);
}
- mTmpFragmentsContainerTransitions.clear();
}
}
}
/**
- * Ensure that fragments that are added are at least at the CREATED state
- * so that they may load Transitions using TransitionInflater. When the transaction
- * cannot be optimized, this is executed in
- * {@link BackStackRecord#setLastIn(SparseArray, SparseArray, Fragment)} instead. Prior to
- * N, this wasn't supported, so no out-of-order creation can be done for compatibility.
- * <p>
- * This won't change the state of the fragment manager, nor will it change the fragment's
- * state if the fragment manager isn't at least at the CREATED state.
+ * Starts all postponed transactions regardless of whether they are ready or not.
*/
- private void moveFragmentsToAtLeastCreated() {
- if (mCurState < Fragment.CREATED) {
- return;
+ private void forcePostponedTransactions() {
+ if (mPostponedTransactions != null) {
+ while (!mPostponedTransactions.isEmpty()) {
+ mPostponedTransactions.remove(0).completeTransaction();
+ }
}
- final int numAdded = mAdded == null ? 0 : mAdded.size();
- for (int i = 0; i < numAdded; i++) {
- Fragment fragment = mAdded.get(i);
- if (fragment.mState < Fragment.CREATED) {
- moveToState(fragment, Fragment.CREATED, 0, 0, false);
+ }
+
+ /**
+ * Ends the animations of fragments so that they immediately reach the end state.
+ * This is used prior to saving the state so that the correct state is saved.
+ */
+ private void endAnimatingAwayFragments() {
+ final int numFragments = mActive == null ? 0 : mActive.size();
+ for (int i = 0; i < numFragments; i++) {
+ Fragment fragment = mActive.get(i);
+ if (fragment != null && fragment.getAnimatingAway() != null) {
+ // Give up waiting for the animation and just end it.
+ fragment.getAnimatingAway().end();
}
}
}
@@ -2114,6 +2266,8 @@
Parcelable saveAllState() {
// Make sure all pending operations have now been executed to get
// our state update-to-date.
+ forcePostponedTransactions();
+ endAnimatingAwayFragments();
execPendingActions();
mStateSaved = true;
@@ -2723,4 +2877,80 @@
return popBackStackState(records, isRecordPop, mName, mId, mFlags);
}
}
+
+ /**
+ * A listener for a postponed transaction. This waits until
+ * {@link Fragment#startPostponedEnterTransition()} is called or a transaction is started
+ * that interacts with this one, based on interactions with the fragment container.
+ */
+ static class StartEnterTransitionListener
+ implements Fragment.OnStartEnterTransitionListener {
+ private final boolean mIsBack;
+ private final BackStackRecord mRecord;
+ private int mNumPostponed;
+
+ public StartEnterTransitionListener(BackStackRecord record, boolean isBack) {
+ mIsBack = isBack;
+ mRecord = record;
+ }
+
+ /**
+ * Called from {@link Fragment#startPostponedEnterTransition()}, this decreases the
+ * number of Fragments that are postponed. This may cause the transaction to schedule
+ * to finish running and run transitions and animations.
+ */
+ @Override
+ public void onStartEnterTransition() {
+ mNumPostponed--;
+ if (mNumPostponed != 0) {
+ return;
+ }
+ mRecord.mManager.scheduleCommit();
+ }
+
+ /**
+ * Called from {@link Fragment#
+ * setOnStartEnterTransitionListener(Fragment.OnStartEnterTransitionListener)}, this
+ * increases the number of fragments that are postponed as part of this transaction.
+ */
+ @Override
+ public void startListening() {
+ mNumPostponed++;
+ }
+
+ /**
+ * @return true if there are no more postponed fragments as part of the transaction.
+ */
+ public boolean isReady() {
+ return mNumPostponed == 0;
+ }
+
+ /**
+ * Completes the transaction and start the animations and transitions. This may skip
+ * the transitions if this is called before all fragments have called
+ * {@link Fragment#startPostponedEnterTransition()}.
+ */
+ public void completeTransaction() {
+ final boolean canceled;
+ canceled = mNumPostponed > 0;
+ FragmentManagerImpl manager = mRecord.mManager;
+ final int numAdded = manager.mAdded.size();
+ for (int i = 0; i < numAdded; i++) {
+ final Fragment fragment = manager.mAdded.get(i);
+ fragment.setOnStartEnterTransitionListener(null);
+ if (canceled && fragment.isPostponed()) {
+ fragment.startPostponedEnterTransition();
+ }
+ }
+ mRecord.mManager.completeExecute(mRecord, mIsBack, !canceled, true);
+ }
+
+ /**
+ * Cancels this transaction instead of completing it. That means that the state isn't
+ * changed, so the pop results in no change to the state.
+ */
+ public void cancelTransaction() {
+ mRecord.mManager.completeExecute(mRecord, mIsBack, false, false);
+ }
+ }
}
diff --git a/core/java/android/app/FragmentTransition.java b/core/java/android/app/FragmentTransition.java
new file mode 100644
index 0000000..6f52114
--- /dev/null
+++ b/core/java/android/app/FragmentTransition.java
@@ -0,0 +1,1330 @@
+/*
+ * Copyright (C) 2016 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.app;
+
+import android.graphics.Rect;
+import android.os.Build;
+import android.transition.Transition;
+import android.transition.TransitionManager;
+import android.transition.TransitionSet;
+import android.util.ArrayMap;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Contains the Fragment Transition functionality for both optimized and unoptimized
+ * Fragment Transactions. With optimized fragment transactions, all Views have been
+ * added to the View hierarchy prior to calling startTransitions. With
+ */
+class FragmentTransition {
+ /**
+ * The inverse of all BackStackRecord operation commands. This assumes that
+ * REPLACE operations have already been replaced by add/remove operations.
+ */
+ private static final int[] INVERSE_OPS = {
+ BackStackRecord.OP_NULL, // inverse of OP_NULL (error)
+ BackStackRecord.OP_REMOVE, // inverse of OP_ADD
+ BackStackRecord.OP_NULL, // inverse of OP_REPLACE (error)
+ BackStackRecord.OP_ADD, // inverse of OP_REMOVE
+ BackStackRecord.OP_SHOW, // inverse of OP_HIDE
+ BackStackRecord.OP_HIDE, // inverse of OP_SHOW
+ BackStackRecord.OP_ATTACH, // inverse of OP_DETACH
+ BackStackRecord.OP_DETACH, // inverse of OP_ATTACH
+ };
+
+ /**
+ * The main entry point for Fragment Transitions, this starts the transitions
+ * set on the leaving Fragment's {@link Fragment#getExitTransition()}, the
+ * entering Fragment's {@link Fragment#getEnterTransition()} and
+ * {@link Fragment#getSharedElementEnterTransition()}. When popping,
+ * the leaving Fragment's {@link Fragment#getReturnTransition()} and
+ * {@link Fragment#getSharedElementReturnTransition()} and the entering
+ * {@link Fragment#getReenterTransition()} will be run.
+ * <p>
+ * With optimized Fragment Transitions, all Views have been added to the
+ * View hierarchy prior to calling this method. The incoming Fragment's Views
+ * will be INVISIBLE. With unoptimized Fragment Transitions, this method
+ * is called before any change has been made to the hierarchy. That means
+ * that the added Fragments have not created their Views yet and the hierarchy
+ * is unknown.
+ *
+ * @param fragmentManager The executing FragmentManagerImpl
+ * @param records The list of transactions being executed.
+ * @param isRecordPop For each transaction, whether it is a pop transaction or not.
+ * @param startIndex The first index into records and isRecordPop to execute as
+ * part of this transition.
+ * @param endIndex One past the last index into records and isRecordPop to execute
+ * as part of this transition.
+ * @param isOptimized true if this is an optimized transaction, meaning that the
+ * Views of incoming fragments have been added. false if the
+ * transaction has yet to be run and Views haven't been created.
+ */
+ static void startTransitions(FragmentManagerImpl fragmentManager,
+ ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop,
+ int startIndex, int endIndex, boolean isOptimized) {
+ if (fragmentManager.mCurState < Fragment.CREATED) {
+ return;
+ }
+ SparseArray<FragmentContainerTransition> transitioningFragments =
+ new SparseArray<>();
+ for (int i = startIndex; i < endIndex; i++) {
+ final BackStackRecord record = records.get(i);
+ final boolean isPop = isRecordPop.get(i);
+ if (isPop) {
+ calculatePopFragments(record, transitioningFragments, isOptimized);
+ } else {
+ calculateFragments(record, transitioningFragments, isOptimized);
+ }
+ }
+
+ if (transitioningFragments.size() != 0) {
+ final View nonExistentView = new View(fragmentManager.mHost.getContext());
+ final int numContainers = transitioningFragments.size();
+ for (int i = 0; i < numContainers; i++) {
+ int containerId = transitioningFragments.keyAt(i);
+ ArrayMap<String, String> nameOverrides = calculateNameOverrides(containerId,
+ records, isRecordPop, startIndex, endIndex);
+
+ FragmentContainerTransition containerTransition = transitioningFragments.valueAt(i);
+
+ if (isOptimized) {
+ configureTransitionsOptimized(fragmentManager, containerId,
+ containerTransition, nonExistentView, nameOverrides);
+ } else {
+ configureTransitionsUnoptimized(fragmentManager, containerId,
+ containerTransition, nonExistentView, nameOverrides);
+ }
+ }
+ }
+ }
+
+ /**
+ * Iterates through the transactions that affect a given fragment container
+ * and tracks the shared element names across transactions. This is most useful
+ * in pop transactions where the names of shared elements are known.
+ *
+ * @param containerId The container ID that is executing the transition.
+ * @param records The list of transactions being executed.
+ * @param isRecordPop For each transaction, whether it is a pop transaction or not.
+ * @param startIndex The first index into records and isRecordPop to execute as
+ * part of this transition.
+ * @param endIndex One past the last index into records and isRecordPop to execute
+ * as part of this transition.
+ * @return A map from the initial shared element name to the final shared element name
+ * before any onMapSharedElements is run.
+ */
+ private static ArrayMap<String, String> calculateNameOverrides(int containerId,
+ ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop,
+ int startIndex, int endIndex) {
+ ArrayMap<String, String> nameOverrides = new ArrayMap<>();
+ for (int recordNum = endIndex - 1; recordNum >= startIndex; recordNum--) {
+ final BackStackRecord record = records.get(recordNum);
+ if (!record.interactsWith(containerId)) {
+ continue;
+ }
+ final boolean isPop = isRecordPop.get(recordNum);
+ if (record.mSharedElementSourceNames != null) {
+ final int numSharedElements = record.mSharedElementSourceNames.size();
+ final ArrayList<String> sources;
+ final ArrayList<String> targets;
+ if (isPop) {
+ targets = record.mSharedElementSourceNames;
+ sources = record.mSharedElementTargetNames;
+ } else {
+ sources = record.mSharedElementSourceNames;
+ targets = record.mSharedElementTargetNames;
+ }
+ for (int i = 0; i < numSharedElements; i++) {
+ String sourceName = sources.get(i);
+ String targetName = targets.get(i);
+ String previousTarget = nameOverrides.remove(targetName);
+ if (previousTarget != null) {
+ nameOverrides.put(sourceName, previousTarget);
+ } else {
+ nameOverrides.put(sourceName, targetName);
+ }
+ }
+ }
+ }
+ return nameOverrides;
+ }
+
+ /**
+ * Configures a transition for a single fragment container for which the transaction was
+ * optimized. That means that all Fragment Views have been added and incoming fragment
+ * Views are marked invisible.
+ *
+ * @param fragmentManager The executing FragmentManagerImpl
+ * @param containerId The container ID that is executing the transition.
+ * @param fragments A structure holding the transitioning fragments in this container.
+ * @param nonExistentView A View that does not exist in the hierarchy. This is used to
+ * prevent transitions from acting on other Views when there is no
+ * other target.
+ * @param nameOverrides A map of the shared element names from the starting fragment to
+ * the final fragment's Views as given in
+ * {@link FragmentTransaction#addSharedElement(View, String)}.
+ */
+ private static void configureTransitionsOptimized(FragmentManagerImpl fragmentManager,
+ int containerId, FragmentContainerTransition fragments,
+ View nonExistentView, ArrayMap<String, String> nameOverrides) {
+ ViewGroup sceneRoot = (ViewGroup) fragmentManager.mContainer.onFindViewById(containerId);
+ if (sceneRoot == null) {
+ return;
+ }
+ final Fragment inFragment = fragments.lastIn;
+ final Fragment outFragment = fragments.firstOut;
+ final boolean inIsPop = fragments.lastInIsPop;
+ final boolean outIsPop = fragments.firstOutIsPop;
+
+ ArrayList<View> sharedElementsIn = new ArrayList<>();
+ ArrayList<View> sharedElementsOut = new ArrayList<>();
+ Transition enterTransition = getEnterTransition(inFragment, inIsPop);
+ Transition exitTransition = getExitTransition(outFragment, outIsPop);
+
+ TransitionSet sharedElementTransition = configureSharedElementsOptimized(sceneRoot,
+ nonExistentView, nameOverrides, fragments, sharedElementsOut, sharedElementsIn,
+ enterTransition, exitTransition);
+
+ if (enterTransition == null && sharedElementTransition == null &&
+ exitTransition == null) {
+ return; // no transitions!
+ }
+
+ ArrayList<View> exitingViews = configureEnteringExitingViews(exitTransition,
+ outFragment, sharedElementsOut, nonExistentView);
+
+ ArrayList<View> enteringViews = configureEnteringExitingViews(enterTransition,
+ inFragment, sharedElementsIn, nonExistentView);
+
+ setViewVisibility(enteringViews, View.INVISIBLE);
+
+ Transition transition = mergeTransitions(enterTransition, exitTransition,
+ sharedElementTransition, inFragment, inIsPop);
+
+ if (transition != null) {
+ transition.setNameOverrides(nameOverrides);
+ scheduleRemoveTargets(transition,
+ enterTransition, enteringViews, exitTransition, exitingViews,
+ sharedElementTransition, sharedElementsIn);
+ TransitionManager.beginDelayedTransition(sceneRoot, transition);
+ setViewVisibility(enteringViews, View.VISIBLE);
+ // Swap the shared element targets
+ if (sharedElementTransition != null) {
+ sharedElementTransition.getTargets().clear();
+ sharedElementTransition.getTargets().addAll(sharedElementsIn);
+ replaceTargets(sharedElementTransition, sharedElementsOut, sharedElementsIn);
+ }
+ }
+ }
+
+ /**
+ * Configures a transition for a single fragment container for which the transaction was
+ * not optimized. That means that the transaction has not been executed yet, so incoming
+ * Views are not yet known.
+ *
+ * @param fragmentManager The executing FragmentManagerImpl
+ * @param containerId The container ID that is executing the transition.
+ * @param fragments A structure holding the transitioning fragments in this container.
+ * @param nonExistentView A View that does not exist in the hierarchy. This is used to
+ * prevent transitions from acting on other Views when there is no
+ * other target.
+ * @param nameOverrides A map of the shared element names from the starting fragment to
+ * the final fragment's Views as given in
+ * {@link FragmentTransaction#addSharedElement(View, String)}.
+ */
+ private static void configureTransitionsUnoptimized(FragmentManagerImpl fragmentManager,
+ int containerId, FragmentContainerTransition fragments,
+ View nonExistentView, ArrayMap<String, String> nameOverrides) {
+ ViewGroup sceneRoot = (ViewGroup) fragmentManager.mContainer.onFindViewById(containerId);
+ if (sceneRoot == null) {
+ return;
+ }
+ final Fragment inFragment = fragments.lastIn;
+ final Fragment outFragment = fragments.firstOut;
+ final boolean inIsPop = fragments.lastInIsPop;
+ final boolean outIsPop = fragments.firstOutIsPop;
+
+ Transition enterTransition = getEnterTransition(inFragment, inIsPop);
+ Transition exitTransition = getExitTransition(outFragment, outIsPop);
+
+ ArrayList<View> sharedElementsOut = new ArrayList<>();
+ ArrayList<View> sharedElementsIn = new ArrayList<>();
+
+ TransitionSet sharedElementTransition = configureSharedElementsUnoptimized(sceneRoot,
+ nonExistentView, nameOverrides, fragments, sharedElementsOut, sharedElementsIn,
+ enterTransition, exitTransition);
+
+ if (enterTransition == null && sharedElementTransition == null &&
+ exitTransition == null) {
+ return; // no transitions!
+ }
+
+ ArrayList<View> exitingViews = configureEnteringExitingViews(exitTransition,
+ outFragment, sharedElementsOut, nonExistentView);
+
+ if (exitingViews == null || exitingViews.isEmpty()) {
+ exitTransition = null;
+ }
+
+ if (enterTransition != null) {
+ // Ensure the entering transition doesn't target anything until the views are made
+ // visible
+ enterTransition.addTarget(nonExistentView);
+ }
+
+ Transition transition = mergeTransitions(enterTransition, exitTransition,
+ sharedElementTransition, inFragment, fragments.lastInIsPop);
+
+ if (transition != null) {
+ transition.setNameOverrides(nameOverrides);
+ final ArrayList<View> enteringViews = new ArrayList<>();
+ scheduleRemoveTargets(transition,
+ enterTransition, enteringViews, exitTransition, exitingViews,
+ sharedElementTransition, sharedElementsIn);
+ scheduleTargetChange(sceneRoot, inFragment, nonExistentView, sharedElementsIn,
+ enterTransition, enteringViews, exitTransition, exitingViews);
+
+ TransitionManager.beginDelayedTransition(sceneRoot, transition);
+ }
+ }
+
+ /**
+ * This method is used for fragment transitions for unoptimized transactions to change the
+ * enter and exit transition targets after the call to
+ * {@link TransitionManager#beginDelayedTransition(ViewGroup, Transition)}. The exit transition
+ * must ensure that it does not target any Views and the enter transition must start targeting
+ * the Views of the incoming Fragment.
+ *
+ * @param sceneRoot The fragment container View
+ * @param inFragment The last fragment that is entering
+ * @param nonExistentView A view that does not exist in the hierarchy that is used as a
+ * transition target to ensure no View is targeted.
+ * @param sharedElementsIn The shared element Views of the incoming fragment
+ * @param enterTransition The enter transition of the incoming fragment
+ * @param enteringViews The entering Views of the incoming fragment
+ * @param exitTransition The exit transition of the outgoing fragment
+ * @param exitingViews The exiting views of the outgoing fragment
+ */
+ private static void scheduleTargetChange(final ViewGroup sceneRoot,
+ final Fragment inFragment, final View nonExistentView,
+ final ArrayList<View> sharedElementsIn,
+ final Transition enterTransition, final ArrayList<View> enteringViews,
+ final Transition exitTransition, final ArrayList<View> exitingViews) {
+
+ sceneRoot.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
+
+ if (enterTransition != null) {
+ enterTransition.removeTarget(nonExistentView);
+ ArrayList<View> views = configureEnteringExitingViews(
+ enterTransition, inFragment, sharedElementsIn, nonExistentView);
+ enteringViews.addAll(views);
+ }
+
+ if (exitingViews != null) {
+ ArrayList<View> tempExiting = new ArrayList<>();
+ tempExiting.add(nonExistentView);
+ replaceTargets(exitTransition, exitingViews, tempExiting);
+ exitingViews.clear();
+ exitingViews.add(nonExistentView);
+ }
+
+ return true;
+ }
+ });
+ }
+
+ /**
+ * Returns a TransitionSet containing the shared element transition. The wrapping TransitionSet
+ * targets all shared elements to ensure that no other Views are targeted. The shared element
+ * transition can then target any or all shared elements without worrying about accidentally
+ * targeting entering or exiting Views.
+ *
+ * @param inFragment The incoming fragment
+ * @param outFragment the outgoing fragment
+ * @param isPop True if this is a pop transaction or false if it is a normal (add) transaction.
+ * @return A TransitionSet wrapping the shared element transition or null if no such transition
+ * exists.
+ */
+ private static TransitionSet getSharedElementTransition(Fragment inFragment,
+ Fragment outFragment, boolean isPop) {
+ if (inFragment == null || outFragment == null) {
+ return null;
+ }
+ Transition transition = cloneTransition(isPop
+ ? outFragment.getSharedElementReturnTransition()
+ : inFragment.getSharedElementEnterTransition());
+ if (transition == null) {
+ return null;
+ }
+ TransitionSet transitionSet = new TransitionSet();
+ transitionSet.addTransition(transition);
+ return transitionSet;
+ }
+
+ /**
+ * Returns a clone of the enter transition or null if no such transition exists.
+ */
+ private static Transition getEnterTransition(Fragment inFragment, boolean isPop) {
+ if (inFragment == null) {
+ return null;
+ }
+ return cloneTransition(isPop ? inFragment.getReenterTransition() :
+ inFragment.getEnterTransition());
+ }
+
+ /**
+ * Returns a clone of the exit transition or null if no such transition exists.
+ */
+ private static Transition getExitTransition(Fragment outFragment, boolean isPop) {
+ if (outFragment == null) {
+ return null;
+ }
+ return cloneTransition(isPop ? outFragment.getReturnTransition() :
+ outFragment.getExitTransition());
+ }
+
+ /**
+ * Returns a clone of a transition or null if it is null
+ */
+ private static Transition cloneTransition(Transition transition) {
+ if (transition != null) {
+ transition = transition.clone();
+ }
+ return transition;
+ }
+
+ /**
+ * Configures the shared elements of an optimized fragment transaction's transition.
+ * This retrieves the shared elements of the outgoing and incoming fragments, maps the
+ * views, and sets up the epicenter on the transitions.
+ * <p>
+ * The epicenter of exit and shared element transitions is the first shared element
+ * in the outgoing fragment. The epicenter of the entering transition is the first shared
+ * element in the incoming fragment.
+ *
+ * @param sceneRoot The fragment container View
+ * @param nonExistentView A View that does not exist in the hierarchy. This is used to
+ * prevent transitions from acting on other Views when there is no
+ * other target.
+ * @param nameOverrides A map of the shared element names from the starting fragment to
+ * the final fragment's Views as given in
+ * {@link FragmentTransaction#addSharedElement(View, String)}.
+ * @param fragments A structure holding the transitioning fragments in this container.
+ * @param sharedElementsOut A list modified to contain the shared elements in the outgoing
+ * fragment
+ * @param sharedElementsIn A list modified to contain the shared elements in the incoming
+ * fragment
+ * @param enterTransition The transition used for entering Views, modified by applying the
+ * epicenter
+ * @param exitTransition The transition used for exiting Views, modified by applying the
+ * epicenter
+ * @return The shared element transition or null if no shared elements exist
+ */
+ private static TransitionSet configureSharedElementsOptimized(final ViewGroup sceneRoot,
+ final View nonExistentView, ArrayMap<String, String> nameOverrides,
+ final FragmentContainerTransition fragments,
+ final ArrayList<View> sharedElementsOut,
+ final ArrayList<View> sharedElementsIn,
+ final Transition enterTransition, final Transition exitTransition) {
+ final Fragment inFragment = fragments.lastIn;
+ final Fragment outFragment = fragments.firstOut;
+ if (inFragment != null) {
+ inFragment.getView().setVisibility(View.VISIBLE);
+ }
+ if (inFragment == null || outFragment == null) {
+ return null; // no shared element without a fragment
+ }
+
+ final boolean inIsPop = fragments.lastInIsPop;
+ TransitionSet sharedElementTransition = nameOverrides.isEmpty() ? null
+ : getSharedElementTransition(inFragment, outFragment, inIsPop);
+
+ ArrayMap<String, View> outSharedElements = captureOutSharedElements(nameOverrides,
+ sharedElementTransition, fragments);
+
+ ArrayMap<String, View> inSharedElements = captureInSharedElements(nameOverrides,
+ sharedElementTransition, fragments);
+
+ if (nameOverrides.isEmpty()) {
+ sharedElementTransition = null;
+ } else {
+ sharedElementsOut.addAll(outSharedElements.values());
+ sharedElementsIn.addAll(inSharedElements.values());
+ }
+
+ if (enterTransition == null && exitTransition == null && sharedElementTransition == null) {
+ // don't call onSharedElementStart/End since there is no transition
+ return null;
+ }
+
+ callSharedElementStartEnd(inFragment, outFragment, inIsPop, outSharedElements, true);
+
+ final Rect epicenter;
+ final View epicenterView;
+ if (sharedElementTransition != null) {
+ sharedElementsIn.add(nonExistentView);
+ setSharedElementTargets(sharedElementTransition, nonExistentView, sharedElementsOut);
+ final boolean outIsPop = fragments.firstOutIsPop;
+ final BackStackRecord outTransaction = fragments.firstOutTransaction;
+ setOutEpicenter(sharedElementTransition, exitTransition, outSharedElements, outIsPop,
+ outTransaction);
+ epicenter = new Rect();
+ epicenterView = getInEpicenterView(inSharedElements, fragments,
+ enterTransition, inIsPop);
+ if (epicenterView != null) {
+ enterTransition.setEpicenterCallback(new Transition.EpicenterCallback() {
+ @Override
+ public Rect onGetEpicenter(Transition transition) {
+ return epicenter;
+ }
+ });
+ }
+ } else {
+ epicenter = null;
+ epicenterView = null;
+ }
+
+ sceneRoot.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
+ callSharedElementStartEnd(inFragment, outFragment, inIsPop,
+ inSharedElements, false);
+ if (epicenterView != null) {
+ epicenterView.getBoundsOnScreen(epicenter);
+ }
+ return true;
+ }
+ });
+ return sharedElementTransition;
+ }
+
+ /**
+ * Configures the shared elements of an unoptimized fragment transaction's transition.
+ * This retrieves the shared elements of the incoming fragments, and schedules capturing
+ * the incoming fragment's shared elements. It also maps the views, and sets up the epicenter
+ * on the transitions.
+ * <p>
+ * The epicenter of exit and shared element transitions is the first shared element
+ * in the outgoing fragment. The epicenter of the entering transition is the first shared
+ * element in the incoming fragment.
+ *
+ * @param sceneRoot The fragment container View
+ * @param nonExistentView A View that does not exist in the hierarchy. This is used to
+ * prevent transitions from acting on other Views when there is no
+ * other target.
+ * @param nameOverrides A map of the shared element names from the starting fragment to
+ * the final fragment's Views as given in
+ * {@link FragmentTransaction#addSharedElement(View, String)}.
+ * @param fragments A structure holding the transitioning fragments in this container.
+ * @param sharedElementsOut A list modified to contain the shared elements in the outgoing
+ * fragment
+ * @param sharedElementsIn A list modified to contain the shared elements in the incoming
+ * fragment
+ * @param enterTransition The transition used for entering Views, modified by applying the
+ * epicenter
+ * @param exitTransition The transition used for exiting Views, modified by applying the
+ * epicenter
+ * @return The shared element transition or null if no shared elements exist
+ */
+ private static TransitionSet configureSharedElementsUnoptimized(final ViewGroup sceneRoot,
+ final View nonExistentView, ArrayMap<String, String> nameOverrides,
+ final FragmentContainerTransition fragments,
+ final ArrayList<View> sharedElementsOut,
+ final ArrayList<View> sharedElementsIn,
+ final Transition enterTransition, final Transition exitTransition) {
+ final Fragment inFragment = fragments.lastIn;
+ final Fragment outFragment = fragments.firstOut;
+
+ if (inFragment == null || outFragment == null) {
+ return null; // no transition
+ }
+
+ final boolean inIsPop = fragments.lastInIsPop;
+ TransitionSet sharedElementTransition = nameOverrides.isEmpty() ? null
+ : getSharedElementTransition(inFragment, outFragment, inIsPop);
+
+ ArrayMap<String, View> outSharedElements = captureOutSharedElements(nameOverrides,
+ sharedElementTransition, fragments);
+
+ if (nameOverrides.isEmpty()) {
+ sharedElementTransition = null;
+ } else {
+ sharedElementsOut.addAll(outSharedElements.values());
+ }
+
+ if (enterTransition == null && exitTransition == null && sharedElementTransition == null) {
+ // don't call onSharedElementStart/End since there is no transition
+ return null;
+ }
+
+ callSharedElementStartEnd(inFragment, outFragment, inIsPop, outSharedElements, true);
+
+ final Rect inEpicenter;
+ if (sharedElementTransition != null) {
+ inEpicenter = new Rect();
+ setSharedElementTargets(sharedElementTransition, nonExistentView, sharedElementsOut);
+ final boolean outIsPop = fragments.firstOutIsPop;
+ final BackStackRecord outTransaction = fragments.firstOutTransaction;
+ setOutEpicenter(sharedElementTransition, exitTransition, outSharedElements, outIsPop,
+ outTransaction);
+ if (enterTransition != null) {
+ enterTransition.setEpicenterCallback(new Transition.EpicenterCallback() {
+ @Override
+ public Rect onGetEpicenter(Transition transition) {
+ if (inEpicenter.isEmpty()) {
+ return null;
+ }
+ return inEpicenter;
+ }
+ });
+ }
+ } else {
+ inEpicenter = null;
+ }
+
+ TransitionSet finalSharedElementTransition = sharedElementTransition;
+
+ sceneRoot.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
+ ArrayMap<String, View> inSharedElements = captureInSharedElements(
+ nameOverrides, finalSharedElementTransition, fragments);
+
+ if (inSharedElements != null) {
+ sharedElementsIn.addAll(inSharedElements.values());
+ sharedElementsIn.add(nonExistentView);
+ }
+
+ callSharedElementStartEnd(inFragment, outFragment, inIsPop,
+ inSharedElements, false);
+ if (finalSharedElementTransition != null) {
+ finalSharedElementTransition.getTargets().clear();
+ finalSharedElementTransition.getTargets().addAll(sharedElementsIn);
+ replaceTargets(finalSharedElementTransition, sharedElementsOut,
+ sharedElementsIn);
+
+ final View inEpicenterView = getInEpicenterView(inSharedElements,
+ fragments, enterTransition, inIsPop);
+ if (inEpicenterView != null) {
+ inEpicenterView.getBoundsOnScreen(inEpicenter);
+ }
+ }
+ return true;
+ }
+ });
+ return sharedElementTransition;
+ }
+
+ /**
+ * Finds the shared elements in the outgoing fragment. It also calls
+ * {@link SharedElementCallback#onMapSharedElements(List, Map)} to allow more control
+ * of the shared element mapping. {@code nameOverrides} is updated to match the
+ * actual transition name of the mapped shared elements.
+ *
+ * @param nameOverrides A map of the shared element names from the starting fragment to
+ * the final fragment's Views as given in
+ * {@link FragmentTransaction#addSharedElement(View, String)}.
+ * @param sharedElementTransition The shared element transition
+ * @param fragments A structure holding the transitioning fragments in this container.
+ * @return The mapping of shared element names to the Views in the hierarchy or null
+ * if there is no shared element transition.
+ */
+ private static ArrayMap<String, View> captureOutSharedElements(
+ ArrayMap<String, String> nameOverrides, TransitionSet sharedElementTransition,
+ FragmentContainerTransition fragments) {
+ if (nameOverrides.isEmpty() || sharedElementTransition == null) {
+ nameOverrides.clear();
+ return null;
+ }
+ final Fragment outFragment = fragments.firstOut;
+ final ArrayMap<String, View> outSharedElements = new ArrayMap<>();
+ outFragment.getView().findNamedViews(outSharedElements);
+
+ final SharedElementCallback sharedElementCallback;
+ final ArrayList<String> names;
+ final BackStackRecord outTransaction = fragments.firstOutTransaction;
+ if (fragments.firstOutIsPop) {
+ sharedElementCallback = outFragment.getEnterTransitionCallback();
+ names = outTransaction.mSharedElementTargetNames;
+ } else {
+ sharedElementCallback = outFragment.getExitTransitionCallback();
+ names = outTransaction.mSharedElementSourceNames;
+ }
+
+ outSharedElements.retainAll(names);
+ if (sharedElementCallback != null) {
+ sharedElementCallback.onMapSharedElements(names, outSharedElements);
+ for (int i = names.size() - 1; i >= 0; i--) {
+ String name = names.get(i);
+ View view = outSharedElements.get(name);
+ if (view == null) {
+ nameOverrides.remove(name);
+ } else if (!name.equals(view.getTransitionName())) {
+ String targetValue = nameOverrides.remove(name);
+ nameOverrides.put(view.getTransitionName(), targetValue);
+ }
+ }
+ } else {
+ nameOverrides.retainAll(outSharedElements.keySet());
+ }
+ return outSharedElements;
+ }
+
+ /**
+ * Finds the shared elements in the incoming fragment. It also calls
+ * {@link SharedElementCallback#onMapSharedElements(List, Map)} to allow more control
+ * of the shared element mapping. {@code nameOverrides} is updated to match the
+ * actual transition name of the mapped shared elements.
+ *
+ * @param nameOverrides A map of the shared element names from the starting fragment to
+ * the final fragment's Views as given in
+ * {@link FragmentTransaction#addSharedElement(View, String)}.
+ * @param sharedElementTransition The shared element transition
+ * @param fragments A structure holding the transitioning fragments in this container.
+ * @return The mapping of shared element names to the Views in the hierarchy or null
+ * if there is no shared element transition.
+ */
+ private static ArrayMap<String, View> captureInSharedElements(
+ ArrayMap<String, String> nameOverrides, TransitionSet sharedElementTransition,
+ FragmentContainerTransition fragments) {
+ Fragment inFragment = fragments.lastIn;
+ final View fragmentView = inFragment.getView();
+ if (nameOverrides.isEmpty() || sharedElementTransition == null || fragmentView == null) {
+ nameOverrides.clear();
+ return null;
+ }
+ final ArrayMap<String, View> inSharedElements = new ArrayMap<>();
+ fragmentView.findNamedViews(inSharedElements);
+
+ final SharedElementCallback sharedElementCallback;
+ final ArrayList<String> names;
+ final BackStackRecord inTransaction = fragments.lastInTransaction;
+ if (fragments.lastInIsPop) {
+ sharedElementCallback = inFragment.getExitTransitionCallback();
+ names = inTransaction.mSharedElementSourceNames;
+ } else {
+ sharedElementCallback = inFragment.getEnterTransitionCallback();
+ names = inTransaction.mSharedElementTargetNames;
+ }
+
+ inSharedElements.retainAll(names);
+ if (sharedElementCallback != null) {
+ sharedElementCallback.onMapSharedElements(names, inSharedElements);
+ for (int i = names.size() - 1; i >= 0; i--) {
+ String name = names.get(i);
+ View view = inSharedElements.get(name);
+ if (view == null) {
+ String key = findKeyForValue(nameOverrides, name);
+ if (key != null) {
+ nameOverrides.remove(key);
+ }
+ } else if (!name.equals(view.getTransitionName())) {
+ String key = findKeyForValue(nameOverrides, name);
+ if (key != null) {
+ nameOverrides.put(key, view.getTransitionName());
+ }
+ }
+ }
+ } else {
+ retainValues(nameOverrides, inSharedElements);
+ }
+ return inSharedElements;
+ }
+
+ /**
+ * Utility to find the String key in {@code map} that maps to {@code value}.
+ */
+ private static String findKeyForValue(ArrayMap<String, String> map, String value) {
+ final int numElements = map.size();
+ for (int i = 0; i < numElements; i++) {
+ if (value.equals(map.valueAt(i))) {
+ return map.keyAt(i);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the View in the incoming Fragment that should be used as the epicenter.
+ *
+ * @param inSharedElements The mapping of shared element names to Views in the
+ * incoming fragment.
+ * @param fragments A structure holding the transitioning fragments in this container.
+ * @param enterTransition The transition used for the incoming Fragment's views
+ * @param inIsPop Is the incoming fragment being added as a pop transaction?
+ */
+ private static View getInEpicenterView(ArrayMap<String, View> inSharedElements,
+ FragmentContainerTransition fragments,
+ Transition enterTransition, boolean inIsPop) {
+ BackStackRecord inTransaction = fragments.lastInTransaction;
+ if (enterTransition != null && inTransaction.mSharedElementSourceNames != null &&
+ !inTransaction.mSharedElementSourceNames.isEmpty()) {
+ final String targetName = inIsPop
+ ? inTransaction.mSharedElementSourceNames.get(0)
+ : inTransaction.mSharedElementTargetNames.get(0);
+ return inSharedElements.get(targetName);
+ }
+ return null;
+ }
+
+ /**
+ * Sets the epicenter for the exit transition.
+ *
+ * @param sharedElementTransition The shared element transition
+ * @param exitTransition The transition for the outgoing fragment's views
+ * @param outSharedElements Shared elements in the outgoing fragment
+ * @param outIsPop Is the outgoing fragment being removed as a pop transaction?
+ * @param outTransaction The transaction that caused the fragment to be removed.
+ */
+ private static void setOutEpicenter(TransitionSet sharedElementTransition,
+ Transition exitTransition, ArrayMap<String, View> outSharedElements, boolean outIsPop,
+ BackStackRecord outTransaction) {
+ if (outTransaction.mSharedElementSourceNames != null &&
+ !outTransaction.mSharedElementSourceNames.isEmpty()) {
+ final String sourceName = outIsPop
+ ? outTransaction.mSharedElementTargetNames.get(0)
+ : outTransaction.mSharedElementSourceNames.get(0);
+ final View outEpicenterView = outSharedElements.get(sourceName);
+ setEpicenter(sharedElementTransition, outEpicenterView);
+
+ if (exitTransition != null) {
+ setEpicenter(exitTransition, outEpicenterView);
+ }
+ }
+ }
+
+ /**
+ * Sets a transition epicenter to the rectangle of a given View.
+ */
+ private static void setEpicenter(Transition transition, View view) {
+ if (view != null) {
+ final Rect epicenter = new Rect();
+ view.getBoundsOnScreen(epicenter);
+
+ transition.setEpicenterCallback(new Transition.EpicenterCallback() {
+ @Override
+ public Rect onGetEpicenter(Transition transition) {
+ return epicenter;
+ }
+ });
+ }
+ }
+
+ /**
+ * A utility to retain only the mappings in {@code nameOverrides} that have a value
+ * that has a key in {@code namedViews}. This is a useful equivalent to
+ * {@link ArrayMap#retainAll(Collection)} for values.
+ */
+ private static void retainValues(ArrayMap<String, String> nameOverrides,
+ ArrayMap<String, View> namedViews) {
+ for (int i = nameOverrides.size() - 1; i >= 0; i--) {
+ final String targetName = nameOverrides.valueAt(i);
+ if (!namedViews.containsKey(targetName)) {
+ nameOverrides.removeAt(i);
+ }
+ }
+ }
+
+ /**
+ * Calls the {@link SharedElementCallback#onSharedElementStart(List, List, List)} or
+ * {@link SharedElementCallback#onSharedElementEnd(List, List, List)} on the appropriate
+ * incoming or outgoing fragment.
+ *
+ * @param inFragment The incoming fragment
+ * @param outFragment The outgoing fragment
+ * @param isPop Is the incoming fragment part of a pop transaction?
+ * @param sharedElements The shared element Views
+ * @param isStart Call the start or end call on the SharedElementCallback
+ */
+ private static void callSharedElementStartEnd(Fragment inFragment, Fragment outFragment,
+ boolean isPop, ArrayMap<String, View> sharedElements, boolean isStart) {
+ SharedElementCallback sharedElementCallback = isPop
+ ? outFragment.getEnterTransitionCallback()
+ : inFragment.getEnterTransitionCallback();
+ if (sharedElementCallback != null) {
+ ArrayList<View> views = new ArrayList<>();
+ ArrayList<String> names = new ArrayList<>();
+ final int count = sharedElements == null ? 0 : sharedElements.size();
+ for (int i = 0; i < count; i++) {
+ names.add(sharedElements.keyAt(i));
+ views.add(sharedElements.valueAt(i));
+ }
+ if (isStart) {
+ sharedElementCallback.onSharedElementStart(names, views, null);
+ } else {
+ sharedElementCallback.onSharedElementEnd(names, views, null);
+ }
+ }
+ }
+
+ /**
+ * Finds all children of the shared elements and sets the wrapping TransitionSet
+ * targets to point to those. It also limits transitions that have no targets to the
+ * specific shared elements. This allows developers to target child views of the
+ * shared elements specifically, but this doesn't happen by default.
+ */
+ private static void setSharedElementTargets(TransitionSet transition,
+ View nonExistentView, ArrayList<View> sharedViews) {
+ final List<View> views = transition.getTargets();
+ views.clear();
+ final int count = sharedViews.size();
+ for (int i = 0; i < count; i++) {
+ final View view = sharedViews.get(i);
+ bfsAddViewChildren(views, view);
+ }
+ views.add(nonExistentView);
+ sharedViews.add(nonExistentView);
+ addTargets(transition, sharedViews);
+ }
+
+ /**
+ * Uses a breadth-first scheme to add startView and all of its children to views.
+ * It won't add a child if it is already in views.
+ */
+ private static void bfsAddViewChildren(final List<View> views, final View startView) {
+ final int startIndex = views.size();
+ if (containedBeforeIndex(views, startView, startIndex)) {
+ return; // This child is already in the list, so all its children are also.
+ }
+ views.add(startView);
+ for (int index = startIndex; index < views.size(); index++) {
+ final View view = views.get(index);
+ if (view instanceof ViewGroup) {
+ ViewGroup viewGroup = (ViewGroup) view;
+ final int childCount = viewGroup.getChildCount();
+ for (int childIndex = 0; childIndex < childCount; childIndex++) {
+ final View child = viewGroup.getChildAt(childIndex);
+ if (!containedBeforeIndex(views, child, startIndex)) {
+ views.add(child);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Does a linear search through views for view, limited to maxIndex.
+ */
+ private static boolean containedBeforeIndex(final List<View> views, final View view,
+ final int maxIndex) {
+ for (int i = 0; i < maxIndex; i++) {
+ if (views.get(i) == view) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * After the transition has started, remove all targets that we added to the transitions
+ * so that the transitions are left in a clean state.
+ */
+ private static void scheduleRemoveTargets(final Transition overalTransition,
+ final Transition enterTransition, final ArrayList<View> enteringViews,
+ final Transition exitTransition, final ArrayList<View> exitingViews,
+ final TransitionSet sharedElementTransition, final ArrayList<View> sharedElementsIn) {
+ overalTransition.addListener(new Transition.TransitionListenerAdapter() {
+ @Override
+ public void onTransitionStart(Transition transition) {
+ if (enterTransition != null) {
+ replaceTargets(enterTransition, enteringViews, null);
+ }
+ if (exitTransition != null) {
+ replaceTargets(exitTransition, exitingViews, null);
+ }
+ if (sharedElementTransition != null) {
+ replaceTargets(sharedElementTransition, sharedElementsIn, null);
+ }
+ }
+ });
+ }
+
+ /**
+ * This method removes the views from transitions that target ONLY those views and
+ * replaces them with the new targets list.
+ * The views list should match those added in addTargets and should contain
+ * one view that is not in the view hierarchy (state.nonExistentView).
+ */
+ public static void replaceTargets(Transition transition, ArrayList<View> oldTargets,
+ ArrayList<View> newTargets) {
+ if (transition instanceof TransitionSet) {
+ TransitionSet set = (TransitionSet) transition;
+ int numTransitions = set.getTransitionCount();
+ for (int i = 0; i < numTransitions; i++) {
+ Transition child = set.getTransitionAt(i);
+ replaceTargets(child, oldTargets, newTargets);
+ }
+ } else if (!hasSimpleTarget(transition)) {
+ List<View> targets = transition.getTargets();
+ if (targets != null && targets.size() == oldTargets.size() &&
+ targets.containsAll(oldTargets)) {
+ // We have an exact match. We must have added these earlier in addTargets
+ final int targetCount = newTargets == null ? 0 : newTargets.size();
+ for (int i = 0; i < targetCount; i++) {
+ transition.addTarget(newTargets.get(i));
+ }
+ for (int i = oldTargets.size() - 1; i >= 0; i--) {
+ transition.removeTarget(oldTargets.get(i));
+ }
+ }
+ }
+ }
+
+ /**
+ * This method adds views as targets to the transition, but only if the transition
+ * doesn't already have a target. It is best for views to contain one View object
+ * that does not exist in the view hierarchy (state.nonExistentView) so that
+ * when they are removed later, a list match will suffice to remove the targets.
+ * Otherwise, if you happened to have targeted the exact views for the transition,
+ * the replaceTargets call will remove them unexpectedly.
+ */
+ public static void addTargets(Transition transition, ArrayList<View> views) {
+ if (transition == null) {
+ return;
+ }
+ if (transition instanceof TransitionSet) {
+ TransitionSet set = (TransitionSet) transition;
+ int numTransitions = set.getTransitionCount();
+ for (int i = 0; i < numTransitions; i++) {
+ Transition child = set.getTransitionAt(i);
+ addTargets(child, views);
+ }
+ } else if (!hasSimpleTarget(transition)) {
+ List<View> targets = transition.getTargets();
+ if (isNullOrEmpty(targets)) {
+ // We can just add the target views
+ int numViews = views.size();
+ for (int i = 0; i < numViews; i++) {
+ transition.addTarget(views.get(i));
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns true if there are any targets based on ID, transition or type.
+ */
+ private static boolean hasSimpleTarget(Transition transition) {
+ return !isNullOrEmpty(transition.getTargetIds()) ||
+ !isNullOrEmpty(transition.getTargetNames()) ||
+ !isNullOrEmpty(transition.getTargetTypes());
+ }
+
+ /**
+ * Simple utility to detect if a list is null or has no elements.
+ */
+ private static boolean isNullOrEmpty(List list) {
+ return list == null || list.isEmpty();
+ }
+
+ private static ArrayList<View> configureEnteringExitingViews(Transition transition,
+ Fragment fragment, ArrayList<View> sharedElements, View nonExistentView) {
+ ArrayList<View> viewList = null;
+ if (transition != null) {
+ viewList = new ArrayList<>();
+ View root = fragment.getView();
+ root.captureTransitioningViews(viewList);
+ if (sharedElements != null) {
+ viewList.removeAll(sharedElements);
+ }
+ if (!viewList.isEmpty()) {
+ viewList.add(nonExistentView);
+ addTargets(transition, viewList);
+ }
+ }
+ return viewList;
+ }
+
+ /**
+ * Sets the visibility of all Views in {@code views} to {@code visibility}.
+ */
+ private static void setViewVisibility(ArrayList<View> views, @View.Visibility int visibility) {
+ if (views == null) {
+ return;
+ }
+ for (int i = views.size() - 1; i >= 0; i--) {
+ final View view = views.get(i);
+ view.setVisibility(visibility);
+ }
+ }
+
+ /**
+ * Merges exit, shared element, and enter transitions so that they act together or
+ * sequentially as defined in the fragments.
+ */
+ private static Transition mergeTransitions(Transition enterTransition,
+ Transition exitTransition, Transition sharedElementTransition, Fragment inFragment,
+ boolean isPop) {
+ boolean overlap = true;
+ if (enterTransition != null && exitTransition != null && inFragment != null) {
+ overlap = isPop ? inFragment.getAllowReturnTransitionOverlap() :
+ inFragment.getAllowEnterTransitionOverlap();
+ }
+
+ // Wrap the transitions. Explicit targets like in enter and exit will cause the
+ // views to be targeted regardless of excluded views. If that happens, then the
+ // excluded fragments views (hidden fragments) will still be in the transition.
+
+ Transition transition;
+ if (overlap) {
+ // Regular transition -- do it all together
+ TransitionSet transitionSet = new TransitionSet();
+ if (enterTransition != null) {
+ transitionSet.addTransition(enterTransition);
+ }
+ if (exitTransition != null) {
+ transitionSet.addTransition(exitTransition);
+ }
+ if (sharedElementTransition != null) {
+ transitionSet.addTransition(sharedElementTransition);
+ }
+ transition = transitionSet;
+ } else {
+ // First do exit, then enter, but allow shared element transition to happen
+ // during both.
+ Transition staggered = null;
+ if (exitTransition != null && enterTransition != null) {
+ staggered = new TransitionSet()
+ .addTransition(exitTransition)
+ .addTransition(enterTransition)
+ .setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
+ } else if (exitTransition != null) {
+ staggered = exitTransition;
+ } else if (enterTransition != null) {
+ staggered = enterTransition;
+ }
+ if (sharedElementTransition != null) {
+ TransitionSet together = new TransitionSet();
+ if (staggered != null) {
+ together.addTransition(staggered);
+ }
+ together.addTransition(sharedElementTransition);
+ transition = together;
+ } else {
+ transition = staggered;
+ }
+ }
+ return transition;
+ }
+
+ /**
+ * Finds the first removed fragment and last added fragments when going forward.
+ * If none of the fragments have transitions, then both lists will be empty.
+ *
+ * @param transitioningFragments Keyed on the container ID, the first fragments to be removed,
+ * and last fragments to be added. This will be modified by
+ * this method.
+ */
+ public static void calculateFragments(BackStackRecord transaction,
+ SparseArray<FragmentContainerTransition> transitioningFragments,
+ boolean isOptimized) {
+ final int numOps = transaction.mOps.size();
+ for (int opNum = 0; opNum < numOps; opNum++) {
+ final BackStackRecord.Op op = transaction.mOps.get(opNum);
+ addToFirstInLastOut(transaction, op, transitioningFragments, false, isOptimized);
+ }
+ }
+
+ /**
+ * Finds the first removed fragment and last added fragments when popping the back stack.
+ * If none of the fragments have transitions, then both lists will be empty.
+ *
+ * @param transitioningFragments Keyed on the container ID, the first fragments to be removed,
+ * and last fragments to be added. This will be modified by
+ * this method.
+ */
+ public static void calculatePopFragments(BackStackRecord transaction,
+ SparseArray<FragmentContainerTransition> transitioningFragments, boolean isOptimized) {
+ if (!transaction.mManager.mContainer.onHasView()) {
+ return; // nothing to see, so no transitions
+ }
+ final int numOps = transaction.mOps.size();
+ for (int opNum = numOps - 1; opNum >= 0; opNum--) {
+ final BackStackRecord.Op op = transaction.mOps.get(opNum);
+ addToFirstInLastOut(transaction, op, transitioningFragments, true, isOptimized);
+ }
+ }
+
+ /**
+ * Examines the {@code command} and may set the first out or last in fragment for the fragment's
+ * container.
+ *
+ * @param transaction The executing transaction
+ * @param op The operation being run.
+ * @param transitioningFragments A structure holding the first in and last out fragments
+ * for each fragment container.
+ * @param isPop Is the operation a pop?
+ * @param isOptimizedTransaction True if the operations have been partially executed and the
+ * added fragments have Views in the hierarchy or false if the
+ * operations haven't been executed yet.
+ */
+ private static void addToFirstInLastOut(BackStackRecord transaction, BackStackRecord.Op op,
+ SparseArray<FragmentContainerTransition> transitioningFragments, boolean isPop,
+ boolean isOptimizedTransaction) {
+ final Fragment fragment = op.fragment;
+ final int containerId = fragment.mContainerId;
+ if (containerId == 0) {
+ return; // no container, no transition
+ }
+ final int command = isPop ? INVERSE_OPS[op.cmd] : op.cmd;
+ boolean setLastIn = false;
+ boolean wasRemoved = false;
+ boolean setFirstOut = false;
+ boolean wasAdded = false;
+ switch (command) {
+ case BackStackRecord.OP_SHOW:
+ if (isOptimizedTransaction) {
+ setLastIn = fragment.mHiddenChanged && !fragment.mHidden &&
+ fragment.mAdded;
+ } else {
+ setLastIn = fragment.mHidden;
+ }
+ wasAdded = true;
+ break;
+ case BackStackRecord.OP_ADD:
+ case BackStackRecord.OP_ATTACH:
+ if (isOptimizedTransaction) {
+ setLastIn = fragment.mIsNewlyAdded;
+ } else {
+ setLastIn = !fragment.mAdded && !fragment.mHidden;
+ }
+ wasAdded = true;
+ break;
+ case BackStackRecord.OP_HIDE:
+ if (isOptimizedTransaction) {
+ setFirstOut = fragment.mHiddenChanged && fragment.mAdded &&
+ fragment.mHidden;
+ } else {
+ setFirstOut = fragment.mAdded && !fragment.mHidden;
+ }
+ wasRemoved = true;
+ break;
+ case BackStackRecord.OP_REMOVE:
+ case BackStackRecord.OP_DETACH:
+ if (isOptimizedTransaction) {
+ setFirstOut = !fragment.mAdded && fragment.mView != null &&
+ fragment.mView.getVisibility() == View.VISIBLE;
+ } else {
+ setFirstOut = fragment.mAdded && !fragment.mHidden;
+ }
+ wasRemoved = true;
+ break;
+ }
+ FragmentContainerTransition containerTransition = transitioningFragments.get(containerId);
+ if (setLastIn) {
+ containerTransition =
+ ensureContainer(containerTransition, transitioningFragments, containerId);
+ containerTransition.lastIn = fragment;
+ containerTransition.lastInIsPop = isPop;
+ containerTransition.lastInTransaction = transaction;
+ }
+ if (!isOptimizedTransaction && wasAdded) {
+ if (containerTransition != null && containerTransition.firstOut == fragment) {
+ containerTransition.firstOut = null;
+ }
+
+ /**
+ * Ensure that fragments that are entering are at least at the CREATED state
+ * so that they may load Transitions using TransitionInflater.
+ */
+ FragmentManagerImpl manager = transaction.mManager;
+ if (fragment.mState < Fragment.CREATED && manager.mCurState >= Fragment.CREATED &&
+ manager.mHost.getContext().getApplicationInfo().targetSdkVersion >=
+ Build.VERSION_CODES.N && !transaction.mAllowOptimization) {
+ manager.makeActive(fragment);
+ manager.moveToState(fragment, Fragment.CREATED, 0, 0, false);
+ }
+ }
+ if (setFirstOut && (containerTransition == null || containerTransition.firstOut == null)) {
+ containerTransition =
+ ensureContainer(containerTransition, transitioningFragments, containerId);
+ containerTransition.firstOut = fragment;
+ containerTransition.firstOutIsPop = isPop;
+ containerTransition.firstOutTransaction = transaction;
+ }
+
+ if (!isOptimizedTransaction && wasRemoved &&
+ (containerTransition != null && containerTransition.lastIn == fragment)) {
+ containerTransition.lastIn = null;
+ }
+ }
+
+ /**
+ * Ensures that a FragmentContainerTransition has been added to the SparseArray. If so,
+ * it returns the existing one. If not, one is created and added to the SparseArray and
+ * returned.
+ */
+ private static FragmentContainerTransition ensureContainer(
+ FragmentContainerTransition containerTransition,
+ SparseArray<FragmentContainerTransition> transitioningFragments, int containerId) {
+ if (containerTransition == null) {
+ containerTransition = new FragmentContainerTransition();
+ transitioningFragments.put(containerId, containerTransition);
+ }
+ return containerTransition;
+ }
+
+ /**
+ * Tracks the last fragment added and first fragment removed for fragment transitions.
+ * This also tracks which fragments are changed by push or pop transactions.
+ */
+ public static class FragmentContainerTransition {
+ /**
+ * The last fragment added/attached/shown in its container
+ */
+ public Fragment lastIn;
+
+ /**
+ * true when lastIn was added during a pop transaction or false if added with a push
+ */
+ public boolean lastInIsPop;
+
+ /**
+ * The transaction that included the last in fragment
+ */
+ public BackStackRecord lastInTransaction;
+
+ /**
+ * The first fragment with a View that was removed/detached/hidden in its container.
+ */
+ public Fragment firstOut;
+
+ /**
+ * true when firstOut was removed during a pop transaction or false otherwise
+ */
+ public boolean firstOutIsPop;
+
+ /**
+ * The transaction that included the first out fragment
+ */
+ public BackStackRecord firstOutTransaction;
+ }
+}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 1253e69..5edd03f 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -274,12 +274,24 @@
/**
* Updates global configuration and applies changes to the entire system.
- * @param values Update values for global configuration.
+ * @param values Update values for global configuration. If null is passed it will request the
+ * Window Manager to compute new config for the default display.
* @throws RemoteException
* @return Returns true if the configuration was updated.
*/
public boolean updateConfiguration(Configuration values) throws RemoteException;
+ /**
+ * Updates override configuration applied to specific display.
+ * @param values Update values for display configuration. If null is passed it will request the
+ * Window Manager to compute new config for the specified display.
+ * @param displayId Id of the display to apply the config to.
+ * @throws RemoteException
+ * @return Returns true if the configuration was updated.
+ */
+ public boolean updateDisplayOverrideConfiguration(Configuration values, int displayId)
+ throws RemoteException;
+
public void setRequestedOrientation(IBinder token,
int requestedOrientation) throws RemoteException;
public int getRequestedOrientation(IBinder token) throws RemoteException;
@@ -1103,4 +1115,5 @@
int REQUEST_ACTIVITY_RELAUNCH = IBinder.FIRST_CALL_TRANSACTION+400;
int GET_DEFAULT_PICTURE_IN_PICTURE_BOUNDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 401;
int GET_PICTURE_IN_PICTURE_MOVEMENT_BOUNDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 402;
+ int UPDATE_DISPLAY_OVERRIDE_CONFIGURATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 403;
}
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 2dd3b1a..e2f6fb5 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -155,6 +155,7 @@
void scheduleLocalVoiceInteractionStarted(IBinder token,
IVoiceInteractor voiceInteractor) = 61;
void handleTrustStorageUpdate() = 62;
+ void attachAgent(String path) = 63;
/**
* Don't change the existing transaction Ids as they could be used in the native code.
* When adding a new method, assign the next available transaction id.
diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java
index 2ded4c8..243579a 100644
--- a/core/java/android/bluetooth/BluetoothUuid.java
+++ b/core/java/android/bluetooth/BluetoothUuid.java
@@ -275,6 +275,48 @@
}
/**
+ * Parse UUID to bytes. The returned value is shortest representation, a 16-bit, 32-bit or 128-bit UUID,
+ * Note returned value is little endian (Bluetooth).
+ *
+ * @param uuid uuid to parse.
+ * @return shortest representation of {@code uuid} as bytes.
+ * @throws IllegalArgumentException If the {@code uuid} is null.
+ */
+ public static byte[] uuidToBytes(ParcelUuid uuid) {
+ if (uuid == null) {
+ throw new IllegalArgumentException("uuid cannot be null");
+ }
+
+ if (is16BitUuid(uuid)) {
+ byte[] uuidBytes = new byte[UUID_BYTES_16_BIT];
+ int uuidVal = getServiceIdentifierFromParcelUuid(uuid);
+ uuidBytes[0] = (byte)(uuidVal & 0xFF);
+ uuidBytes[1] = (byte)((uuidVal & 0xFF00) >> 8);
+ return uuidBytes;
+ }
+
+ if (is32BitUuid(uuid)) {
+ byte[] uuidBytes = new byte[UUID_BYTES_32_BIT];
+ int uuidVal = getServiceIdentifierFromParcelUuid(uuid);
+ uuidBytes[0] = (byte)(uuidVal & 0xFF);
+ uuidBytes[1] = (byte)((uuidVal & 0xFF00) >> 8);
+ uuidBytes[2] = (byte)((uuidVal & 0xFF0000) >> 16);
+ uuidBytes[3] = (byte)((uuidVal & 0xFF000000) >> 24);
+ return uuidBytes;
+ }
+
+ // Construct a 128 bit UUID.
+ long msb = uuid.getUuid().getMostSignificantBits();
+ long lsb = uuid.getUuid().getLeastSignificantBits();
+
+ byte[] uuidBytes = new byte[UUID_BYTES_128_BIT];
+ ByteBuffer buf = ByteBuffer.wrap(uuidBytes).order(ByteOrder.LITTLE_ENDIAN);
+ buf.putLong(8, msb);
+ buf.putLong(0, lsb);
+ return uuidBytes;
+ }
+
+ /**
* Check whether the given parcelUuid can be converted to 16 bit bluetooth uuid.
*
* @param parcelUuid
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index dbb9650..e4a12e8 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -302,7 +302,7 @@
/**
* Implement parsing and execution of a command. If it isn't a command you understand,
* call {@link #handleDefaultCommands(String)} and return its result as a last resort.
- * User {@link #getNextOption()}, {@link #getNextArg()}, and {@link #getNextArgRequired()}
+ * Use {@link #getNextOption()}, {@link #getNextArg()}, and {@link #getNextArgRequired()}
* to process additional command line arguments. Command output can be written to
* {@link #getOutPrintWriter()} and errors to {@link #getErrPrintWriter()}.
*
diff --git a/core/java/android/util/Half.java b/core/java/android/util/Half.java
new file mode 100644
index 0000000..f4eb132
--- /dev/null
+++ b/core/java/android/util/Half.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2016 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.util;
+
+/**
+ * <p>Half is a utility class to manipulate half-precision 16-bit
+ * <a href="https://en.wikipedia.org/wiki/Half-precision_floating-point_format">IEEE 754</a>
+ * floating point data types (also called fp16 or binary16). A half-precision
+ * float is stored in a short data type. A half-precision float can be
+ * created from or converted to single-precision floats.</p>
+ *
+ * <p>The IEEE 754 standard specifies an fp16 as having the following format:</p>
+ * <ul>
+ * <li>Sign bit: 1 bit</li>
+ * <li>Exponent width: 5 bits</li>
+ * <li>Mantissa: 10 bits</li>
+ * </ul>
+ *
+ * <p>The format is laid out thusly:</p>
+ * <pre>
+ * 1 11111 1111111111
+ * ^ --^-- -----^----
+ * sign | |_______ mantissa
+ * |
+ * -- exponent
+ * </pre>
+ *
+ * @hide
+ */
+public final class Half {
+ /**
+ * The number of bits used to represent a half-precision float value.
+ */
+ public static final int SIZE = 16;
+
+ /**
+ * Epsilon is the difference between 1.0 and the next value representable
+ * by a half-precision floating-point.
+ */
+ public static final short EPSILON = (short) 0x1400;
+ /**
+ * Smallest negative value a half-precision float may have.
+ */
+ public static final short LOWEST_VALUE = (short) 0xfbff;
+ /**
+ * Maximum exponent a finite half-precision float may have.
+ */
+ public static final short MAX_EXPONENT = 15;
+ /**
+ * Maximum positive finite value a half-precision float may have.
+ */
+ public static final short MAX_VALUE = (short) 0x7bff;
+ /**
+ * Minimum exponent a normalized half-precision float may have.
+ */
+ public static final short MIN_EXPONENT = -14;
+ /**
+ * Smallest positive normal value a half-precision float may have.
+ */
+ public static final short MIN_NORMAL = (short) 0x0400;
+ /**
+ * Smallest positive non-zero value a half-precision float may have.
+ */
+ public static final short MIN_VALUE = (short) 0x0001;
+ /**
+ * A Not-a-Number representation of a half-precision float.
+ */
+ public static final short NaN = (short) 0x7e00;
+ /**
+ * Negative infinity of type half-precision float.
+ */
+ public static final short NEGATIVE_INFINITY = (short) 0xfc00;
+ /**
+ * Negative 0 of type half-precision float.
+ */
+ public static final short NEGATIVE_ZERO = (short) 0x8000;
+ /**
+ * Positive infinity of type half-precision float.
+ */
+ public static final short POSITIVE_INFINITY = (short) 0x7c00;
+ /**
+ * Positive 0 of type half-precision float.
+ */
+ public static final short POSITIVE_ZERO = (short) 0x0000;
+
+ private static final int FP16_SIGN_SHIFT = 15;
+ private static final int FP16_EXPONENT_SHIFT = 10;
+ private static final int FP16_EXPONENT_MASK = 0x1f;
+ private static final int FP16_MANTISSA_MASK = 0x3ff;
+ private static final int FP16_EXPONENT_BIAS = 15;
+
+ private static final int FP32_SIGN_SHIFT = 31;
+ private static final int FP32_EXPONENT_SHIFT = 23;
+ private static final int FP32_EXPONENT_MASK = 0xff;
+ private static final int FP32_MANTISSA_MASK = 0x7fffff;
+ private static final int FP32_EXPONENT_BIAS = 127;
+
+ private static final int FP32_DENORMAL_MAGIC = 126 << 23;
+ private static final float FP32_DENORMAL_FLOAT =
+ Float.intBitsToFloat(FP32_DENORMAL_MAGIC);
+
+ private Half() {
+ }
+
+ /**
+ * Returns the sign of the specified half-precision float.
+ *
+ * @param h A half-precision float value
+ * @return 1 if the value is positive, -1 if the value is negative
+ */
+ public static int getSign(short h) {
+ return (h >>> FP16_SIGN_SHIFT) == 0 ? 1 : -1;
+ }
+
+ /**
+ * Returns the unbiased exponent used in the representation of
+ * the specified half-precision float value. if the value is NaN
+ * or infinite, this* method returns {@link #MAX_EXPONENT} + 1.
+ * If the argument is* 0 or denormal, this method returns
+ * {@link #MIN_EXPONENT} - 1.
+ *
+ * @param h A half-precision float value
+ * @return The unbiased exponent of the specified value
+ */
+ public static int getExponent(short h) {
+ return ((h >>> FP16_EXPONENT_SHIFT) & FP16_EXPONENT_MASK) - FP16_EXPONENT_BIAS;
+ }
+
+ /**
+ * Returns the mantissa, or significand, used in the representation
+ * of the specified half-precision float value.
+ *
+ * @param h A half-precision float value
+ * @return The mantissa, or significand, of the specified vlaue
+ */
+ public static int getMantissa(short h) {
+ return h & FP16_MANTISSA_MASK;
+ }
+
+ /**
+ * Returns true if the specified half-precision float value represents
+ * infinity, false otherwise.
+ *
+ * @param h A half-precision float value
+ * @return true if the value is positive infinity or negative infinity,
+ * false otherwise
+ */
+ public static boolean isInfinite(short h) {
+ int e = (h >>> FP16_EXPONENT_SHIFT) & FP16_EXPONENT_MASK;
+ int m = (h ) & FP16_MANTISSA_MASK;
+ return e == 0x1f && m == 0;
+ }
+
+ /**
+ * Returns true if the specified half-precision float value represents
+ * a Not-a-Number, false otherwise.
+ *
+ * @param h A half-precision float value
+ * @return true if the value is a NaN, false otherwise
+ */
+ public static boolean isNaN(short h) {
+ int e = (h >>> FP16_EXPONENT_SHIFT) & FP16_EXPONENT_MASK;
+ int m = (h ) & FP16_MANTISSA_MASK;
+ return e == 0x1f && m != 0;
+ }
+
+ /**
+ * <p>Converts the specified half-precision float value into a
+ * single-precision float value with the following special cases:</p>
+ * <ul>
+ * <li>If the input is {@link #NaN}, the returned* value is {@link Float#NaN}</li>
+ * <li>If the input is {@link #POSITIVE_INFINITY} or
+ * {@link #NEGATIVE_INFINITY}, the returned value is respectively
+ * {@link Float#POSITIVE_INFINITY} or {@link Float#NEGATIVE_INFINITY}</li>
+ * <li>If the input is 0 (positive or negative), the returned value is +/-0.0f</li>
+ * <li>Otherwise, the returned value is a normalized single-precision float value</li>
+ * </ul>
+ *
+ * @param h The half-precision float value to convert to single-precision
+ * @return A normalized single-precision float value
+ */
+ public static float toFloat(short h) {
+ int bits = h & 0xffff;
+ int s = (bits >>> FP16_SIGN_SHIFT );
+ int e = (bits >>> FP16_EXPONENT_SHIFT) & FP16_EXPONENT_MASK;
+ int m = (bits ) & FP16_MANTISSA_MASK;
+
+ int outE = 0;
+ int outM = 0;
+
+ if (e == 0) { // Denormal or 0
+ if (m != 0) {
+ // Convert denorm fp16 into normalized fp32
+ float o = Float.intBitsToFloat(FP32_DENORMAL_MAGIC + m);
+ o -= FP32_DENORMAL_FLOAT;
+ return s == 0 ? o : -o;
+ }
+ } else {
+ outM = m << 13;
+ if (e == 0x1f) { // Infinite or NaN
+ outE = 0xff;
+ } else {
+ outE = e - FP16_EXPONENT_BIAS + FP32_EXPONENT_BIAS;
+ }
+ }
+
+ int out = (s << FP32_SIGN_SHIFT) | (outE << FP32_EXPONENT_SHIFT) | outM;
+ return Float.intBitsToFloat(out);
+ }
+
+ /**
+ * <p>Converts the specified single-precision float value into a
+ * half-precision float value with the following special cases:</p>
+ * <ul>
+ * <li>If the input is NaN (see {@link Float#isNaN(float)}), the returned
+ * value is {@link #NaN}</li>
+ * <li>If the input is {@link Float#POSITIVE_INFINITY} or
+ * {@link Float#NEGATIVE_INFINITY}, the returned value is respectively
+ * {@link #POSITIVE_INFINITY} or {@link #NEGATIVE_INFINITY}</li>
+ * <li>If the input is 0 (positive or negative), the returned value is
+ * {@link #POSITIVE_ZERO} or {@link #NEGATIVE_ZERO}</li>
+ * <li>If the input is a less than {@link #MIN_VALUE}, the returned value
+ * is flushed to {@link #POSITIVE_ZERO} or {@link #NEGATIVE_ZERO}</li>
+ * <li>If the input is a less than {@link #MIN_NORMAL}, the returned value
+ * is a denorm half-precision float</li>
+ * <li>Otherwise, the returned value is rounded to the nearest
+ * representable half-precision float value</li>
+ * </ul>
+ *
+ * @param f The single-precision float value to convert to half-precision
+ * @return A half-precision float value
+ */
+ @SuppressWarnings("StatementWithEmptyBody")
+ public static short valueOf(float f) {
+ int bits = Float.floatToRawIntBits(f);
+ int s = (bits >>> FP32_SIGN_SHIFT );
+ int e = (bits >>> FP32_EXPONENT_SHIFT) & FP32_EXPONENT_MASK;
+ int m = (bits ) & FP32_MANTISSA_MASK;
+
+ int outE = 0;
+ int outM = 0;
+
+ if (e == 0xff) { // Infinite or NaN
+ outE = 0x1f;
+ outM = m != 0 ? 0x200 : 0;
+ } else {
+ e = e - FP32_EXPONENT_BIAS + FP16_EXPONENT_BIAS;
+ if (e >= 0x1f) { // Overflow
+ outE = 0x31;
+ } else if (e <= 0) { // Underflow
+ if (e < -10) {
+ // The absolute fp32 value is less than MIN_VALUE, flush to +/-0
+ } else {
+ // The fp32 value is a normalized float less than MIN_NORMAL,
+ // we convert to a denorm fp16
+ m = (m | 0x800000) >> (1 - e);
+ if ((m & 0x1000) != 0) m += 0x2000;
+ outM = m >> 13;
+ }
+ } else {
+ outE = e;
+ outM = m >> 13;
+ if ((m & 0x1000) != 0) {
+ // Round to nearest "0.5" up
+ int out = (outE << FP16_EXPONENT_SHIFT) | outM;
+ out++;
+ out |= (s << FP16_SIGN_SHIFT);
+ return (short) out;
+ }
+ }
+ }
+
+ int out = (s << FP16_SIGN_SHIFT) | (outE << FP16_EXPONENT_SHIFT) | outM;
+ return (short) out;
+ }
+
+ /**
+ * Returns a string representation of the specified half-precision
+ * float value. Calling this method is equivalent to calling
+ * <code>Float.toString(toFloat(h))</code>. See {@link Float#toString(float)}
+ * for more information on the format of the string representation.
+ *
+ * @param h A half-precision float value
+ * @return A string representation of the specified value
+ */
+ public static String toString(short h) {
+ return Float.toString(toFloat(h));
+ }
+
+ /**
+ * <p>Returns a hexadecimal string representation of the specified half-precision
+ * float value. If the value is a NaN, the result is <code>"NaN"</code>,
+ * otherwise the result follows this format:</p>
+ * <ul>
+ * <li>If the sign is positive, no sign character appears in the result</li>
+ * <li>If the sign is negative, the first character is <code>'-'</code></li>
+ * <li>If the value is inifinity, the string is <code>"Infinity"</code></li>
+ * <li>If the value is 0, the string is <code>"0x0.0p0"</code></li>
+ * <li>If the value has a normalized representation, the exponent and
+ * mantissa are represented in the string in two fields. The mantissa starts
+ * with <code>"0x1."</code> followed by its lowercase hexadecimal
+ * representation. Trailing zeroes are removed unless all digits are 0, then
+ * a single zero is used. The mantissa representation is followed by the
+ * exponent, represented by <code>"p"</code>, itself followed by a decimal
+ * string of the unbiased exponent</li>
+ * <li>If the value has a denormal representation, the mantissa starts
+ * with <code>"0x0."</code> followed by its lowercase hexadecimal
+ * representation. Trailing zeroes are removed unless all digits are 0, then
+ * a single zero is used. The mantissa representation is followed by the
+ * exponent, represented by <code>"p-14"</code></li>
+ * </ul>
+ *
+ * @param h A half-precision float value
+ * @return A hexadecimal string representation of the specified value
+ */
+ public static String toHexString(short h) {
+ StringBuilder o = new StringBuilder();
+
+ int bits = h & 0xffff;
+ int s = (bits >>> FP16_SIGN_SHIFT );
+ int e = (bits >>> FP16_EXPONENT_SHIFT) & FP16_EXPONENT_MASK;
+ int m = (bits ) & FP16_MANTISSA_MASK;
+
+ if (e == 0x1f) { // Infinite or NaN
+ if (m == 0) {
+ if (s == 1) o.append('-');
+ o.append("Infinity");
+ } else {
+ o.append("NaN");
+ }
+ } else {
+ if (s == 1) o.append('-');
+ if (e == 0) {
+ if (m == 0) {
+ o.append("0x0.0p0");
+ } else {
+ o.append("0x0.");
+ String mantissa = Integer.toHexString(m);
+ o.append(mantissa.replaceFirst("0{2,}$", ""));
+ o.append("p-14");
+ }
+ } else {
+ o.append("0x1.");
+ String mantissa = Integer.toHexString(m);
+ o.append(mantissa.replaceFirst("0{2,}$", ""));
+ o.append('p');
+ o.append(Integer.toString(e - FP16_EXPONENT_BIAS));
+ }
+ }
+
+ return o.toString();
+ }
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 717b675..7917041 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -191,10 +191,10 @@
// If there is a change, the new Configuration is returned and the
// caller must call setNewConfiguration() sometime later.
Configuration updateOrientationFromAppTokens(in Configuration currentConfig,
- IBinder freezeThisOneIfNeeded);
- // Notify window manager of the new configuration. Returns an array of stack ids that's
- // affected by the update, ActivityManager should resize these stacks.
- int[] setNewConfiguration(in Configuration config);
+ IBinder freezeThisOneIfNeeded, int displayId);
+ // Notify window manager of the new display override configuration. Returns an array of stack
+ // ids that were affected by the update, ActivityManager should resize these stacks.
+ int[] setNewDisplayOverrideConfiguration(in Configuration overrideConfig, int displayId);
// Retrieves the new bounds after the configuration update evaluated by window manager.
Rect getBoundsForNewConfiguration(int stackId);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c346849..de6f2c5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1712,11 +1712,6 @@
<permission android:name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE"
android:protectionLevel="signature|privileged|development" />
- <!-- @SystemApi @hide Allows an application to retrieve a package's importance.
- This permission is not available to third party applications. -->
- <permission android:name="android.permission.GET_PACKAGE_IMPORTANCE"
- android:protectionLevel="signature|privileged" />
-
<!-- Allows use of PendingIntent.getIntent().
@hide -->
<permission android:name="android.permission.GET_INTENT_SENDER_INTENT"
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index bfe8c07..77a45b3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -484,7 +484,8 @@
mMainHandler.scheduleAPCopyingAndCloseWriteLock();
}
- private AccessPoint getCachedOrCreate(ScanResult result, List<AccessPoint> cache) {
+ @VisibleForTesting
+ AccessPoint getCachedOrCreate(ScanResult result, List<AccessPoint> cache) {
final int N = cache.size();
for (int i = 0; i < N; i++) {
if (cache.get(i).matches(result)) {
@@ -493,10 +494,13 @@
return ret;
}
}
- return new AccessPoint(mContext, result);
+ final AccessPoint accessPoint = new AccessPoint(mContext, result);
+ accessPoint.setListener(mAccessPointListenerAdapter);
+ return accessPoint;
}
- private AccessPoint getCachedOrCreate(WifiConfiguration config, List<AccessPoint> cache) {
+ @VisibleForTesting
+ AccessPoint getCachedOrCreate(WifiConfiguration config, List<AccessPoint> cache) {
final int N = cache.size();
for (int i = 0; i < N; i++) {
if (cache.get(i).matches(config)) {
@@ -505,7 +509,7 @@
return ret;
}
}
- AccessPoint accessPoint = new AccessPoint(mContext, config);
+ final AccessPoint accessPoint = new AccessPoint(mContext, config);
accessPoint.setListener(mAccessPointListenerAdapter);
return accessPoint;
}
diff --git a/packages/SettingsLib/tests/AndroidManifest.xml b/packages/SettingsLib/tests/AndroidManifest.xml
index 9fd5a41..00b2164 100644
--- a/packages/SettingsLib/tests/AndroidManifest.xml
+++ b/packages/SettingsLib/tests/AndroidManifest.xml
@@ -21,6 +21,8 @@
<uses-permission android:name="android.permission.MANAGE_USERS" />
<uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY"/>
<uses-permission android:name="android.permission.SET_TIME_ZONE" />
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+
<application>
<uses-library android:name="android.test.runner" />
diff --git a/packages/SettingsLib/tests/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/src/com/android/settingslib/wifi/WifiTrackerTest.java
new file mode 100644
index 0000000..c650190
--- /dev/null
+++ b/packages/SettingsLib/tests/src/com/android/settingslib/wifi/WifiTrackerTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 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 com.android.settingslib.wifi;
+
+
+import static org.junit.Assert.assertTrue;
+
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.List;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class WifiTrackerTest {
+
+ @Test
+ public void testAccessPointListenerSetWhenLookingUpUsingScanResults() {
+ ScanResult scanResult = new ScanResult();
+ scanResult.level = 123;
+ scanResult.BSSID = "bssid-" + 111;
+ scanResult.timestamp = SystemClock.elapsedRealtime() * 1000;
+ scanResult.capabilities = "";
+
+ WifiTracker tracker = new WifiTracker(
+ InstrumentationRegistry.getTargetContext(), null, true, true);
+
+ AccessPoint result = tracker.getCachedOrCreate(scanResult, new ArrayList<AccessPoint>());
+ assertTrue(result.mAccessPointListener != null);
+ }
+
+ @Test
+ public void testAccessPointListenerSetWhenLookingUpUsingWifiConfiguration() {
+ WifiConfiguration configuration = new WifiConfiguration();
+ configuration.SSID = "test123";
+ configuration.BSSID="bssid";
+ configuration.networkId = 123;
+ configuration.allowedKeyManagement = new BitSet();
+ configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+
+ WifiTracker tracker = new WifiTracker(
+ InstrumentationRegistry.getTargetContext(), null, true, true);
+
+ AccessPoint result = tracker.getCachedOrCreate(configuration, new ArrayList<AccessPoint>());
+ assertTrue(result.mAccessPointListener != null);
+ }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 3084b9b..e7f5f4f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2137,7 +2137,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 133;
+ private static final int SETTINGS_VERSION = 134;
private final int mUserId;
@@ -2452,6 +2452,21 @@
}
if (currentVersion == 130) {
+ // Split Ambient settings
+ final SettingsState secureSettings = getSecureSettingsLocked(userId);
+ boolean dozeExplicitlyDisabled = "0".equals(secureSettings.
+ getSettingLocked(Settings.Secure.DOZE_ENABLED).getValue());
+
+ if (dozeExplicitlyDisabled) {
+ secureSettings.insertSettingLocked(Settings.Secure.DOZE_PULSE_ON_PICK_UP,
+ "0", SettingsState.SYSTEM_PACKAGE_NAME);
+ secureSettings.insertSettingLocked(Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP,
+ "0", SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ currentVersion = 131;
+ }
+
+ if (currentVersion == 131) {
// Initialize new multi-press timeout to default value
final SettingsState systemSecureSettings = getSecureSettingsLocked(userId);
final String oldValue = systemSecureSettings.getSettingLocked(
@@ -2464,11 +2479,11 @@
SettingsState.SYSTEM_PACKAGE_NAME);
}
- currentVersion = 131;
+ currentVersion = 132;
}
- if (currentVersion == 131) {
- // Version 131: Allow managed profile to optionally use the parent's ringtones
+ if (currentVersion == 132) {
+ // Version 132: Allow managed profile to optionally use the parent's ringtones
final SettingsState systemSecureSettings = getSecureSettingsLocked(userId);
String defaultSyncParentSounds = (getContext().getResources()
.getBoolean(R.bool.def_sync_parent_sounds) ? "1" : "0");
@@ -2476,11 +2491,11 @@
Settings.Secure.SYNC_PARENT_SOUNDS,
defaultSyncParentSounds,
SettingsState.SYSTEM_PACKAGE_NAME);
- currentVersion = 132;
+ currentVersion = 133;
}
- if (currentVersion == 132) {
- // Version 132: Add default end button behavior
+ if (currentVersion == 133) {
+ // Version 133: Add default end button behavior
final SettingsState systemSettings = getSystemSettingsLocked(userId);
if (systemSettings.getSettingLocked(Settings.System.END_BUTTON_BEHAVIOR) ==
null) {
@@ -2489,7 +2504,7 @@
systemSettings.insertSettingLocked(Settings.System.END_BUTTON_BEHAVIOR,
defaultEndButtonBehavior, SettingsState.SYSTEM_PACKAGE_NAME);
}
- currentVersion = 133;
+ currentVersion = 134;
}
if (currentVersion != newVersion) {
diff --git a/packages/SystemUI/res/layout/car_navigation_bar.xml b/packages/SystemUI/res/layout/car_navigation_bar.xml
index f7f673d..4b6929f 100644
--- a/packages/SystemUI/res/layout/car_navigation_bar.xml
+++ b/packages/SystemUI/res/layout/car_navigation_bar.xml
@@ -28,7 +28,8 @@
-->
<LinearLayout
android:layout_height="match_parent"
- android:layout_width="match_parent"
+ android:layout_width="@dimen/car_navigation_bar_width"
+ android:layout_gravity="center"
android:orientation="horizontal"
android:clipChildren="false"
android:clipToPadding="false"
diff --git a/packages/SystemUI/res/values/dimens_car.xml b/packages/SystemUI/res/values/dimens_car.xml
index 04402b7..988caf5 100644
--- a/packages/SystemUI/res/values/dimens_car.xml
+++ b/packages/SystemUI/res/values/dimens_car.xml
@@ -29,4 +29,5 @@
<dimen name="car_fullscreen_user_pod_image_avatar_height">128dp</dimen>
<dimen name="car_fullscreen_user_pod_text_size">24sp</dimen>
<dimen name="car_navigation_button_width">64dp</dimen>
+ <dimen name="car_navigation_bar_width">760dp</dimen>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index a7331d8..8a5a8a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -248,6 +248,11 @@
}
}
+ @Override
+ public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
+ // Do nothing, we don't want to display media art in the lock screen for a car.
+ }
+
private int startActivityWithOptions(Intent intent, Bundle options) {
int result = ActivityManager.START_CANCELED;
try {
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index bb5f62b..2825cf9 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -466,9 +466,8 @@
if (mCarModeEnabled) {
if (mLastBroadcastState != Intent.EXTRA_DOCK_STATE_CAR) {
adjustStatusBarCarModeLocked();
-
if (oldAction != null) {
- getContext().sendBroadcastAsUser(new Intent(oldAction), UserHandle.ALL);
+ sendForegroundBroadcastToAllUsers(oldAction);
}
mLastBroadcastState = Intent.EXTRA_DOCK_STATE_CAR;
action = UiModeManager.ACTION_ENTER_CAR_MODE;
@@ -476,7 +475,7 @@
} else if (isDeskDockState(mDockState)) {
if (!isDeskDockState(mLastBroadcastState)) {
if (oldAction != null) {
- getContext().sendBroadcastAsUser(new Intent(oldAction), UserHandle.ALL);
+ sendForegroundBroadcastToAllUsers(oldAction);
}
mLastBroadcastState = mDockState;
action = UiModeManager.ACTION_ENTER_DESK_MODE;
@@ -502,6 +501,7 @@
Intent intent = new Intent(action);
intent.putExtra("enableFlags", enableFlags);
intent.putExtra("disableFlags", disableFlags);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
getContext().sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
mResultReceiver, null, Activity.RESULT_OK, null, null);
@@ -550,6 +550,11 @@
}
}
+ private void sendForegroundBroadcastToAllUsers(String action) {
+ getContext().sendBroadcastAsUser(new Intent(action)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), UserHandle.ALL);
+ }
+
private void updateAfterBroadcastLocked(String action, int enableFlags, int disableFlags) {
// Launch a dock activity
String category = null;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9a638a2..1edc8d2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -219,7 +219,6 @@
import android.util.SparseArray;
import android.util.TimeUtils;
import android.util.Xml;
-import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -256,6 +255,7 @@
import libcore.io.IoUtils;
import libcore.util.EmptyArray;
+import static android.Manifest.permission.CHANGE_CONFIGURATION;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
@@ -279,6 +279,7 @@
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.res.Configuration.UI_MODE_TYPE_TELEVISION;
+import static android.os.Build.VERSION_CODES.N;
import static android.os.Process.PROC_CHAR;
import static android.os.Process.PROC_OUT_LONG;
import static android.os.Process.PROC_PARENS;
@@ -293,6 +294,7 @@
import static android.provider.Settings.System.FONT_SCALE;
import static android.util.TypedValue.COMPLEX_UNIT_DIP;
import static android.view.Display.DEFAULT_DISPLAY;
+
import static com.android.internal.util.XmlUtils.readBooleanAttribute;
import static com.android.internal.util.XmlUtils.readIntAttribute;
import static com.android.internal.util.XmlUtils.readLongAttribute;
@@ -1130,10 +1132,11 @@
private int mConfigurationSeq;
/**
- * Temp object used when global configuration is updated. It is also sent to outer world
- * instead of {@link #getGlobalConfiguration} because we don't trust anyone...
+ * Temp object used when global and/or display override configuration is updated. It is also
+ * sent to outer world instead of {@link #getGlobalConfiguration} because we don't trust
+ * anyone...
*/
- private Configuration mTempGlobalConfig = new Configuration();
+ private Configuration mTempConfig = new Configuration();
private final UpdateConfigurationResult mTmpUpdateConfigurationResult =
new UpdateConfigurationResult();
@@ -2717,14 +2720,14 @@
mTrackingAssociations = "1".equals(SystemProperties.get("debug.track-associations"));
- mTempGlobalConfig.setToDefaults();
- mTempGlobalConfig.setLocales(LocaleList.getDefault());
- mConfigurationSeq = mTempGlobalConfig.seq = 1;
+ mTempConfig.setToDefaults();
+ mTempConfig.setLocales(LocaleList.getDefault());
+ mConfigurationSeq = mTempConfig.seq = 1;
mProcessCpuTracker.init();
mStackSupervisor = new ActivityStackSupervisor(this);
- mStackSupervisor.onConfigurationChanged(mTempGlobalConfig);
+ mStackSupervisor.onConfigurationChanged(mTempConfig);
mCompatModePackages = new CompatModePackages(this, systemDir, mHandler);
mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
mActivityStarter = new ActivityStarter(this, mStackSupervisor);
@@ -4069,7 +4072,7 @@
@Override
public int getPackageProcessState(String packageName, String callingPackage) {
if (!hasUsageStatsPermission(callingPackage)) {
- enforceCallingPermission(android.Manifest.permission.GET_PACKAGE_IMPORTANCE,
+ enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
"getPackageProcessState");
}
@@ -4079,20 +4082,12 @@
final ProcessRecord proc = mLruProcesses.get(i);
if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT
|| procState > proc.setProcState) {
- boolean found = false;
- for (int j=proc.pkgList.size()-1; j>=0 && !found; j--) {
- if (proc.pkgList.keyAt(j).equals(packageName)) {
- procState = proc.setProcState;
- found = true;
- }
+ if (proc.pkgList.containsKey(packageName)) {
+ procState = proc.setProcState;
+ break;
}
- if (proc.pkgDeps != null && !found) {
- for (int j=proc.pkgDeps.size()-1; j>=0; j--) {
- if (proc.pkgDeps.valueAt(j).equals(packageName)) {
- procState = proc.setProcState;
- break;
- }
- }
+ if (proc.pkgDeps != null && proc.pkgDeps.contains(packageName)) {
+ procState = proc.setProcState;
}
}
}
@@ -4762,23 +4757,12 @@
if (r == null) {
return;
}
- TaskRecord task = r.task;
- if (task != null && (!task.mFullscreen || !task.getStack().mFullscreen)) {
- // Fixed screen orientation isn't supported when activities aren't in full screen
- // mode.
- return;
- }
final long origId = Binder.clearCallingIdentity();
- mWindowManager.setAppOrientation(r.appToken, requestedOrientation);
- Configuration config = mWindowManager.updateOrientationFromAppTokens(
- getGlobalConfiguration(), r.mayFreezeScreenLocked(r.app) ? r.appToken : null);
- if (config != null) {
- r.frozenBeforeDestroy = true;
- if (!updateConfigurationLocked(config, r, false)) {
- mStackSupervisor.resumeFocusedStackTopActivityLocked();
- }
+ try {
+ r.setRequestedOrientation(requestedOrientation);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
}
- Binder.restoreCallingIdentity(origId);
}
}
@@ -14877,6 +14861,7 @@
}
if (dumpPackage == null) {
pw.println(" mGlobalConfiguration: " + getGlobalConfiguration());
+ mStackSupervisor.dumpDisplayConfigs(pw, " ");
}
if (dumpAll) {
pw.println(" mConfigWillChange: " + getFocusedStack().mConfigWillChange);
@@ -18939,8 +18924,7 @@
@Override
public void updatePersistentConfiguration(Configuration values) {
- enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
- "updatePersistentConfiguration()");
+ enforceCallingPermission(CHANGE_CONFIGURATION, "updatePersistentConfiguration()");
enforceWriteSettingsPermission("updatePersistentConfiguration()");
if (values == null) {
throw new NullPointerException("Configuration must not be null");
@@ -18965,12 +18949,16 @@
private void updateFontScaleIfNeeded(@UserIdInt int userId) {
final float scaleFactor = Settings.System.getFloatForUser(mContext.getContentResolver(),
FONT_SCALE, 1.0f, userId);
- if (getGlobalConfiguration().fontScale != scaleFactor) {
- final Configuration configuration = mWindowManager.computeNewConfiguration();
- configuration.fontScale = scaleFactor;
- synchronized (this) {
- updatePersistentConfigurationLocked(configuration, userId);
+
+ synchronized (this) {
+ if (getGlobalConfiguration().fontScale == scaleFactor) {
+ return;
}
+
+ final Configuration configuration
+ = mWindowManager.computeNewConfiguration(DEFAULT_DISPLAY);
+ configuration.fontScale = scaleFactor;
+ updatePersistentConfigurationLocked(configuration, userId);
}
}
@@ -18995,21 +18983,20 @@
@Override
public boolean updateConfiguration(Configuration values) {
- enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
- "updateConfiguration()");
+ enforceCallingPermission(CHANGE_CONFIGURATION, "updateConfiguration()");
synchronized(this) {
if (values == null && mWindowManager != null) {
// sentinel: fetch the current configuration from the window manager
- values = mWindowManager.computeNewConfiguration();
+ values = mWindowManager.computeNewConfiguration(DEFAULT_DISPLAY);
}
if (mWindowManager != null) {
+ // Update OOM levels based on display size.
mProcessList.applyDisplaySize(mWindowManager);
}
final long origId = Binder.clearCallingIdentity();
-
try {
if (values != null) {
Settings.System.clearConfiguration(values);
@@ -19096,8 +19083,8 @@
/** Update default (global) configuration and notify listeners about changes. */
private int updateGlobalConfiguration(@NonNull Configuration values, boolean initLocale,
boolean persistent, int userId, boolean deferResume) {
- mTempGlobalConfig.setTo(getGlobalConfiguration());
- final int changes = mTempGlobalConfig.updateFrom(values);
+ mTempConfig.setTo(getGlobalConfiguration());
+ final int changes = mTempConfig.updateFrom(values);
if (changes == 0) {
return 0;
}
@@ -19124,33 +19111,33 @@
}
mConfigurationSeq = Math.max(++mConfigurationSeq, 1);
- mTempGlobalConfig.seq = mConfigurationSeq;
+ mTempConfig.seq = mConfigurationSeq;
// Update stored global config and notify everyone about the change.
- mStackSupervisor.onConfigurationChanged(mTempGlobalConfig);
+ mStackSupervisor.onConfigurationChanged(mTempConfig);
- Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + mTempGlobalConfig);
+ Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + mTempConfig);
// TODO(multi-display): Update UsageEvents#Event to include displayId.
- mUsageStatsService.reportConfigurationChange(mTempGlobalConfig,
+ mUsageStatsService.reportConfigurationChange(mTempConfig,
mUserController.getCurrentUserIdLocked());
// TODO: If our config changes, should we auto dismiss any currently showing dialogs?
- mShowDialogs = shouldShowDialogs(mTempGlobalConfig, mInVrMode);
+ mShowDialogs = shouldShowDialogs(mTempConfig, mInVrMode);
AttributeCache ac = AttributeCache.instance();
if (ac != null) {
- ac.updateConfiguration(mTempGlobalConfig);
+ ac.updateConfiguration(mTempConfig);
}
// Make sure all resources in our process are updated right now, so that anyone who is going
// to retrieve resource values after we return will be sure to get the new ones. This is
// especially important during boot, where the first config change needs to guarantee all
// resources have that config before following boot code is executed.
- mSystemThread.applyConfigurationToResources(mTempGlobalConfig);
+ mSystemThread.applyConfigurationToResources(mTempConfig);
// We need another copy of global config because we're scheduling some calls instead of
// running them in place. We need to be sure that object we send will be handled unchanged.
- final Configuration configCopy = new Configuration(mTempGlobalConfig);
+ final Configuration configCopy = new Configuration(mTempConfig);
if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {
Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG);
msg.obj = configCopy;
@@ -19158,16 +19145,6 @@
mHandler.sendMessage(msg);
}
- // TODO(multi-display): Clear also on secondary display density change?
- final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
- if (isDensityChange) {
- // Reset the unsupported display size dialog.
- mUiHandler.sendEmptyMessage(SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG);
-
- killAllBackgroundProcessesExcept(Build.VERSION_CODES.N,
- ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
- }
-
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
ProcessRecord app = mLruProcesses.get(i);
try {
@@ -19197,13 +19174,116 @@
UserHandle.USER_ALL);
}
+ // Override configuration of the default display duplicates global config, so we need to
+ // update it also. This will also notify WindowManager about changes.
+ performDisplayOverrideConfigUpdate(mStackSupervisor.getConfiguration(), deferResume,
+ DEFAULT_DISPLAY);
+
+ return changes;
+ }
+
+ @Override
+ public boolean updateDisplayOverrideConfiguration(Configuration values, int displayId) {
+ enforceCallingPermission(CHANGE_CONFIGURATION, "updateDisplayOverrideConfiguration()");
+
+ synchronized (this) {
+ if (values == null && mWindowManager != null) {
+ // sentinel: fetch the current configuration from the window manager
+ values = mWindowManager.computeNewConfiguration(displayId);
+ }
+
+ if (mWindowManager != null) {
+ // Update OOM levels based on display size.
+ mProcessList.applyDisplaySize(mWindowManager);
+ }
+
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ if (values != null) {
+ Settings.System.clearConfiguration(values);
+ }
+ updateDisplayOverrideConfigurationLocked(values, null /* starting */,
+ false /* deferResume */, displayId, mTmpUpdateConfigurationResult);
+ return mTmpUpdateConfigurationResult.changes != 0;
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+ }
+
+ boolean updateDisplayOverrideConfigurationLocked(Configuration values, ActivityRecord starting,
+ boolean deferResume, int displayId) {
+ return updateDisplayOverrideConfigurationLocked(values, starting, deferResume /* deferResume */,
+ displayId, null /* result */);
+ }
+
+ /**
+ * Updates override configuration specific for the selected display. If no config is provided,
+ * new one will be computed in WM based on current display info.
+ */
+ private boolean updateDisplayOverrideConfigurationLocked(Configuration values,
+ ActivityRecord starting, boolean deferResume, int displayId,
+ UpdateConfigurationResult result) {
+ int changes = 0;
+ boolean kept = true;
+
+ if (mWindowManager != null) {
+ mWindowManager.deferSurfaceLayout();
+ }
+ try {
+ if (values != null) {
+ if (displayId == DEFAULT_DISPLAY) {
+ // Override configuration of the default display duplicates global config, so
+ // we're calling global config update instead for default display. It will also
+ // apply the correct override config.
+ changes = updateGlobalConfiguration(values, false /* initLocale */,
+ false /* persistent */, UserHandle.USER_NULL /* userId */, deferResume);
+ } else {
+ changes = performDisplayOverrideConfigUpdate(values, deferResume, displayId);
+ }
+ }
+
+ kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);
+ } finally {
+ if (mWindowManager != null) {
+ mWindowManager.continueSurfaceLayout();
+ }
+ }
+
+ if (result != null) {
+ result.changes = changes;
+ result.activityRelaunched = !kept;
+ }
+ return kept;
+ }
+
+ private int performDisplayOverrideConfigUpdate(Configuration values, boolean deferResume,
+ int displayId) {
+ mTempConfig.setTo(mStackSupervisor.getDisplayOverrideConfiguration(displayId));
+ final int changes = mTempConfig.updateFrom(values);
+ if (changes == 0) {
+ return 0;
+ }
+
+ Slog.i(TAG, "Override config changes=" + Integer.toHexString(changes) + " " + mTempConfig
+ + " for displayId=" + displayId);
+ mStackSupervisor.setDisplayOverrideConfiguration(mTempConfig, displayId);
+
+ final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
+ if (isDensityChange) {
+ // Reset the unsupported display size dialog.
+ mUiHandler.sendEmptyMessage(SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG);
+
+ killAllBackgroundProcessesExcept(N, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ }
+
// Update the configuration with WM first and check if any of the stacks need to be resized
// due to the configuration change. If so, resize the stacks now and do any relaunches if
// necessary. This way we don't need to relaunch again afterwards in
// ensureActivityConfigurationLocked().
if (mWindowManager != null) {
final int[] resizedStacks =
- mWindowManager.setNewConfiguration(mTempGlobalConfig);
+ mWindowManager.setNewDisplayOverrideConfiguration(mTempConfig, displayId);
if (resizedStacks != null) {
for (int stackId : resizedStacks) {
resizeStackWithBoundsFromWindowManager(stackId, deferResume);
@@ -22403,4 +22483,29 @@
// before the profile user is unlocked.
return rInfo != null && rInfo.activityInfo != null;
}
+
+ /**
+ * Attach an agent to the specified process (proces name or PID)
+ */
+ public void attachAgent(String process, String path) {
+ try {
+ synchronized (this) {
+ ProcessRecord proc = findProcessLocked(process, UserHandle.USER_SYSTEM, "attachAgent");
+ if (proc == null || proc.thread == null) {
+ throw new IllegalArgumentException("Unknown process: " + process);
+ }
+
+ boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0"));
+ if (!isDebuggable) {
+ if ((proc.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
+ throw new SecurityException("Process not debuggable: " + proc);
+ }
+ }
+
+ proc.thread.attachAgent(path);
+ }
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Process disappeared");
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index c7a04c1..7a692b6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -220,6 +220,8 @@
return runTask(pw);
case "write":
return runWrite(pw);
+ case "attach-agent":
+ return runAttachAgent(pw);
default:
return handleDefaultCommands(cmd);
}
@@ -2462,6 +2464,21 @@
return 0;
}
+ int runAttachAgent(PrintWriter pw) {
+ // TODO: revisit the permissions required for attaching agents
+ mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
+ "attach-agent");
+ String process = getNextArgRequired();
+ String agent = getNextArgRequired();
+ String opt;
+ if ((opt = getNextArg()) != null) {
+ pw.println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ mInternal.attachAgent(process, agent);
+ return 0;
+ }
+
@Override
public void onHelp() {
PrintWriter pw = getOutPrintWriter();
@@ -2619,6 +2636,8 @@
pw.println(" Optionally controls lenient background check mode, returns current mode.");
pw.println(" get-uid-state <UID>");
pw.println(" Gets the process state of an app given its <UID>.");
+ pw.println(" attach-agent <PROCESS> <FILE>");
+ pw.println(" Attach an agent to the specified <PROCESS>, which may be either a process name or a PID.");
pw.println(" get-config");
pw.println(" Rtrieve the configuration and any recent configurations of the device.");
pw.println(" suppress-resize-config-changes <true|false>");
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index b7618a1..abe4f30 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -1646,6 +1646,17 @@
return null;
}
+ /**
+ * @return display id to which this record is attached, -1 if not attached.
+ */
+ int getDisplayId() {
+ final ActivityStack stack = getStack();
+ if (stack == null) {
+ return -1;
+ }
+ return stack.mDisplayId;
+ }
+
final boolean isDestroyable() {
if (finishing || app == null || state == ActivityState.DESTROYING
|| state == ActivityState.DESTROYED) {
@@ -1704,6 +1715,26 @@
}
}
+ void setRequestedOrientation(int requestedOrientation) {
+ if (task != null && (!task.mFullscreen || !task.getStack().mFullscreen)) {
+ // Fixed screen orientation isn't supported when activities aren't in full screen mode.
+ return;
+ }
+
+ service.mWindowManager.setAppOrientation(appToken, requestedOrientation);
+ final int displayId = getDisplayId();
+ final Configuration config = service.mWindowManager.updateOrientationFromAppTokens(
+ mStackSupervisor.getDisplayOverrideConfiguration(displayId),
+ mayFreezeScreenLocked(app) ? appToken : null, displayId);
+ if (config != null) {
+ frozenBeforeDestroy = true;
+ if (!service.updateDisplayOverrideConfigurationLocked(config, this,
+ false /* deferResume */, displayId)) {
+ mStackSupervisor.resumeFocusedStackTopActivityLocked();
+ }
+ }
+ }
+
// TODO: now used only in one place to address race-condition. Remove when that will be fixed.
void setLastReportedConfiguration(@NonNull Configuration config) {
mLastReportedConfiguration.setTo(config);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 55066fd..22d8078 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -2285,13 +2285,14 @@
// the screen based on the new activity order.
boolean notUpdated = true;
if (mStackSupervisor.isFocusedStack(this)) {
- Configuration config = mWindowManager.updateOrientationFromAppTokens(
- mService.getGlobalConfiguration(),
- next.mayFreezeScreenLocked(next.app) ? next.appToken : null);
+ final Configuration config = mWindowManager.updateOrientationFromAppTokens(
+ mStackSupervisor.getDisplayOverrideConfiguration(mDisplayId),
+ next.mayFreezeScreenLocked(next.app) ? next.appToken : null, mDisplayId);
if (config != null) {
next.frozenBeforeDestroy = true;
}
- notUpdated = !mService.updateConfigurationLocked(config, next, false);
+ notUpdated = !mService.updateDisplayOverrideConfigurationLocked(config, next,
+ false /* deferResume */, mDisplayId);
}
if (notUpdated) {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index a111230..ca36908 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -419,7 +419,7 @@
}
@Override
- protected ConfigurationContainer getChildAt(int index) {
+ protected ActivityDisplay getChildAt(int index) {
return mActivityDisplays.valueAt(index);
}
@@ -428,6 +428,24 @@
return null;
}
+ Configuration getDisplayOverrideConfiguration(int displayId) {
+ final ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
+ if (activityDisplay == null) {
+ throw new IllegalArgumentException("No display found with id: " + displayId);
+ }
+
+ return activityDisplay.getOverrideConfiguration();
+ }
+
+ void setDisplayOverrideConfiguration(Configuration overrideConfiguration, int displayId) {
+ final ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
+ if (activityDisplay == null) {
+ throw new IllegalArgumentException("No display found with id: " + displayId);
+ }
+
+ activityDisplay.onOverrideConfigurationChanged(overrideConfiguration);
+ }
+
static class FindTaskResult {
ActivityRecord r;
boolean matchedByRootAffinity;
@@ -1190,20 +1208,20 @@
r.startLaunchTickingLocked();
}
- // Have the window manager re-evaluate the orientation of
- // the screen based on the new activity order. Note that
- // as a result of this, it can call back into the activity
- // manager with a new orientation. We don't care about that,
- // because the activity is not currently running so we are
- // just restarting it anyway.
+ // Have the window manager re-evaluate the orientation of the screen based on the new
+ // activity order. Note that as a result of this, it can call back into the activity
+ // manager with a new orientation. We don't care about that, because the activity is not
+ // currently running so we are just restarting it anyway.
if (checkConfig) {
- Configuration config = mWindowManager.updateOrientationFromAppTokens(
- mService.getGlobalConfiguration(),
- r.mayFreezeScreenLocked(app) ? r.appToken : null);
+ final int displayId = r.getDisplayId();
+ final Configuration config = mWindowManager.updateOrientationFromAppTokens(
+ getDisplayOverrideConfiguration(displayId),
+ r.mayFreezeScreenLocked(app) ? r.appToken : null, displayId);
// Deferring resume here because we're going to launch new activity shortly.
// We don't want to perform a redundant launch of the same record while ensuring
// configurations and trying to resume top activity of focused stack.
- mService.updateConfigurationLocked(config, r, false, true /* deferResume */);
+ mService.updateDisplayOverrideConfigurationLocked(config, r, true /* deferResume */,
+ displayId);
}
r.app = app;
@@ -3156,6 +3174,20 @@
}
/**
+ * Dump all connected displays' configurations.
+ * @param prefix Prefix to apply to each line of the dump.
+ */
+ void dumpDisplayConfigs(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.println("Display override configurations:");
+ final int displayCount = mActivityDisplays.size();
+ for (int i = 0; i < displayCount; i++) {
+ final ActivityDisplay activityDisplay = mActivityDisplays.valueAt(i);
+ pw.print(prefix); pw.print(" "); pw.print(activityDisplay.mDisplayId); pw.print(": ");
+ pw.println(activityDisplay.getOverrideConfiguration());
+ }
+ }
+
+ /**
* Dumps the activities matching the given {@param name} in the either the focused stack
* or all visible stacks if {@param dumpVisibleStacks} is true.
*/
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index b8b16d5..6e5c025 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5408,18 +5408,15 @@
}
} else if (mDismissKeyguard != DISMISS_KEYGUARD_NONE) {
mKeyguardHidden = false;
- boolean willDismiss = false;
+ boolean dismissKeyguard = false;
+ final boolean trusted = mKeyguardDelegate.isTrusted();
if (mDismissKeyguard == DISMISS_KEYGUARD_START) {
- final boolean trusted = mKeyguardDelegate.isTrusted();
- willDismiss = trusted && mKeyguardOccluded && mKeyguardDelegate != null
- && mKeyguardDelegate.isShowing();
+ final boolean willDismiss = trusted && mKeyguardOccluded
+ && mKeyguardDelegate != null && mKeyguardDelegate.isShowing();
if (willDismiss) {
mCurrentlyDismissingKeyguard = true;
}
-
- // Only launch the next keyguard unlock window once per window.
- mHandler.post(() -> mKeyguardDelegate.dismiss(
- trusted /* allowWhileOccluded */));
+ dismissKeyguard = true;
}
// If we are currently dismissing Keyguard, there is no need to unocclude it.
@@ -5430,6 +5427,12 @@
| FINISH_LAYOUT_REDO_WALLPAPER;
}
}
+
+ if (dismissKeyguard) {
+ // Only launch the next keyguard unlock window once per window.
+ mHandler.post(() -> mKeyguardDelegate.dismiss(
+ trusted /* allowWhileOccluded */));
+ }
} else {
mWinDismissingKeyguard = null;
mSecureDismissingKeyguard = false;
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 56eaa83..d46b535 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -949,7 +949,7 @@
mService.updateFocusedWindowLocked(
UPDATE_FOCUS_WILL_PLACE_SURFACES, true /*updateInputWindows*/);
- mService.getDefaultDisplayContentLocked().setLayoutNeeded();
+ getDisplayContent().setLayoutNeeded();
mService.mWindowPlacerLocked.performSurfacePlacement();
Binder.restoreCallingIdentity(origId);
return true;
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 8b5f62e..f75f224 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -371,6 +371,7 @@
}
void notifyDockedStackExistsChanged(boolean exists) {
+ // TODO(multi-display): Perform all actions only for current display.
final int size = mDockedStackListeners.beginBroadcast();
for (int i = 0; i < size; ++i) {
final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 065a3dc..6a06ef3 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.INPUT_CONSUMER_PIP;
import static android.view.WindowManager.INPUT_CONSUMER_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
@@ -384,7 +385,8 @@
/* Notifies that the input device configuration has changed. */
@Override
public void notifyConfigurationChanged() {
- mService.sendNewConfiguration();
+ // TODO(multi-display): Notify proper displays that are associated with this input device.
+ mService.sendNewConfiguration(DEFAULT_DISPLAY);
synchronized (mInputDevicesReadyMonitor) {
if (!mInputDevicesReady) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 24a0d1a..90e27ea 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -236,7 +236,7 @@
mService.configureDisplayPolicyLocked(dc);
// TODO(multi-display): Create an input channel for each display with touch capability.
- if (displayId == Display.DEFAULT_DISPLAY) {
+ if (displayId == DEFAULT_DISPLAY) {
dc.mTapDetector = new TaskTapPointerEventListener(
mService, dc);
mService.registerPointerEventListener(dc.mTapDetector);
@@ -274,8 +274,7 @@
mService.mStackIdToStack.put(stackId, stack);
if (stackId == DOCKED_STACK_ID) {
- mService.getDefaultDisplayContentLocked().mDividerControllerLocked
- .notifyDockedStackExistsChanged(true);
+ dc.mDividerControllerLocked.notifyDockedStackExistsChanged(true);
}
}
@@ -569,8 +568,33 @@
}
}
- /** Set new config and return array of ids of stacks that were changed during update. */
- int[] setGlobalConfigurationIfNeeded(Configuration newConfiguration) {
+ /**
+ * Set new display override config and return array of ids of stacks that were changed during
+ * update. If called for the default display, global configuration will also be updated.
+ */
+ int[] setDisplayOverrideConfigurationIfNeeded(Configuration newConfiguration, int displayId) {
+ final DisplayContent displayContent = getDisplayContent(displayId);
+ if (displayContent == null) {
+ throw new IllegalArgumentException("Display not found for id: " + displayId);
+ }
+
+ final Configuration currentConfig = displayContent.getOverrideConfiguration();
+ final boolean configChanged = currentConfig.diff(newConfiguration) != 0;
+ if (!configChanged) {
+ return null;
+ }
+ displayContent.onOverrideConfigurationChanged(currentConfig);
+
+ if (displayId == DEFAULT_DISPLAY) {
+ // Override configuration of the default display duplicates global config. In this case
+ // we also want to update the global config.
+ return setGlobalConfigurationIfNeeded(newConfiguration);
+ } else {
+ return updateStackBoundsAfterConfigChange(displayId);
+ }
+ }
+
+ private int[] setGlobalConfigurationIfNeeded(Configuration newConfiguration) {
final boolean configChanged = getConfiguration().diff(newConfiguration) != 0;
if (!configChanged) {
return null;
@@ -603,6 +627,16 @@
return mChangedStackList.isEmpty() ? null : ArrayUtils.convertToIntArray(mChangedStackList);
}
+ /** Same as {@link #updateStackBoundsAfterConfigChange()} but only for a specific display. */
+ private int[] updateStackBoundsAfterConfigChange(int displayId) {
+ mChangedStackList.clear();
+
+ final DisplayContent dc = getDisplayContent(displayId);
+ dc.updateStackBoundsAfterConfigChange(mChangedStackList);
+
+ return mChangedStackList.isEmpty() ? null : ArrayUtils.convertToIntArray(mChangedStackList);
+ }
+
private void prepareFreezingTaskBounds() {
for (int i = mChildren.size() - 1; i >= 0; i--) {
mChildren.get(i).prepareFreezingTaskBounds();
@@ -1145,8 +1179,10 @@
if (mUpdateRotation) {
if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-rotate rotation");
- if (mService.updateRotationUncheckedLocked(false)) {
- mService.mH.sendEmptyMessage(SEND_NEW_CONFIGURATION);
+ // TODO(multi-display): Update rotation for different displays separately.
+ final int displayId = defaultDisplay.getDisplayId();
+ if (mService.updateRotationUncheckedLocked(false, displayId)) {
+ mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
} else {
mUpdateRotation = false;
}
@@ -1231,7 +1267,7 @@
final int displayId = dc.getDisplayId();
final int dw = displayInfo.logicalWidth;
final int dh = displayInfo.logicalHeight;
- final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
+ final boolean isDefaultDisplay = (displayId == DEFAULT_DISPLAY);
final WindowSurfacePlacer surfacePlacer = mService.mWindowPlacerLocked;
// Reset for each display.
@@ -1259,9 +1295,9 @@
if (isDefaultDisplay
&& (dc.pendingLayoutChanges & FINISH_LAYOUT_REDO_CONFIG) != 0) {
if (DEBUG_LAYOUT) Slog.v(TAG, "Computing new config from layout");
- if (mService.updateOrientationFromAppTokensLocked(true)) {
+ if (mService.updateOrientationFromAppTokensLocked(true, displayId)) {
dc.setLayoutNeeded();
- mService.mH.sendEmptyMessage(SEND_NEW_CONFIGURATION);
+ mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 19c9b7d..7f543f9 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -327,8 +327,9 @@
* the adjusted bounds's top.
*/
void alignToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds, boolean alignBottom) {
- final Configuration overrideConfig = getOverrideConfiguration();
- if (!isResizeable() || Configuration.EMPTY.equals(overrideConfig)) {
+ // Task override config might be empty, while display or stack override config isn't, so
+ // we have to check merged override config here.
+ if (!isResizeable() || Configuration.EMPTY.equals(getMergedOverrideConfiguration())) {
return;
}
@@ -340,7 +341,7 @@
mTmpRect2.offsetTo(adjustedBounds.left, adjustedBounds.top);
}
setTempInsetBounds(tempInsetBounds);
- resizeLocked(mTmpRect2, overrideConfig, false /* forced */);
+ resizeLocked(mTmpRect2, getOverrideConfiguration(), false /* forced */);
}
/** Return true if the current bound can get outputted to the rest of the system as-is. */
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 9effb8d..5402f0a 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -448,8 +448,7 @@
// Calculate the current position.
final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
- final int dividerSize = mService.getDefaultDisplayContentLocked()
- .getDockedDividerController().getContentWidth();
+ final int dividerSize = mDisplayContent.getDockedDividerController().getContentWidth();
final int dockSide = getDockSide(outBounds);
final int dividerPosition = DockedDividerUtils.calculatePositionForBounds(outBounds,
dockSide, dividerSize);
@@ -783,13 +782,14 @@
mAnimationBackgroundSurface.destroySurface();
mAnimationBackgroundSurface = null;
}
+ final DockedStackDividerController dividerController =
+ mDisplayContent.mDividerControllerLocked;
mDisplayContent = null;
mService.mWindowPlacerLocked.requestTraversal();
if (mStackId == DOCKED_STACK_ID) {
- mService.getDefaultDisplayContentLocked().mDividerControllerLocked
- .notifyDockedStackExistsChanged(false);
+ dividerController.notifyDockedStackExistsChanged(false);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 40001b2..e6c9512 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1413,7 +1413,7 @@
win.applyAdjustForImeIfNeeded();
if (type == TYPE_DOCK_DIVIDER) {
- getDefaultDisplayContentLocked().getDockedDividerController().setWindow(win);
+ mRoot.getDisplayContent(displayId).getDockedDividerController().setWindow(win);
}
final WindowStateAnimator winAnimator = win.mWinAnimator;
@@ -1480,13 +1480,13 @@
if (localLOGV || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addWindow: New client "
+ client.asBinder() + ": window=" + win + " Callers=" + Debug.getCallers(5));
- if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked(false)) {
+ if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked(false, displayId)) {
reportNewConfig = true;
}
}
if (reportNewConfig) {
- sendNewConfiguration();
+ sendNewConfiguration(displayId);
}
Binder.restoreCallingIdentity(origId);
@@ -1881,11 +1881,13 @@
== PackageManager.PERMISSION_GRANTED;
long origId = Binder.clearCallingIdentity();
+ final int displayId;
synchronized(mWindowMap) {
WindowState win = windowForClientLocked(session, client, false);
if (win == null) {
return 0;
}
+ displayId = win.getDisplayId();
WindowStateAnimator winAnimator = win.mWinAnimator;
if (viewVisibility != View.GONE) {
@@ -2066,16 +2068,16 @@
}
if (wallpaperMayMove) {
- getDefaultDisplayContentLocked().pendingLayoutChanges |=
+ win.getDisplayContent().pendingLayoutChanges |=
WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
}
win.setDisplayLayoutNeeded();
win.mGivenInsetsPending = (flags&WindowManagerGlobal.RELAYOUT_INSETS_PENDING) != 0;
- configChanged = updateOrientationFromAppTokensLocked(false);
+ configChanged = updateOrientationFromAppTokensLocked(false, displayId);
mWindowPlacerLocked.performSurfacePlacement();
if (toBeDisplayed && win.mIsWallpaper) {
- DisplayInfo displayInfo = getDefaultDisplayInfoLocked();
+ DisplayInfo displayInfo = win.getDisplayContent().getDisplayInfo();
dc.mWallpaperController.updateWallpaperOffset(
win, displayInfo.logicalWidth, displayInfo.logicalHeight, false);
}
@@ -2121,7 +2123,7 @@
}
if (configChanged) {
- sendNewConfiguration();
+ sendNewConfiguration(displayId);
}
Binder.restoreCallingIdentity(origId);
return result;
@@ -2156,9 +2158,8 @@
}
win.destroyOrSaveSurface();
}
- //TODO (multidisplay): Magnification is supported only for the default
- if (mAccessibilityController != null
- && win.getDisplayId() == DEFAULT_DISPLAY) {
+ // TODO(multidisplay): Magnification is supported only for the default display.
+ if (mAccessibilityController != null && win.getDisplayId() == DEFAULT_DISPLAY) {
mAccessibilityController.onWindowTransitionLocked(win, transit);
}
return focusMayChange;
@@ -2278,7 +2279,7 @@
}
}
- public void finishDrawingWindow(Session session, IWindow client) {
+ void finishDrawingWindow(Session session, IWindow client) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mWindowMap) {
@@ -2287,7 +2288,7 @@
+ (win != null ? win.mWinAnimator.drawStateToString() : "null"));
if (win != null && win.mWinAnimator.finishDrawingLocked()) {
if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
- getDefaultDisplayContentLocked().pendingLayoutChanges |=
+ win.getDisplayContent().pendingLayoutChanges |=
WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
}
win.setDisplayLayoutNeeded();
@@ -2530,32 +2531,34 @@
}
@Override
- public Configuration updateOrientationFromAppTokens(
- Configuration currentConfig, IBinder freezeThisOneIfNeeded) {
+ public Configuration updateOrientationFromAppTokens(Configuration currentConfig,
+ IBinder freezeThisOneIfNeeded, int displayId) {
if (!checkCallingPermission(MANAGE_APP_TOKENS, "updateOrientationFromAppTokens()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
- Configuration config = null;
- long ident = Binder.clearCallingIdentity();
-
- synchronized(mWindowMap) {
- config = updateOrientationFromAppTokensLocked(currentConfig,
- freezeThisOneIfNeeded);
+ final Configuration config;
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized(mWindowMap) {
+ config = updateOrientationFromAppTokensLocked(currentConfig, freezeThisOneIfNeeded,
+ displayId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
- Binder.restoreCallingIdentity(ident);
return config;
}
- private Configuration updateOrientationFromAppTokensLocked(
- Configuration currentConfig, IBinder freezeThisOneIfNeeded) {
+ private Configuration updateOrientationFromAppTokensLocked(Configuration currentConfig,
+ IBinder freezeThisOneIfNeeded, int displayId) {
if (!mDisplayReady) {
return null;
}
Configuration config = null;
- if (updateOrientationFromAppTokensLocked(false)) {
+ if (updateOrientationFromAppTokensLocked(false, displayId)) {
// If we changed the orientation but mOrientationChangeComplete is already true,
// we used seamless rotation, and we don't need to freeze the screen.
if (freezeThisOneIfNeeded != null && !mRoot.mOrientationChangeComplete) {
@@ -2564,7 +2567,7 @@
atoken.startFreezingScreen();
}
}
- config = computeNewConfigurationLocked();
+ config = computeNewConfigurationLocked(displayId);
} else if (currentConfig != null) {
// No obvious action we need to take, but if our current state mismatches the activity
@@ -2574,10 +2577,10 @@
// to keep override configs clear of non-empty values (e.g. fontSize).
mTempConfiguration.unset();
mTempConfiguration.updateFrom(currentConfig);
- computeScreenConfigurationLocked(mTempConfiguration);
+ computeScreenConfigurationLocked(mTempConfiguration, displayId);
if (currentConfig.diff(mTempConfiguration) != 0) {
mWaitingForConfig = true;
- final DisplayContent displayContent = getDefaultDisplayContentLocked();
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
displayContent.setLayoutNeeded();
int anim[] = new int[2];
if (displayContent.isDimming()) {
@@ -2593,31 +2596,28 @@
return config;
}
- /*
- * Determine the new desired orientation of the display, returning
- * a non-null new Configuration if it has changed from the current
- * orientation. IF TRUE IS RETURNED SOMEONE MUST CALL
- * setNewConfiguration() TO TELL THE WINDOW MANAGER IT CAN UNFREEZE THE
- * SCREEN. This will typically be done for you if you call
- * sendNewConfiguration().
+ /**
+ * Determine the new desired orientation of the display, returning a non-null new Configuration
+ * if it has changed from the current orientation. IF TRUE IS RETURNED SOMEONE MUST CALL
+ * {@link #setNewDisplayOverrideConfiguration(Configuration, int)} TO TELL THE WINDOW MANAGER IT
+ * CAN UNFREEZE THE SCREEN. This will typically be done for you if you call
+ * {@link #sendNewConfiguration(int)}.
*
- * The orientation is computed from non-application windows first. If none of
- * the non-application windows specify orientation, the orientation is computed from
- * application tokens.
- * @see android.view.IWindowManager#updateOrientationFromAppTokens(
- * android.os.IBinder)
+ * The orientation is computed from non-application windows first. If none of the
+ * non-application windows specify orientation, the orientation is computed from application
+ * tokens.
+ * @see android.view.IWindowManager#updateOrientationFromAppTokens(Configuration, IBinder, int)
*/
- boolean updateOrientationFromAppTokensLocked(boolean inTransaction) {
+ boolean updateOrientationFromAppTokensLocked(boolean inTransaction, int displayId) {
long ident = Binder.clearCallingIdentity();
try {
- // TODO: multi-display
- int req = getDefaultDisplayContentLocked().getOrientation();
+ final int req = mRoot.getDisplayContent(displayId).getOrientation();
if (req != mLastOrientation) {
mLastOrientation = req;
//send a message to Policy indicating orientation change to take
//action like disabling/enabling sensors etc.,
mPolicy.setCurrentOrientationLw(req);
- if (updateRotationUncheckedLocked(inTransaction)) {
+ if (updateRotationUncheckedLocked(inTransaction, displayId)) {
// changed
return true;
}
@@ -2643,8 +2643,8 @@
}
@Override
- public int[] setNewConfiguration(Configuration config) {
- if (!checkCallingPermission(MANAGE_APP_TOKENS, "setNewConfiguration()")) {
+ public int[] setNewDisplayOverrideConfiguration(Configuration overrideConfig, int displayId) {
+ if (!checkCallingPermission(MANAGE_APP_TOKENS, "setNewDisplayOverrideConfiguration()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
@@ -2653,7 +2653,7 @@
mWaitingForConfig = false;
mLastFinishedFreezeSource = "new-config";
}
- return mRoot.setGlobalConfigurationIfNeeded(config);
+ return mRoot.setDisplayOverrideConfigurationIfNeeded(overrideConfig, displayId);
}
}
@@ -4436,8 +4436,9 @@
}
try {
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenshotWallpaper");
- return screenshotApplicationsInner(null, DEFAULT_DISPLAY, -1, -1, true, 1f,
- Bitmap.Config.ARGB_8888, true);
+ return screenshotApplicationsInner(null /* appToken */, DEFAULT_DISPLAY, -1 /* width */,
+ -1 /* height */, true /* includeFullDisplay */, 1f /* frameScale */,
+ Bitmap.Config.ARGB_8888, true /* wallpaperOnly */);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
@@ -4455,15 +4456,13 @@
throw new SecurityException("Requires READ_FRAME_BUFFER permission");
}
- FgThread.getHandler().post(new Runnable() {
- @Override
- public void run() {
- Bitmap bm = screenshotApplicationsInner(null, DEFAULT_DISPLAY, -1, -1,
- true, 1f, Bitmap.Config.ARGB_8888, false);
- try {
- receiver.send(bm);
- } catch (RemoteException e) {
- }
+ FgThread.getHandler().post(() -> {
+ Bitmap bm = screenshotApplicationsInner(null /* appToken */, DEFAULT_DISPLAY,
+ -1 /* width */, -1 /* height */, true /* includeFullDisplay */,
+ 1f /* frameScale */, Bitmap.Config.ARGB_8888, false /* wallpaperOnly */);
+ try {
+ receiver.send(bm);
+ } catch (RemoteException e) {
}
});
@@ -4864,16 +4863,17 @@
if (mDeferredRotationPauseCount > 0) {
mDeferredRotationPauseCount -= 1;
if (mDeferredRotationPauseCount == 0) {
- boolean changed = updateRotationUncheckedLocked(false);
+ // TODO(multi-display): Update rotation for different displays separately.
+ final int displayId = DEFAULT_DISPLAY;
+ final boolean changed = updateRotationUncheckedLocked(false, displayId);
if (changed) {
- mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
+ mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayId).sendToTarget();
}
}
}
}
- private void updateRotationUnchecked(boolean alwaysSendConfiguration,
- boolean forceRelayout) {
+ private void updateRotationUnchecked(boolean alwaysSendConfiguration, boolean forceRelayout) {
if(DEBUG_ORIENTATION) Slog.v(TAG_WM, "updateRotationUnchecked:"
+ " alwaysSendConfiguration=" + alwaysSendConfiguration
+ " forceRelayout=" + forceRelayout);
@@ -4882,8 +4882,10 @@
try {
final boolean rotationChanged;
+ // TODO(multi-display): Update rotation for different displays separately.
+ int displayId = DEFAULT_DISPLAY;
synchronized (mWindowMap) {
- rotationChanged = updateRotationUncheckedLocked(false);
+ rotationChanged = updateRotationUncheckedLocked(false, displayId);
if (!rotationChanged || forceRelayout) {
getDefaultDisplayContentLocked().setLayoutNeeded();
mWindowPlacerLocked.performSurfacePlacement();
@@ -4891,22 +4893,20 @@
}
if (rotationChanged || alwaysSendConfiguration) {
- sendNewConfiguration();
+ sendNewConfiguration(displayId);
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
-
- // TODO(multidisplay): Rotate any display?
/**
- * Updates the current rotation.
+ * Updates the current rotation of the specified display.
*
- * Returns true if the rotation has been changed. In this case YOU
- * MUST CALL sendNewConfiguration() TO UNFREEZE THE SCREEN.
+ * Returns true if the rotation has been changed. In this case YOU MUST CALL
+ * {@link #sendNewConfiguration(int)} TO UNFREEZE THE SCREEN.
*/
- boolean updateRotationUncheckedLocked(boolean inTransaction) {
+ boolean updateRotationUncheckedLocked(boolean inTransaction, int displayId) {
if (mDeferredRotationPauseCount > 0) {
// Rotation updates have been paused temporarily. Defer the update until
// updates have been resumed.
@@ -4915,7 +4915,7 @@
}
ScreenRotationAnimation screenRotationAnimation =
- mAnimator.getScreenRotationAnimationLocked(DEFAULT_DISPLAY);
+ mAnimator.getScreenRotationAnimationLocked(displayId);
if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
// Rotation updates cannot be performed while the previous rotation change
// animation is still in progress. Skip this update. We will try updating
@@ -4937,7 +4937,7 @@
return false;
}
- final DisplayContent displayContent = getDefaultDisplayContentLocked();
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
final WindowList windows = displayContent.getWindowList();
final int oldRotation = mRotation;
@@ -5017,7 +5017,7 @@
startFreezingDisplayLocked(inTransaction, anim[0], anim[1]);
// startFreezingDisplayLocked can reset the ScreenRotationAnimation.
screenRotationAnimation =
- mAnimator.getScreenRotationAnimationLocked(DEFAULT_DISPLAY);
+ mAnimator.getScreenRotationAnimationLocked(displayId);
} else {
// The screen rotation animation uses a screenshot to freeze the screen
// while windows resize underneath.
@@ -5035,7 +5035,7 @@
// the top of the method, the caller is obligated to call computeNewConfigurationLocked().
// By updating the Display info here it will be available to
// computeScreenConfigurationLocked later.
- updateDisplayAndOrientationLocked(mRoot.getConfiguration().uiMode);
+ updateDisplayAndOrientationLocked(displayContent.getConfiguration().uiMode, displayId);
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
if (!inTransaction) {
@@ -5568,13 +5568,14 @@
}
/**
- * Instruct the Activity Manager to fetch new configurations, update global configuration
- * and broadcast changes to config-changed listeners if appropriate.
+ * Instruct the Activity Manager to fetch and update the current display's configuration and
+ * broadcast them to config-changed listeners if appropriate.
* NOTE: Can't be called with the window manager lock held since it call into activity manager.
*/
- void sendNewConfiguration() {
+ void sendNewConfiguration(int displayId) {
try {
- final boolean configUpdated = mActivityManager.updateConfiguration(null);
+ final boolean configUpdated = mActivityManager.updateDisplayOverrideConfiguration(
+ null /* values */, displayId);
if (!configUpdated) {
// Something changed (E.g. device rotation), but no configuration update is needed.
// E.g. changing device rotation by 180 degrees. Go ahead and perform surface
@@ -5584,7 +5585,7 @@
if (mWaitingForConfig) {
mWaitingForConfig = false;
mLastFinishedFreezeSource = "config-unchanged";
- getDefaultDisplayContentLocked().setLayoutNeeded();
+ mRoot.getDisplayContent(displayId).setLayoutNeeded();
mWindowPlacerLocked.performSurfacePlacement();
}
}
@@ -5593,18 +5594,18 @@
}
}
- public Configuration computeNewConfiguration() {
+ public Configuration computeNewConfiguration(int displayId) {
synchronized (mWindowMap) {
- return computeNewConfigurationLocked();
+ return computeNewConfigurationLocked(displayId);
}
}
- private Configuration computeNewConfigurationLocked() {
+ private Configuration computeNewConfigurationLocked(int displayId) {
if (!mDisplayReady) {
return null;
}
- Configuration config = new Configuration();
- computeScreenConfigurationLocked(config);
+ final Configuration config = new Configuration();
+ computeScreenConfigurationLocked(config, displayId);
return config;
}
@@ -5713,9 +5714,8 @@
}
/** Do not call if mDisplayReady == false */
- DisplayInfo updateDisplayAndOrientationLocked(int uiMode) {
- // TODO(multidisplay): For now, apply Configuration to main screen only.
- final DisplayContent displayContent = getDefaultDisplayContentLocked();
+ private DisplayInfo updateDisplayAndOrientationLocked(int uiMode, int displayId) {
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
// Use the effective "visual" dimensions based on current rotation
final boolean rotated = (mRotation == Surface.ROTATION_90
@@ -5776,9 +5776,8 @@
}
/** Do not call if mDisplayReady == false */
- void computeScreenConfigurationLocked(Configuration config) {
- final DisplayInfo displayInfo = updateDisplayAndOrientationLocked(
- config.uiMode);
+ private void computeScreenConfigurationLocked(Configuration config, int displayId) {
+ final DisplayInfo displayInfo = updateDisplayAndOrientationLocked(config.uiMode, displayId);
final int dw = displayInfo.logicalWidth;
final int dh = displayInfo.logicalHeight;
@@ -6417,11 +6416,9 @@
View view = null;
try {
- final Configuration overrideConfig =
- wtoken != null ? wtoken.getMergedOverrideConfiguration() : null;
view = mPolicy.addStartingWindow(wtoken.token, sd.pkg, sd.theme,
sd.compatInfo, sd.nonLocalizedLabel, sd.labelRes, sd.icon, sd.logo,
- sd.windowFlags, overrideConfig);
+ sd.windowFlags, wtoken.getMergedOverrideConfiguration());
} catch (Exception e) {
Slog.w(TAG_WM, "Exception when adding starting window", e);
}
@@ -6668,8 +6665,9 @@
}
case SEND_NEW_CONFIGURATION: {
- removeMessages(SEND_NEW_CONFIGURATION);
- sendNewConfiguration();
+ removeMessages(SEND_NEW_CONFIGURATION, msg.obj);
+ final int displayId = (Integer) msg.obj;
+ sendNewConfiguration(displayId);
break;
}
@@ -7317,16 +7315,19 @@
configureDisplayPolicyLocked(displayContent);
displayContent.setLayoutNeeded();
- boolean configChanged = updateOrientationFromAppTokensLocked(false);
- final Configuration globalConfig = mRoot.getConfiguration();
- mTempConfiguration.setTo(globalConfig);
- computeScreenConfigurationLocked(mTempConfiguration);
- configChanged |= globalConfig.diff(mTempConfiguration) != 0;
+ final int displayId = displayContent.getDisplayId();
+ boolean configChanged = updateOrientationFromAppTokensLocked(false /* inTransaction */,
+ displayId);
+ final Configuration currentDisplayConfig = displayContent.getConfiguration();
+ mTempConfiguration.setTo(currentDisplayConfig);
+ computeScreenConfigurationLocked(mTempConfiguration, displayId);
+ configChanged |= currentDisplayConfig.diff(mTempConfiguration) != 0;
if (configChanged) {
mWaitingForConfig = true;
- startFreezingDisplayLocked(false, 0, 0);
- mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
+ startFreezingDisplayLocked(false /* inTransaction */, 0 /* exitAnim */,
+ 0 /* enterAnim */);
+ mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayId).sendToTarget();
}
mWindowPlacerLocked.performSurfacePlacement();
@@ -7744,7 +7745,7 @@
// to avoid inconsistent states. However, something interesting
// could have actually changed during that time so re-evaluate it
// now to catch that.
- configChanged = updateOrientationFromAppTokensLocked(false);
+ configChanged = updateOrientationFromAppTokensLocked(false, displayId);
// A little kludge: a lot could have happened while the
// display was frozen, so now that we are coming back we
@@ -7758,11 +7759,11 @@
if (updateRotation) {
if (DEBUG_ORIENTATION) Slog.d(TAG_WM, "Performing post-rotate rotation");
- configChanged |= updateRotationUncheckedLocked(false);
+ configChanged |= updateRotationUncheckedLocked(false, displayId);
}
if (configChanged) {
- mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
+ mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayId).sendToTarget();
}
}
@@ -8887,8 +8888,9 @@
if (DEBUG_ORIENTATION) {
Slog.i(TAG, "Performing post-rotate rotation after seamless rotation");
}
- if (updateRotationUncheckedLocked(false)) {
- mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
+ final int displayId = w.getDisplayId();
+ if (updateRotationUncheckedLocked(false, displayId)) {
+ mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayId).sendToTarget();
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f80e085..a7b46111 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -38,7 +38,6 @@
import android.util.DisplayMetrics;
import android.util.Slog;
import android.util.TimeUtils;
-import android.view.Display;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.IApplicationToken;
@@ -1670,7 +1669,7 @@
//TODO (multidisplay): Accessibility supported only for the default display.
if (mService.mAccessibilityController != null
- && getDisplayContent().getDisplayId() == Display.DEFAULT_DISPLAY) {
+ && getDisplayContent().getDisplayId() == DEFAULT_DISPLAY) {
mService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
}
@@ -1831,6 +1830,8 @@
// Visibility of the removed window. Will be used later to update orientation later on.
boolean wasVisible = false;
+ final int displayId = getDisplayId();
+
// First, see if we need to run an animation. If we do, we have to hold off on removing the
// window until the animation is done. If the display is frozen, just remove immediately,
// since the animation wouldn't be seen.
@@ -1891,8 +1892,7 @@
mAnimatingExit = true;
}
//TODO (multidisplay): Magnification is supported only for the default display.
- if (mService.mAccessibilityController != null
- && getDisplayId() == Display.DEFAULT_DISPLAY) {
+ if (mService.mAccessibilityController != null && displayId == DEFAULT_DISPLAY) {
mService.mAccessibilityController.onWindowTransitionLocked(this, transit);
}
}
@@ -1922,8 +1922,8 @@
removeImmediately();
// Removing a visible window will effect the computed orientation
// So just update orientation if needed.
- if (wasVisible && mService.updateOrientationFromAppTokensLocked(false)) {
- mService.mH.sendEmptyMessage(SEND_NEW_CONFIGURATION);
+ if (wasVisible && mService.updateOrientationFromAppTokensLocked(false, displayId)) {
+ mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
}
mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
Binder.restoreCallingIdentity(origId);
@@ -3037,8 +3037,7 @@
}
//TODO (multidisplay): Accessibility supported only for the default display.
- if (mService.mAccessibilityController != null
- && getDisplayId() == Display.DEFAULT_DISPLAY) {
+ if (mService.mAccessibilityController != null && getDisplayId() == DEFAULT_DISPLAY) {
mService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
}
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index 121067a..2c46413 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -71,3 +71,5 @@
android.hardware.vibrator@1.0 \
android.hardware.light@2.0 \
android.hardware.vr@1.0 \
+ android.hardware.audio.common@2.0 \
+ android.hardware.tv.input@1.0 \
diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp
index e34a8e8..179fba0 100644
--- a/services/core/jni/com_android_server_tv_TvInputHal.cpp
+++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp
@@ -24,6 +24,9 @@
#include "JNIHelp.h"
#include "jni.h"
+#include <android/hardware/tv/input/1.0/ITvInputCallback.h>
+#include <android/hardware/tv/input/1.0/ITvInput.h>
+#include <android/hardware/tv/input/1.0/types.h>
#include <gui/Surface.h>
#include <utils/Errors.h>
#include <utils/KeyedVector.h>
@@ -32,6 +35,20 @@
#include <utils/NativeHandle.h>
#include <hardware/tv_input.h>
+using ::android::hardware::audio::common::V2_0::AudioDevice;
+using ::android::hardware::tv::input::V1_0::ITvInput;
+using ::android::hardware::tv::input::V1_0::ITvInputCallback;
+using ::android::hardware::tv::input::V1_0::Result;
+using ::android::hardware::tv::input::V1_0::TvInputDeviceInfo;
+using ::android::hardware::tv::input::V1_0::TvInputEvent;
+using ::android::hardware::tv::input::V1_0::TvInputEventType;
+using ::android::hardware::tv::input::V1_0::TvInputType;
+using ::android::hardware::tv::input::V1_0::TvStreamConfig;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::hidl_string;
+
namespace android {
static struct {
@@ -239,9 +256,9 @@
int addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface);
int removeStream(int deviceId, int streamId);
- const tv_stream_config_t* getStreamConfigs(int deviceId, int* numConfigs);
+ const hidl_vec<TvStreamConfig> getStreamConfigs(int deviceId);
- void onDeviceAvailable(const tv_input_device_info_t& info);
+ void onDeviceAvailable(const TvInputDeviceInfo& info);
void onDeviceUnavailable(int deviceId);
void onStreamConfigurationsChanged(int deviceId);
void onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded);
@@ -263,73 +280,60 @@
class NotifyHandler : public MessageHandler {
public:
- NotifyHandler(JTvInputHal* hal, const tv_input_event_t* event);
- ~NotifyHandler();
+ NotifyHandler(JTvInputHal* hal, const TvInputEvent& event);
virtual void handleMessage(const Message& message);
private:
- tv_input_event_t mEvent;
+ TvInputEvent mEvent;
JTvInputHal* mHal;
};
- JTvInputHal(JNIEnv* env, jobject thiz, tv_input_device_t* dev, const sp<Looper>& looper);
+ class TvInputCallback : public ITvInputCallback {
+ public:
+ TvInputCallback(JTvInputHal* hal);
+ Return<void> notify(const TvInputEvent& event) override;
+ private:
+ JTvInputHal* mHal;
+ };
- static void notify(
- tv_input_device_t* dev, tv_input_event_t* event, void* data);
-
- static void cloneTvInputEvent(
- tv_input_event_t* dstEvent, const tv_input_event_t* srcEvent);
+ JTvInputHal(JNIEnv* env, jobject thiz, sp<ITvInput> tvInput, const sp<Looper>& looper);
Mutex mLock;
jweak mThiz;
- tv_input_device_t* mDevice;
- tv_input_callback_ops_t mCallback;
sp<Looper> mLooper;
KeyedVector<int, KeyedVector<int, Connection> > mConnections;
+
+ sp<ITvInput> mTvInput;
+ sp<ITvInputCallback> mTvInputCallback;
};
-JTvInputHal::JTvInputHal(JNIEnv* env, jobject thiz, tv_input_device_t* device,
+JTvInputHal::JTvInputHal(JNIEnv* env, jobject thiz, sp<ITvInput> tvInput,
const sp<Looper>& looper) {
mThiz = env->NewWeakGlobalRef(thiz);
- mDevice = device;
- mCallback.notify = &JTvInputHal::notify;
+ mTvInput = tvInput;
mLooper = looper;
-
- mDevice->initialize(mDevice, &mCallback, this);
+ mTvInputCallback = new TvInputCallback(this);
+ mTvInput->setCallback(mTvInputCallback);
}
JTvInputHal::~JTvInputHal() {
- mDevice->common.close((hw_device_t*)mDevice);
-
+ mTvInput->setCallback(nullptr);
JNIEnv* env = AndroidRuntime::getJNIEnv();
env->DeleteWeakGlobalRef(mThiz);
mThiz = NULL;
}
JTvInputHal* JTvInputHal::createInstance(JNIEnv* env, jobject thiz, const sp<Looper>& looper) {
- tv_input_module_t* module = NULL;
- status_t err = hw_get_module(TV_INPUT_HARDWARE_MODULE_ID,
- (hw_module_t const**)&module);
- if (err) {
- ALOGE("Couldn't load %s module (%s)",
- TV_INPUT_HARDWARE_MODULE_ID, strerror(-err));
- return 0;
+ // TODO(b/31632518)
+ sp<ITvInput> tvInput = ITvInput::getService("tv.input");
+ if (tvInput == nullptr) {
+ ALOGE("Couldn't get tv.input service.");
+ return nullptr;
}
- tv_input_device_t* device = NULL;
- err = module->common.methods->open(
- (hw_module_t*)module,
- TV_INPUT_DEFAULT_DEVICE,
- (hw_device_t**)&device);
- if (err) {
- ALOGE("Couldn't open %s device (%s)",
- TV_INPUT_DEFAULT_DEVICE, strerror(-err));
- return 0;
- }
-
- return new JTvInputHal(env, thiz, device, looper);
+ return new JTvInputHal(env, thiz, tvInput, looper);
}
int JTvInputHal::addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface) {
@@ -353,16 +357,22 @@
}
if (connection.mSourceHandle == NULL && connection.mThread == NULL) {
// Need to configure stream
- int numConfigs = 0;
- const tv_stream_config_t* configs = NULL;
- if (mDevice->get_stream_configurations(
- mDevice, deviceId, &numConfigs, &configs) != 0) {
- ALOGE("Couldn't get stream configs");
+ Result result = Result::UNKNOWN;
+ hidl_vec<TvStreamConfig> list;
+ mTvInput->getStreamConfigurations(deviceId,
+ [&result, &list](Result res, hidl_vec<TvStreamConfig> configs) {
+ result = res;
+ if (res == Result::OK) {
+ list = configs;
+ }
+ });
+ if (result != Result::OK) {
+ ALOGE("Couldn't get stream configs for device id:%d result:%d", deviceId, result);
return UNKNOWN_ERROR;
}
int configIndex = -1;
- for (int i = 0; i < numConfigs; ++i) {
- if (configs[i].stream_id == streamId) {
+ for (size_t i = 0; i < list.size(); ++i) {
+ if (list[i].streamId == streamId) {
configIndex = i;
break;
}
@@ -371,34 +381,27 @@
ALOGE("Cannot find a config with given stream ID: %d", streamId);
return BAD_VALUE;
}
- connection.mStreamType = configs[configIndex].type;
+ connection.mStreamType = TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE;
- tv_stream_t stream;
- stream.stream_id = configs[configIndex].stream_id;
- if (connection.mStreamType == TV_STREAM_TYPE_BUFFER_PRODUCER) {
- stream.buffer_producer.width = configs[configIndex].max_video_width;
- stream.buffer_producer.height = configs[configIndex].max_video_height;
- }
- if (mDevice->open_stream(mDevice, deviceId, &stream) != 0) {
- ALOGE("Couldn't add stream");
+ result = Result::UNKNOWN;
+ const native_handle_t* sidebandStream;
+ mTvInput->openStream(deviceId, streamId,
+ [&result, &sidebandStream](Result res, const native_handle_t* handle) {
+ result = res;
+ if (res == Result::OK) {
+ sidebandStream = handle;
+ }
+ });
+ if (result != Result::OK) {
+ ALOGE("Couldn't open stream. device id:%d stream id:%d result:%d", deviceId, streamId,
+ result);
return UNKNOWN_ERROR;
}
- if (connection.mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE) {
- connection.mSourceHandle = NativeHandle::create(
- stream.sideband_stream_source_handle, false);
- } else if (connection.mStreamType == TV_STREAM_TYPE_BUFFER_PRODUCER) {
- if (connection.mThread != NULL) {
- connection.mThread->shutdown();
- }
- connection.mThread = new BufferProducerThread(mDevice, deviceId, &stream);
- connection.mThread->run("BufferProducerThread");
- }
+ connection.mSourceHandle = NativeHandle::create((native_handle_t*)sidebandStream, false);
}
connection.mSurface = surface;
- if (connection.mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE) {
+ if (connection.mSurface != nullptr) {
connection.mSurface->setSidebandStream(connection.mSourceHandle);
- } else if (connection.mStreamType == TV_STREAM_TYPE_BUFFER_PRODUCER) {
- connection.mThread->setSurface(surface);
}
return NO_ERROR;
}
@@ -421,8 +424,8 @@
connection.mThread->shutdown();
connection.mThread.clear();
}
- if (mDevice->close_stream(mDevice, deviceId, streamId) != 0) {
- ALOGE("Couldn't remove stream");
+ if (mTvInput->closeStream(deviceId, streamId) != Result::OK) {
+ ALOGE("Couldn't close stream. device id:%d stream id:%d", deviceId, streamId);
return BAD_VALUE;
}
if (connection.mSourceHandle != NULL) {
@@ -431,41 +434,26 @@
return NO_ERROR;
}
-const tv_stream_config_t* JTvInputHal::getStreamConfigs(int deviceId, int* numConfigs) {
- const tv_stream_config_t* configs = NULL;
- if (mDevice->get_stream_configurations(
- mDevice, deviceId, numConfigs, &configs) != 0) {
- ALOGE("Couldn't get stream configs");
- return NULL;
+const hidl_vec<TvStreamConfig> JTvInputHal::getStreamConfigs(int deviceId) {
+ Result result = Result::UNKNOWN;
+ hidl_vec<TvStreamConfig> list;
+ mTvInput->getStreamConfigurations(deviceId,
+ [&result, &list](Result res, hidl_vec<TvStreamConfig> configs) {
+ result = res;
+ if (res == Result::OK) {
+ list = configs;
+ }
+ });
+ if (result != Result::OK) {
+ ALOGE("Couldn't get stream configs for device id:%d result:%d", deviceId, result);
}
- return configs;
+ return list;
}
-// static
-void JTvInputHal::notify(
- tv_input_device_t* dev, tv_input_event_t* event, void* data) {
- JTvInputHal* thiz = (JTvInputHal*)data;
- thiz->mLooper->sendMessage(new NotifyHandler(thiz, event), event->type);
-}
-
-// static
-void JTvInputHal::cloneTvInputEvent(
- tv_input_event_t* dstEvent, const tv_input_event_t* srcEvent) {
- memcpy(dstEvent, srcEvent, sizeof(tv_input_event_t));
- if ((srcEvent->type == TV_INPUT_EVENT_DEVICE_AVAILABLE ||
- srcEvent->type == TV_INPUT_EVENT_DEVICE_UNAVAILABLE ||
- srcEvent->type == TV_INPUT_EVENT_STREAM_CONFIGURATIONS_CHANGED) &&
- srcEvent->device_info.audio_address != NULL){
- char* audio_address = new char[strlen(srcEvent->device_info.audio_address) + 1];
- strcpy(audio_address, srcEvent->device_info.audio_address);
- dstEvent->device_info.audio_address = audio_address;
- }
-}
-
-void JTvInputHal::onDeviceAvailable(const tv_input_device_info_t& info) {
+void JTvInputHal::onDeviceAvailable(const TvInputDeviceInfo& info) {
{
Mutex::Autolock autoLock(&mLock);
- mConnections.add(info.device_id, KeyedVector<int, Connection>());
+ mConnections.add(info.deviceId, KeyedVector<int, Connection>());
}
JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -473,17 +461,20 @@
gTvInputHardwareInfoBuilderClassInfo.clazz,
gTvInputHardwareInfoBuilderClassInfo.constructor);
env->CallObjectMethod(
- builder, gTvInputHardwareInfoBuilderClassInfo.deviceId, info.device_id);
+ builder, gTvInputHardwareInfoBuilderClassInfo.deviceId, info.deviceId);
env->CallObjectMethod(
builder, gTvInputHardwareInfoBuilderClassInfo.type, info.type);
- if (info.type == TV_INPUT_TYPE_HDMI) {
+ if (info.type == TvInputType::HDMI) {
env->CallObjectMethod(
- builder, gTvInputHardwareInfoBuilderClassInfo.hdmiPortId, info.hdmi.port_id);
+ builder, gTvInputHardwareInfoBuilderClassInfo.hdmiPortId, info.portId);
}
env->CallObjectMethod(
- builder, gTvInputHardwareInfoBuilderClassInfo.audioType, info.audio_type);
- if (info.audio_type != AUDIO_DEVICE_NONE) {
- jstring audioAddress = env->NewStringUTF(info.audio_address);
+ builder, gTvInputHardwareInfoBuilderClassInfo.audioType, info.audioType);
+ if (info.audioType != AudioDevice::NONE) {
+ uint8_t buffer[info.audioAddress.size() + 1];
+ memcpy(buffer, info.audioAddress.data(), info.audioAddress.size());
+ buffer[info.audioAddress.size()] = '\0';
+ jstring audioAddress = env->NewStringUTF(reinterpret_cast<const char *>(buffer));
env->CallObjectMethod(
builder, gTvInputHardwareInfoBuilderClassInfo.audioAddress, audioAddress);
env->DeleteLocalRef(audioAddress);
@@ -556,48 +547,37 @@
}
}
-JTvInputHal::NotifyHandler::NotifyHandler(JTvInputHal* hal, const tv_input_event_t* event) {
+JTvInputHal::NotifyHandler::NotifyHandler(JTvInputHal* hal, const TvInputEvent& event) {
mHal = hal;
- cloneTvInputEvent(&mEvent, event);
-}
-
-JTvInputHal::NotifyHandler::~NotifyHandler() {
- if ((mEvent.type == TV_INPUT_EVENT_DEVICE_AVAILABLE ||
- mEvent.type == TV_INPUT_EVENT_DEVICE_UNAVAILABLE ||
- mEvent.type == TV_INPUT_EVENT_STREAM_CONFIGURATIONS_CHANGED) &&
- mEvent.device_info.audio_address != NULL) {
- delete mEvent.device_info.audio_address;
- }
+ mEvent = event;
}
void JTvInputHal::NotifyHandler::handleMessage(const Message& message) {
switch (mEvent.type) {
- case TV_INPUT_EVENT_DEVICE_AVAILABLE: {
- mHal->onDeviceAvailable(mEvent.device_info);
+ case TvInputEventType::DEVICE_AVAILABLE: {
+ mHal->onDeviceAvailable(mEvent.deviceInfo);
} break;
- case TV_INPUT_EVENT_DEVICE_UNAVAILABLE: {
- mHal->onDeviceUnavailable(mEvent.device_info.device_id);
+ case TvInputEventType::DEVICE_UNAVAILABLE: {
+ mHal->onDeviceUnavailable(mEvent.deviceInfo.deviceId);
} break;
- case TV_INPUT_EVENT_STREAM_CONFIGURATIONS_CHANGED: {
- mHal->onStreamConfigurationsChanged(mEvent.device_info.device_id);
- } break;
- case TV_INPUT_EVENT_CAPTURE_SUCCEEDED: {
- mHal->onCaptured(mEvent.capture_result.device_id,
- mEvent.capture_result.stream_id,
- mEvent.capture_result.seq,
- true /* succeeded */);
- } break;
- case TV_INPUT_EVENT_CAPTURE_FAILED: {
- mHal->onCaptured(mEvent.capture_result.device_id,
- mEvent.capture_result.stream_id,
- mEvent.capture_result.seq,
- false /* succeeded */);
+ case TvInputEventType::STREAM_CONFIGURATIONS_CHANGED: {
+ mHal->onStreamConfigurationsChanged(mEvent.deviceInfo.deviceId);
} break;
default:
ALOGE("Unrecognizable event");
}
}
+JTvInputHal::TvInputCallback::TvInputCallback(JTvInputHal* hal) {
+ mHal = hal;
+}
+
+Return<void> JTvInputHal::TvInputCallback::notify(const TvInputEvent& event) {
+ // TODO(b/32200867): Ensure the event type values are in sync with the framework code.
+ mHal->mLooper->sendMessage(new NotifyHandler(mHal, event), static_cast<int>(event.type));
+ return Void();
+}
+
////////////////////////////////////////////////////////////////////////////////
static jlong nativeOpen(JNIEnv* env, jobject thiz, jobject messageQueueObj) {
@@ -628,22 +608,22 @@
static jobjectArray nativeGetStreamConfigs(JNIEnv* env, jclass clazz,
jlong ptr, jint deviceId, jint generation) {
JTvInputHal* tvInputHal = (JTvInputHal*)ptr;
- int numConfigs = 0;
- const tv_stream_config_t* configs = tvInputHal->getStreamConfigs(deviceId, &numConfigs);
+ const hidl_vec<TvStreamConfig> configs = tvInputHal->getStreamConfigs(deviceId);
- jobjectArray result = env->NewObjectArray(numConfigs, gTvStreamConfigClassInfo.clazz, NULL);
- for (int i = 0; i < numConfigs; ++i) {
+ jobjectArray result = env->NewObjectArray(configs.size(), gTvStreamConfigClassInfo.clazz, NULL);
+ for (size_t i = 0; i < configs.size(); ++i) {
jobject builder = env->NewObject(
gTvStreamConfigBuilderClassInfo.clazz,
gTvStreamConfigBuilderClassInfo.constructor);
env->CallObjectMethod(
- builder, gTvStreamConfigBuilderClassInfo.streamId, configs[i].stream_id);
+ builder, gTvStreamConfigBuilderClassInfo.streamId, configs[i].streamId);
env->CallObjectMethod(
- builder, gTvStreamConfigBuilderClassInfo.type, configs[i].type);
+ builder, gTvStreamConfigBuilderClassInfo.type,
+ TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE);
env->CallObjectMethod(
- builder, gTvStreamConfigBuilderClassInfo.maxWidth, configs[i].max_video_width);
+ builder, gTvStreamConfigBuilderClassInfo.maxWidth, configs[i].maxVideoWidth);
env->CallObjectMethod(
- builder, gTvStreamConfigBuilderClassInfo.maxHeight, configs[i].max_video_height);
+ builder, gTvStreamConfigBuilderClassInfo.maxHeight, configs[i].maxVideoHeight);
env->CallObjectMethod(
builder, gTvStreamConfigBuilderClassInfo.generation, generation);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 85b0d96..62947eb 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -118,6 +118,8 @@
import java.util.Timer;
import java.util.TimerTask;
+import static android.view.Display.DEFAULT_DISPLAY;
+
public final class SystemServer {
private static final String TAG = "SystemServer";
@@ -1403,7 +1405,7 @@
// Update the configuration for this context by hand, because we're going
// to start using it before the config change done in wm.systemReady() will
// propagate to it.
- Configuration config = wm.computeNewConfiguration();
+ final Configuration config = wm.computeNewConfiguration(DEFAULT_DISPLAY);
DisplayMetrics metrics = new DisplayMetrics();
WindowManager w = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
w.getDefaultDisplay().getMetrics(metrics);
diff --git a/services/tests/runtests.py b/services/tests/runtests.py
new file mode 100755
index 0000000..35fec90f
--- /dev/null
+++ b/services/tests/runtests.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2016 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.
+
+import os
+import subprocess
+import sys
+
+INSTRUMENTED_PACKAGE_RUNNER = ('com.android.frameworks.servicestests/'
+ 'android.support.test.runner.AndroidJUnitRunner')
+
+PACKAGE_WHITELIST = (
+ 'android.net',
+ 'com.android.server.connectivity',
+)
+
+COLOR_RED = '\033[0;31m'
+COLOR_NONE ='\033[0m'
+
+def run(shell_command, echo=True):
+ if echo:
+ print '%s + %s%s' % (
+ COLOR_RED,
+ echo if isinstance(echo, str) else shell_command,
+ COLOR_NONE)
+ return subprocess.check_call(shell_command, shell=True)
+
+
+def main():
+ build_top = os.environ.get('ANDROID_BUILD_TOP', None)
+ out_dir = os.environ.get('OUT', None)
+ if build_top is None or out_dir is None:
+ print 'You need to source and lunch before you can use this script'
+ return 1
+
+ print 'Building tests...'
+ run('make -j32 -C %s -f build/core/main.mk '
+ 'MODULES-IN-frameworks-base-services-tests-servicestests' % build_top,
+ echo='mmma -j32 %s/frameworks/base/services/tests/servicestests' %
+ build_top)
+
+ print 'Installing tests...'
+ run('adb root')
+ run('adb wait-for-device')
+ apk_path = (
+ '%s/data/app/FrameworksServicesTests/FrameworksServicesTests.apk' %
+ out_dir)
+ run('adb install -r -g "%s"' % apk_path)
+
+ print 'Running tests...'
+ if len(sys.argv) != 1:
+ run('adb shell am instrument -w "%s" %s' %
+ (INSTRUMENTED_PACKAGE_RUNNER, ' '.join(sys.argv[1:])))
+ return 0
+
+ # It would be nice if the activity manager accepted a list of packages, but
+ # in lieu of that...
+ for package in PACKAGE_WHITELIST:
+ run('adb shell am instrument -w -e package %s %s' %
+ (package, INSTRUMENTED_PACKAGE_RUNNER))
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
index 8424344..f737b24 100644
--- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
+++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
@@ -25,6 +25,7 @@
import junit.framework.TestCase;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.view.Display.DEFAULT_DISPLAY;
/**
* TODO: Remove this. This is only a placeholder, need to implement this.
@@ -113,7 +114,8 @@
}
try {
- mWm.updateOrientationFromAppTokens(new Configuration(), null);
+ mWm.updateOrientationFromAppTokens(new Configuration(),
+ null /* freezeThisOneIfNeeded */, DEFAULT_DISPLAY);
fail("IWindowManager.updateOrientationFromAppTokens did not throw SecurityException as"
+ " expected");
} catch (SecurityException e) {
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 09ab657..6b4db42 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -413,7 +413,8 @@
}
@Override
- public int[] setNewConfiguration(Configuration arg0) throws RemoteException {
+ public int[] setNewDisplayOverrideConfiguration(Configuration arg0, int displayId)
+ throws RemoteException {
// TODO Auto-generated method stub
return null;
}
@@ -487,7 +488,7 @@
}
@Override
- public Configuration updateOrientationFromAppTokens(Configuration arg0, IBinder arg1)
+ public Configuration updateOrientationFromAppTokens(Configuration arg0, IBinder arg1, int arg2)
throws RemoteException {
// TODO Auto-generated method stub
return null;