diff options
Diffstat (limited to 'core/java/android')
29 files changed, 1437 insertions, 574 deletions
diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java index 0cccedc..c7030b0 100644 --- a/core/java/android/app/ActivityTransitionCoordinator.java +++ b/core/java/android/app/ActivityTransitionCoordinator.java @@ -206,6 +206,9 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { protected ResultReceiver mResultReceiver; final private FixedEpicenterCallback mEpicenterCallback = new FixedEpicenterCallback(); final protected boolean mIsReturning; + private Runnable mPendingTransition; + private boolean mIsStartingTransition; + public ActivityTransitionCoordinator(Window window, ArrayList<String> allSharedElementNames, @@ -290,13 +293,17 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { if (transition == null || views == null || views.isEmpty()) { return null; } + // Add the targets to a set containing transition so that transition + // remains unaffected. We don't want to modify the targets of transition itself. TransitionSet set = new TransitionSet(); - set.addTransition(transition); if (views != null) { - for (View view: views) { + for (View view : views) { set.addTarget(view); } } + // By adding the transition after addTarget, we prevent addTarget from + // affecting transition. + set.addTransition(transition); return set; } @@ -523,7 +530,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { * @param transitionArgs Bundle to store shared element placement information. * @param tempBounds A temporary Rect for capturing the current location of views. */ - private static void captureSharedElementState(View view, String name, Bundle transitionArgs, + protected static void captureSharedElementState(View view, String name, Bundle transitionArgs, Rect tempBounds) { Bundle sharedElementBundle = new Bundle(); tempBounds.set(0, 0, view.getWidth(), view.getHeight()); @@ -559,6 +566,32 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { transitionArgs.putBundle(name, sharedElementBundle); } + + protected void startTransition(Runnable runnable) { + if (mIsStartingTransition) { + mPendingTransition = runnable; + } else { + mIsStartingTransition = true; + runnable.run(); + } + } + + protected void transitionStarted() { + mIsStartingTransition = false; + } + + protected class ContinueTransitionListener extends Transition.TransitionListenerAdapter { + @Override + public void onTransitionStart(Transition transition) { + mIsStartingTransition = false; + Runnable pending = mPendingTransition; + mPendingTransition = null; + if (pending != null) { + startTransition(pending); + } + } + } + private static int scaleTypeToInt(ImageView.ScaleType scaleType) { for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) { if (scaleType == SCALE_TYPE_VALUES[i]) { diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java index 89ee145..01a388f 100644 --- a/core/java/android/app/BackStackRecord.java +++ b/core/java/android/app/BackStackRecord.java @@ -16,36 +16,53 @@ package android.app; +import com.android.internal.util.FastPrintWriter; + +import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import android.transition.Transition; +import android.transition.TransitionInflater; +import android.transition.TransitionManager; +import android.transition.TransitionSet; +import android.util.ArrayMap; import android.util.Log; import android.util.LogWriter; -import com.android.internal.util.FastPrintWriter; +import android.util.Pair; +import android.view.View; +import android.view.ViewGroup; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collection; final class BackStackState implements Parcelable { final int[] mOps; final int mTransition; final int mTransitionStyle; + final int mCustomTransition; + final int mSceneRoot; final String mName; final int mIndex; final int mBreadCrumbTitleRes; final CharSequence mBreadCrumbTitleText; final int mBreadCrumbShortTitleRes; final CharSequence mBreadCrumbShortTitleText; + final ArrayList<String> mSharedElementSourceNames; + final ArrayList<String> mSharedElementTargetNames; public BackStackState(FragmentManagerImpl fm, BackStackRecord bse) { int numRemoved = 0; BackStackRecord.Op op = bse.mHead; while (op != null) { - if (op.removed != null) numRemoved += op.removed.size(); + if (op.removed != null) { + numRemoved += op.removed.size(); + } op = op.next; } - mOps = new int[bse.mNumOp*7 + numRemoved]; + mOps = new int[bse.mNumOp * 7 + numRemoved]; if (!bse.mAddToBackStack) { throw new IllegalStateException("Not on back stack"); @@ -63,7 +80,7 @@ final class BackStackState implements Parcelable { if (op.removed != null) { final int N = op.removed.size(); mOps[pos++] = N; - for (int i=0; i<N; i++) { + for (int i = 0; i < N; i++) { mOps[pos++] = op.removed.get(i).mIndex; } } else { @@ -79,6 +96,10 @@ final class BackStackState implements Parcelable { mBreadCrumbTitleText = bse.mBreadCrumbTitleText; mBreadCrumbShortTitleRes = bse.mBreadCrumbShortTitleRes; mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText; + mCustomTransition = bse.mCustomTransition; + mSceneRoot = bse.mSceneRoot; + mSharedElementSourceNames = bse.mSharedElementSourceNames; + mSharedElementTargetNames = bse.mSharedElementTargetNames; } public BackStackState(Parcel in) { @@ -91,6 +112,10 @@ final class BackStackState implements Parcelable { mBreadCrumbTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); mBreadCrumbShortTitleRes = in.readInt(); mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mCustomTransition = in.readInt(); + mSceneRoot = in.readInt(); + mSharedElementSourceNames = in.createStringArrayList(); + mSharedElementTargetNames = in.createStringArrayList(); } public BackStackRecord instantiate(FragmentManagerImpl fm) { @@ -100,8 +125,10 @@ final class BackStackState implements Parcelable { while (pos < mOps.length) { BackStackRecord.Op op = new BackStackRecord.Op(); op.cmd = mOps[pos++]; - if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG, - "Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]); + if (FragmentManagerImpl.DEBUG) { + Log.v(FragmentManagerImpl.TAG, + "Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]); + } int findex = mOps[pos++]; if (findex >= 0) { Fragment f = fm.mActive.get(findex); @@ -116,9 +143,11 @@ final class BackStackState implements Parcelable { final int N = mOps[pos++]; if (N > 0) { op.removed = new ArrayList<Fragment>(N); - for (int i=0; i<N; i++) { - if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG, - "Instantiate " + bse + " set remove fragment #" + mOps[pos]); + for (int i = 0; i < N; i++) { + if (FragmentManagerImpl.DEBUG) { + Log.v(FragmentManagerImpl.TAG, + "Instantiate " + bse + " set remove fragment #" + mOps[pos]); + } Fragment r = fm.mActive.get(mOps[pos++]); op.removed.add(r); } @@ -135,6 +164,10 @@ final class BackStackState implements Parcelable { bse.mBreadCrumbTitleText = mBreadCrumbTitleText; bse.mBreadCrumbShortTitleRes = mBreadCrumbShortTitleRes; bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText; + bse.mCustomTransition = mCustomTransition; + bse.mSceneRoot = mSceneRoot; + bse.mSharedElementSourceNames = mSharedElementSourceNames; + bse.mSharedElementTargetNames = mSharedElementTargetNames; bse.bumpBackStackNesting(1); return bse; } @@ -153,6 +186,10 @@ final class BackStackState implements Parcelable { TextUtils.writeToParcel(mBreadCrumbTitleText, dest, 0); dest.writeInt(mBreadCrumbShortTitleRes); TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0); + dest.writeInt(mCustomTransition); + dest.writeInt(mSceneRoot); + dest.writeStringList(mSharedElementSourceNames); + dest.writeStringList(mSharedElementTargetNames); } public static final Parcelable.Creator<BackStackState> CREATOR @@ -217,6 +254,11 @@ final class BackStackRecord extends FragmentTransaction implements int mBreadCrumbShortTitleRes; CharSequence mBreadCrumbShortTitleText; + int mCustomTransition; + int mSceneRoot; + ArrayList<String> mSharedElementSourceNames; + ArrayList<String> mSharedElementTargetNames; + @Override public String toString() { StringBuilder sb = new StringBuilder(128); @@ -240,78 +282,112 @@ final class BackStackRecord extends FragmentTransaction implements void dump(String prefix, PrintWriter writer, boolean full) { if (full) { - writer.print(prefix); writer.print("mName="); writer.print(mName); - writer.print(" mIndex="); writer.print(mIndex); - writer.print(" mCommitted="); writer.println(mCommitted); + writer.print(prefix); + writer.print("mName="); + writer.print(mName); + writer.print(" mIndex="); + writer.print(mIndex); + writer.print(" mCommitted="); + writer.println(mCommitted); if (mTransition != FragmentTransaction.TRANSIT_NONE) { - writer.print(prefix); writer.print("mTransition=#"); - writer.print(Integer.toHexString(mTransition)); - writer.print(" mTransitionStyle=#"); - writer.println(Integer.toHexString(mTransitionStyle)); + writer.print(prefix); + writer.print("mTransition=#"); + writer.print(Integer.toHexString(mTransition)); + writer.print(" mTransitionStyle=#"); + writer.println(Integer.toHexString(mTransitionStyle)); } - if (mEnterAnim != 0 || mExitAnim !=0) { - writer.print(prefix); writer.print("mEnterAnim=#"); - writer.print(Integer.toHexString(mEnterAnim)); - writer.print(" mExitAnim=#"); - writer.println(Integer.toHexString(mExitAnim)); + if (mEnterAnim != 0 || mExitAnim != 0) { + writer.print(prefix); + writer.print("mEnterAnim=#"); + writer.print(Integer.toHexString(mEnterAnim)); + writer.print(" mExitAnim=#"); + writer.println(Integer.toHexString(mExitAnim)); } - if (mPopEnterAnim != 0 || mPopExitAnim !=0) { - writer.print(prefix); writer.print("mPopEnterAnim=#"); - writer.print(Integer.toHexString(mPopEnterAnim)); - writer.print(" mPopExitAnim=#"); - writer.println(Integer.toHexString(mPopExitAnim)); + if (mPopEnterAnim != 0 || mPopExitAnim != 0) { + writer.print(prefix); + writer.print("mPopEnterAnim=#"); + writer.print(Integer.toHexString(mPopEnterAnim)); + writer.print(" mPopExitAnim=#"); + writer.println(Integer.toHexString(mPopExitAnim)); } if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) { - writer.print(prefix); writer.print("mBreadCrumbTitleRes=#"); - writer.print(Integer.toHexString(mBreadCrumbTitleRes)); - writer.print(" mBreadCrumbTitleText="); - writer.println(mBreadCrumbTitleText); + writer.print(prefix); + writer.print("mBreadCrumbTitleRes=#"); + writer.print(Integer.toHexString(mBreadCrumbTitleRes)); + writer.print(" mBreadCrumbTitleText="); + writer.println(mBreadCrumbTitleText); } if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) { - writer.print(prefix); writer.print("mBreadCrumbShortTitleRes=#"); - writer.print(Integer.toHexString(mBreadCrumbShortTitleRes)); - writer.print(" mBreadCrumbShortTitleText="); - writer.println(mBreadCrumbShortTitleText); + writer.print(prefix); + writer.print("mBreadCrumbShortTitleRes=#"); + writer.print(Integer.toHexString(mBreadCrumbShortTitleRes)); + writer.print(" mBreadCrumbShortTitleText="); + writer.println(mBreadCrumbShortTitleText); } } if (mHead != null) { - writer.print(prefix); writer.println("Operations:"); + writer.print(prefix); + writer.println("Operations:"); String innerPrefix = prefix + " "; Op op = mHead; int num = 0; while (op != null) { String cmdStr; switch (op.cmd) { - case OP_NULL: cmdStr="NULL"; break; - case OP_ADD: cmdStr="ADD"; break; - case OP_REPLACE: cmdStr="REPLACE"; break; - case OP_REMOVE: cmdStr="REMOVE"; break; - case OP_HIDE: cmdStr="HIDE"; break; - case OP_SHOW: cmdStr="SHOW"; break; - case OP_DETACH: cmdStr="DETACH"; break; - case OP_ATTACH: cmdStr="ATTACH"; break; - default: cmdStr="cmd=" + op.cmd; break; + case OP_NULL: + cmdStr = "NULL"; + break; + case OP_ADD: + cmdStr = "ADD"; + break; + case OP_REPLACE: + cmdStr = "REPLACE"; + break; + case OP_REMOVE: + cmdStr = "REMOVE"; + break; + case OP_HIDE: + cmdStr = "HIDE"; + break; + case OP_SHOW: + cmdStr = "SHOW"; + break; + case OP_DETACH: + cmdStr = "DETACH"; + break; + case OP_ATTACH: + cmdStr = "ATTACH"; + break; + default: + cmdStr = "cmd=" + op.cmd; + break; } - writer.print(prefix); writer.print(" Op #"); writer.print(num); - writer.print(": "); writer.print(cmdStr); - writer.print(" "); writer.println(op.fragment); + writer.print(prefix); + writer.print(" Op #"); + writer.print(num); + writer.print(": "); + writer.print(cmdStr); + writer.print(" "); + writer.println(op.fragment); if (full) { if (op.enterAnim != 0 || op.exitAnim != 0) { - writer.print(innerPrefix); writer.print("enterAnim=#"); - writer.print(Integer.toHexString(op.enterAnim)); - writer.print(" exitAnim=#"); - writer.println(Integer.toHexString(op.exitAnim)); + writer.print(innerPrefix); + writer.print("enterAnim=#"); + writer.print(Integer.toHexString(op.enterAnim)); + writer.print(" exitAnim=#"); + writer.println(Integer.toHexString(op.exitAnim)); } if (op.popEnterAnim != 0 || op.popExitAnim != 0) { - writer.print(innerPrefix); writer.print("popEnterAnim=#"); - writer.print(Integer.toHexString(op.popEnterAnim)); - writer.print(" popExitAnim=#"); - writer.println(Integer.toHexString(op.popExitAnim)); + writer.print(innerPrefix); + writer.print("popEnterAnim=#"); + writer.print(Integer.toHexString(op.popEnterAnim)); + writer.print(" popExitAnim=#"); + writer.println(Integer.toHexString(op.popExitAnim)); } } if (op.removed != null && op.removed.size() > 0) { - for (int i=0; i<op.removed.size(); i++) { + for (int i = 0; i < op.removed.size(); i++) { writer.print(innerPrefix); if (op.removed.size() == 1) { writer.print("Removed: "); @@ -319,8 +395,10 @@ final class BackStackRecord extends FragmentTransaction implements if (i == 0) { writer.println("Removed:"); } - writer.print(innerPrefix); writer.print(" #"); writer.print(i); - writer.print(": "); + writer.print(innerPrefix); + writer.print(" #"); + writer.print(i); + writer.print(": "); } writer.println(op.removed.get(i)); } @@ -494,6 +572,51 @@ final class BackStackRecord extends FragmentTransaction implements return this; } + @Override + public FragmentTransaction setCustomTransition(int sceneRootId, int transitionId) { + mSceneRoot = sceneRootId; + mCustomTransition = transitionId; + return this; + } + + @Override + public FragmentTransaction setSharedElement(View sharedElement, String name) { + String viewName = sharedElement.getViewName(); + if (viewName == null) { + throw new IllegalArgumentException("Unique viewNames are required for all" + + " sharedElements"); + } + mSharedElementSourceNames = new ArrayList<String>(1); + mSharedElementSourceNames.add(viewName); + + mSharedElementTargetNames = new ArrayList<String>(1); + mSharedElementTargetNames.add(name); + return this; + } + + @Override + public FragmentTransaction setSharedElements(Pair<View, String>... sharedElements) { + if (sharedElements == null || sharedElements.length == 0) { + mSharedElementSourceNames = null; + mSharedElementTargetNames = null; + } else { + ArrayList<String> sourceNames = new ArrayList<String>(sharedElements.length); + ArrayList<String> targetNames = new ArrayList<String>(sharedElements.length); + for (int i = 0; i < sharedElements.length; i++) { + String viewName = sharedElements[i].first.getViewName(); + if (viewName == null) { + throw new IllegalArgumentException("Unique viewNames are required for all" + + " sharedElements"); + } + sourceNames.add(viewName); + targetNames.add(sharedElements[i].second); + } + mSharedElementSourceNames = sourceNames; + mSharedElementTargetNames = targetNames; + } + return this; + } + public FragmentTransaction setTransitionStyle(int styleRes) { mTransitionStyle = styleRes; return this; @@ -550,21 +673,27 @@ final class BackStackRecord extends FragmentTransaction implements if (!mAddToBackStack) { return; } - if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting in " + this - + " by " + amt); + if (FragmentManagerImpl.DEBUG) { + Log.v(TAG, "Bump nesting in " + this + + " by " + amt); + } Op op = mHead; while (op != null) { if (op.fragment != null) { op.fragment.mBackStackNesting += amt; - if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of " - + op.fragment + " to " + op.fragment.mBackStackNesting); + if (FragmentManagerImpl.DEBUG) { + Log.v(TAG, "Bump nesting of " + + op.fragment + " to " + op.fragment.mBackStackNesting); + } } if (op.removed != null) { - for (int i=op.removed.size()-1; i>=0; i--) { + for (int i = op.removed.size() - 1; i >= 0; i--) { Fragment r = op.removed.get(i); r.mBackStackNesting += amt; - if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of " - + r + " to " + r.mBackStackNesting); + if (FragmentManagerImpl.DEBUG) { + Log.v(TAG, "Bump nesting of " + + r + " to " + r.mBackStackNesting); + } } } op = op.next; @@ -578,9 +707,11 @@ final class BackStackRecord extends FragmentTransaction implements public int commitAllowingStateLoss() { return commitInternal(true); } - + int commitInternal(boolean allowStateLoss) { - if (mCommitted) throw new IllegalStateException("commit already called"); + if (mCommitted) { + throw new IllegalStateException("commit already called"); + } if (FragmentManagerImpl.DEBUG) { Log.v(TAG, "Commit: " + this); LogWriter logw = new LogWriter(Log.VERBOSE, TAG); @@ -597,9 +728,11 @@ final class BackStackRecord extends FragmentTransaction implements mManager.enqueueAction(this, allowStateLoss); return mIndex; } - + public void run() { - if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Run: " + this); + if (FragmentManagerImpl.DEBUG) { + Log.v(TAG, "Run: " + this); + } if (mAddToBackStack) { if (mIndex < 0) { @@ -609,6 +742,9 @@ final class BackStackRecord extends FragmentTransaction implements bumpBackStackNesting(1); + TransitionState state = beginTransition(mSharedElementSourceNames, + mSharedElementTargetNames); + Op op = mHead; while (op != null) { switch (op.cmd) { @@ -616,14 +752,17 @@ final class BackStackRecord extends FragmentTransaction implements Fragment f = op.fragment; f.mNextAnim = op.enterAnim; mManager.addFragment(f, false); - } break; + } + break; case OP_REPLACE: { Fragment f = op.fragment; if (mManager.mAdded != null) { - for (int i=0; i<mManager.mAdded.size(); i++) { + for (int i = 0; i < mManager.mAdded.size(); i++) { Fragment old = mManager.mAdded.get(i); - if (FragmentManagerImpl.DEBUG) Log.v(TAG, - "OP_REPLACE: adding=" + f + " old=" + old); + if (FragmentManagerImpl.DEBUG) { + Log.v(TAG, + "OP_REPLACE: adding=" + f + " old=" + old); + } if (f == null || old.mContainerId == f.mContainerId) { if (old == f) { op.fragment = f = null; @@ -635,8 +774,10 @@ final class BackStackRecord extends FragmentTransaction implements old.mNextAnim = op.exitAnim; if (mAddToBackStack) { old.mBackStackNesting += 1; - if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of " - + old + " to " + old.mBackStackNesting); + if (FragmentManagerImpl.DEBUG) { + Log.v(TAG, "Bump nesting of " + + old + " to " + old.mBackStackNesting); + } } mManager.removeFragment(old, mTransition, mTransitionStyle); } @@ -647,32 +788,38 @@ final class BackStackRecord extends FragmentTransaction implements f.mNextAnim = op.enterAnim; mManager.addFragment(f, false); } - } break; + } + break; case OP_REMOVE: { Fragment f = op.fragment; f.mNextAnim = op.exitAnim; mManager.removeFragment(f, mTransition, mTransitionStyle); - } break; + } + break; case OP_HIDE: { Fragment f = op.fragment; f.mNextAnim = op.exitAnim; mManager.hideFragment(f, mTransition, mTransitionStyle); - } break; + } + break; case OP_SHOW: { Fragment f = op.fragment; f.mNextAnim = op.enterAnim; mManager.showFragment(f, mTransition, mTransitionStyle); - } break; + } + break; case OP_DETACH: { Fragment f = op.fragment; f.mNextAnim = op.exitAnim; mManager.detachFragment(f, mTransition, mTransitionStyle); - } break; + } + break; case OP_ATTACH: { Fragment f = op.fragment; f.mNextAnim = op.enterAnim; mManager.attachFragment(f, mTransition, mTransitionStyle); - } break; + } + break; default: { throw new IllegalArgumentException("Unknown cmd: " + op.cmd); } @@ -687,9 +834,174 @@ final class BackStackRecord extends FragmentTransaction implements if (mAddToBackStack) { mManager.addBackStackState(this); } + + if (state != null) { + updateTransitionEndState(state, mSharedElementTargetNames); + } } - public void popFromBackStack(boolean doStateMove) { + private TransitionState beginTransition(ArrayList<String> sourceNames, + ArrayList<String> targetNames) { + if (mCustomTransition <= 0 || mSceneRoot <= 0) { + return null; + } + View rootView = mManager.mContainer.findViewById(mSceneRoot); + if (!(rootView instanceof ViewGroup)) { + throw new IllegalArgumentException("SceneRoot is not a ViewGroup"); + } + TransitionState state = new TransitionState(); + // get Transition scene root and create Transitions + state.sceneRoot = (ViewGroup) rootView; + state.sceneRoot.captureTransitioningViews(state.transitioningViews); + + state.exitTransition = TransitionInflater.from(mManager.mActivity) + .inflateTransition(mCustomTransition); + state.sharedElementTransition = TransitionInflater.from(mManager.mActivity) + .inflateTransition(mCustomTransition); + state.enterTransition = TransitionInflater.from(mManager.mActivity) + .inflateTransition(mCustomTransition); + // 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. + View nonExistentView = new View(mManager.mActivity); + state.enterTransition.addTarget(nonExistentView); + state.exitTransition.addTarget(nonExistentView); + state.sharedElementTransition.addTarget(nonExistentView); + + setSharedElementEpicenter(state.enterTransition, state); + + state.excludingTransition = new TransitionSet() + .addTransition(state.exitTransition) + .addTransition(state.enterTransition); + + if (sourceNames != null) { + // Map shared elements. + state.sceneRoot.findNamedViews(state.namedViews); + state.namedViews.retainAll(sourceNames); + View epicenterView = state.namedViews.get(sourceNames.get(0)); + if (epicenterView != null) { + // The epicenter is only the first shared element. + setEpicenter(state.exitTransition, epicenterView); + setEpicenter(state.sharedElementTransition, epicenterView); + } + state.transitioningViews.removeAll(state.namedViews.values()); + state.excludingTransition.addTransition(state.sharedElementTransition); + addTransitioningViews(state.sharedElementTransition, state.namedViews.values()); + } + + // Adds the (maybe) exiting views, not including the shared element. + // If some stay, that's ok. + addTransitioningViews(state.exitTransition, state.transitioningViews); + + // Prepare for shared element name mapping. This could be chained in the case + // of popping several back stack states. + state.excludingTransition.setNameOverrides(new ArrayMap<String, String>()); + setNameOverrides(state, sourceNames, targetNames); + + // Don't include any subtree in the views that are hidden when capturing the + // view hierarchy transitions. They should be as if not there. + excludeHiddenFragments(state, true); + + TransitionManager.beginDelayedTransition(state.sceneRoot, state.excludingTransition); + return state; + } + + private void updateTransitionEndState(TransitionState state, ArrayList<String> names) { + // Find all views that are entering. + ArrayList<View> enteringViews = new ArrayList<View>(); + state.sceneRoot.captureTransitioningViews(enteringViews); + enteringViews.removeAll(state.transitioningViews); + + if (names != null) { + // find all shared elements. + state.namedViews.clear(); + state.sceneRoot.findNamedViews(state.namedViews); + state.namedViews.retainAll(names); + if (!state.namedViews.isEmpty()) { + enteringViews.removeAll(state.namedViews.values()); + addTransitioningViews(state.sharedElementTransition, state.namedViews.values()); + // now we know the epicenter of the entering transition. + state.mEnteringEpicenterView = state.namedViews.get(names.get(0)); + } + } + + // Add all entering views to the enter transition. + addTransitioningViews(state.enterTransition, enteringViews); + + // Don't allow capturing state for the newly-hidden fragments. + excludeHiddenFragments(state, false); + + // Allow capturing state for the newly-shown fragments + includeVisibleFragments(state.excludingTransition); + } + + private void addTransitioningViews(Transition transition, Collection<View> views) { + if (views.isEmpty()) { + // Add a view so that we can modify the valid views at the end of the + // fragment transaction. + transition.addTarget(new View(mManager.mActivity)); + } else { + for (View view : views) { + transition.addTarget(view); + } + } + } + + private void excludeHiddenFragments(TransitionState state, boolean forceExclude) { + if (mManager.mAdded != null) { + for (int i = 0; i < mManager.mAdded.size(); i++) { + Fragment fragment = mManager.mAdded.get(i); + if (fragment.mView != null && fragment.mHidden + && (forceExclude || !state.hiddenViews.contains(fragment.mView))) { + state.excludingTransition.excludeTarget(fragment.mView, true); + state.hiddenViews.add(fragment.mView); + } + } + } + if (forceExclude && state.hiddenViews.isEmpty()) { + state.excludingTransition.excludeTarget(new View(mManager.mActivity), true); + } + } + + private void includeVisibleFragments(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.mHidden) { + transition.excludeTarget(fragment.mView, false); + } + } + } + } + + 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.mEnteringEpicenterView != null) { + mEpicenter = new Rect(); + state.mEnteringEpicenterView.getBoundsOnScreen(mEpicenter); + } + return mEpicenter; + } + }); + } + + public TransitionState popFromBackStack(boolean doStateMove, TransitionState state) { if (FragmentManagerImpl.DEBUG) { Log.v(TAG, "popFromBackStack: " + this); LogWriter logw = new LogWriter(Log.VERBOSE, TAG); @@ -698,6 +1010,12 @@ final class BackStackRecord extends FragmentTransaction implements pw.flush(); } + if (state == null) { + state = beginTransition(mSharedElementTargetNames, mSharedElementSourceNames); + } else { + setNameOverrides(state, mSharedElementTargetNames, mSharedElementSourceNames); + } + bumpBackStackNesting(-1); Op op = mTail; @@ -709,7 +1027,8 @@ final class BackStackRecord extends FragmentTransaction implements mManager.removeFragment(f, FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); - } break; + } + break; case OP_REPLACE: { Fragment f = op.fragment; if (f != null) { @@ -719,42 +1038,48 @@ final class BackStackRecord extends FragmentTransaction implements mTransitionStyle); } if (op.removed != null) { - for (int i=0; i<op.removed.size(); i++) { + for (int i = 0; i < op.removed.size(); i++) { Fragment old = op.removed.get(i); old.mNextAnim = op.popEnterAnim; mManager.addFragment(old, false); } } - } break; + } + break; case OP_REMOVE: { Fragment f = op.fragment; f.mNextAnim = op.popEnterAnim; mManager.addFragment(f, false); - } break; + } + break; case OP_HIDE: { Fragment f = op.fragment; f.mNextAnim = op.popEnterAnim; mManager.showFragment(f, FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); - } break; + } + break; case OP_SHOW: { Fragment f = op.fragment; f.mNextAnim = op.popExitAnim; mManager.hideFragment(f, FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); - } break; + } + break; case OP_DETACH: { Fragment f = op.fragment; f.mNextAnim = op.popEnterAnim; mManager.attachFragment(f, FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); - } break; + } + break; case OP_ATTACH: { Fragment f = op.fragment; f.mNextAnim = op.popExitAnim; mManager.detachFragment(f, FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); - } break; + } + break; default: { throw new IllegalArgumentException("Unknown cmd: " + op.cmd); } @@ -766,12 +1091,39 @@ final class BackStackRecord extends FragmentTransaction implements if (doStateMove) { mManager.moveToState(mManager.mCurState, FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle, true); + if (state != null) { + updateTransitionEndState(state, mSharedElementSourceNames); + state = null; + } } if (mIndex >= 0) { mManager.freeBackStackIndex(mIndex); mIndex = -1; } + return state; + } + + private static void setNameOverride(Transition transition, String source, String target) { + ArrayMap<String, String> overrides = transition.getNameOverrides(); + for (int index = 0; index < overrides.size(); index++) { + if (source.equals(overrides.valueAt(index))) { + overrides.setValueAt(index, target); + return; + } + } + overrides.put(source, target); + } + + private static void setNameOverrides(TransitionState state, ArrayList<String> sourceNames, + ArrayList<String> targetNames) { + if (sourceNames != null) { + for (int i = 0; i < sourceNames.size(); i++) { + String source = sourceNames.get(i); + String target = targetNames.get(i); + setNameOverride(state.excludingTransition, source, target); + } + } } public String getName() { @@ -789,4 +1141,16 @@ final class BackStackRecord extends FragmentTransaction implements public boolean isEmpty() { return mNumOp == 0; } + + public class TransitionState { + public ArrayList<View> hiddenViews = new ArrayList<View>(); + public ArrayList<View> transitioningViews = new ArrayList<View>(); + public ArrayMap<String, View> namedViews = new ArrayMap<String, View>(); + public Transition exitTransition; + public Transition sharedElementTransition; + public Transition enterTransition; + public TransitionSet excludingTransition; + public ViewGroup sceneRoot; + public View mEnteringEpicenterView; + } } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index ab3bb49..a42bd3b 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -115,9 +115,8 @@ import android.os.storage.IMountService; import android.os.storage.StorageManager; import android.print.IPrintManager; import android.print.PrintManager; +import android.service.fingerprint.IFingerprintService; import android.service.fingerprint.FingerprintManager; -import android.service.fingerprint.FingerprintManagerReceiver; -import android.service.fingerprint.FingerprintService; import android.telecomm.TelecommManager; import android.telephony.TelephonyManager; import android.content.ClipboardManager; @@ -466,11 +465,6 @@ class ContextImpl extends Context { return new KeyguardManager(); }}); - registerService(FINGERPRINT_SERVICE, new ServiceFetcher() { - public Object createService(ContextImpl ctx) { - return new FingerprintManager(ctx); - }}); - registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext()); @@ -690,6 +684,7 @@ class ContextImpl extends Context { return new MediaSessionManager(ctx); } }); + registerService(TRUST_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { IBinder b = ServiceManager.getService(TRUST_SERVICE); @@ -697,6 +692,14 @@ class ContextImpl extends Context { } }); + registerService(FINGERPRINT_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + IBinder b = ServiceManager.getService(FINGERPRINT_SERVICE); + IFingerprintService service = IFingerprintService.Stub.asInterface(b); + return new FingerprintManager(ctx.getOuterContext(), service); + } + }); + registerService(TV_INPUT_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { IBinder iBinder = ServiceManager.getService(TV_INPUT_SERVICE); diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index f54cb87..365cc8e 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -106,7 +106,16 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { private void sendSharedElementDestination() { ViewGroup decor = getDecor(); - if (!decor.isLayoutRequested()) { + boolean allReady = !decor.isLayoutRequested(); + if (allReady) { + for (int i = 0; i < mSharedElements.size(); i++) { + if (mSharedElements.get(i).isLayoutRequested()) { + allReady = false; + break; + } + } + } + if (allReady) { Bundle state = captureSharedElementState(); mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state); } else { @@ -115,10 +124,15 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { @Override public boolean onPreDraw() { getDecor().getViewTreeObserver().removeOnPreDrawListener(this); + Bundle state = captureSharedElementState(); + mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state); return true; } }); } + if (allowOverlappingTransitions()) { + startEnterTransitionOnly(); + } } private static SharedElementListener getListener(Activity activity, boolean isReturning) { @@ -207,24 +221,6 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { } } - protected void onTakeSharedElements() { - if (!mIsReadyForTransition || mSharedElementsBundle == null) { - return; - } - final Bundle sharedElementState = mSharedElementsBundle; - mSharedElementsBundle = null; - getDecor().getViewTreeObserver() - .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - getDecor().getViewTreeObserver().removeOnPreDrawListener(this); - startSharedElementTransition(sharedElementState); - return false; - } - }); - getDecor().invalidate(); - } - private void startSharedElementTransition(Bundle sharedElementState) { setEpicenter(); // Remove rejected shared elements @@ -242,7 +238,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { setSharedElementState(sharedElementState, sharedElementSnapshots); requestLayoutForSharedElements(); - boolean startEnterTransition = allowOverlappingTransitions(); + boolean startEnterTransition = allowOverlappingTransitions() && !mIsReturning; boolean startSharedElementTransition = true; Transition transition = beginTransition(startEnterTransition, startSharedElementTransition); @@ -258,6 +254,29 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { mResultReceiver = null; // all done sending messages. } + private void onTakeSharedElements() { + if (!mIsReadyForTransition || mSharedElementsBundle == null) { + return; + } + final Bundle sharedElementState = mSharedElementsBundle; + mSharedElementsBundle = null; + getDecor().getViewTreeObserver() + .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + getDecor().getViewTreeObserver().removeOnPreDrawListener(this); + startTransition(new Runnable() { + @Override + public void run() { + startSharedElementTransition(sharedElementState); + } + }); + return false; + } + }); + getDecor().invalidate(); + } + private void requestLayoutForSharedElements() { int numSharedElements = mSharedElements.size(); for (int i = 0; i < numSharedElements; i++) { @@ -282,9 +301,10 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { if (transition == null) { sharedElementTransitionStarted(); } else { - transition.addListener(new Transition.TransitionListenerAdapter() { + transition.addListener(new ContinueTransitionListener() { @Override public void onTransitionStart(Transition transition) { + super.onTransitionStart(transition); transition.removeListener(this); sharedElementTransitionStarted(); } @@ -292,12 +312,17 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { } } if (transition != null) { + if (sharedElementTransition == null) { + transition.addListener(new ContinueTransitionListener()); + } TransitionManager.beginDelayedTransition(getDecor(), transition); if (startSharedElementTransition && !mSharedElementNames.isEmpty()) { mSharedElements.get(0).invalidate(); } else if (startEnterTransition && !mTransitioningViews.isEmpty()) { mTransitioningViews.get(0).invalidate(); } + } else { + transitionStarted(); } return transition; } @@ -388,11 +413,21 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { protected void onRemoteExitTransitionComplete() { if (!allowOverlappingTransitions()) { - boolean startEnterTransition = true; - boolean startSharedElementTransition = false; - Transition transition = beginTransition(startEnterTransition, - startSharedElementTransition); - startEnterTransition(transition); + startEnterTransitionOnly(); } } + + private void startEnterTransitionOnly() { + startTransition(new Runnable() { + @Override + public void run() { + setEpicenter(); + boolean startEnterTransition = true; + boolean startSharedElementTransition = false; + Transition transition = beginTransition(startEnterTransition, + startSharedElementTransition); + startEnterTransition(transition); + } + }); + } } diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java index 8d5b831..9f3dbdc 100644 --- a/core/java/android/app/ExitTransitionCoordinator.java +++ b/core/java/android/app/ExitTransitionCoordinator.java @@ -19,6 +19,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.Intent; +import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; @@ -27,6 +28,7 @@ import android.os.Message; import android.transition.Transition; import android.transition.TransitionManager; import android.view.View; +import android.view.ViewGroupOverlay; import android.view.ViewTreeObserver; import java.util.ArrayList; @@ -60,10 +62,10 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { private boolean mIsHidden; - private boolean mExitTransitionStarted; - private Bundle mExitSharedElementBundle; + private ArrayList<View> mSharedElementSnapshots; + public ExitTransitionCoordinator(Activity activity, ArrayList<String> names, ArrayList<String> accepted, ArrayList<String> mapped, boolean isReturning) { super(activity.getWindow(), names, accepted, mapped, getListener(activity, isReturning), @@ -106,30 +108,83 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { break; case MSG_SHARED_ELEMENT_DESTINATION: mExitSharedElementBundle = resultData; - if (mExitTransitionStarted) { + sharedElementExitBack(); + break; + } + } + + private void sharedElementExitBack() { + if (!mSharedElements.isEmpty() && getSharedElementTransition() != null) { + startTransition(new Runnable() { + public void run() { startSharedElementExit(); } - break; + }); } } private void startSharedElementExit() { - if (!mSharedElements.isEmpty() && getSharedElementTransition() != null) { - Transition transition = getSharedElementExitTransition(); - TransitionManager.beginDelayedTransition(getDecor(), transition); - ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle, - mSharedElementNames); - setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots); + Transition transition = getSharedElementExitTransition(); + final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle, + mSharedElementNames); + mSharedElementSnapshots = createSnapshots(mExitSharedElementBundle, mSharedElementNames); + transition.addListener(new Transition.TransitionListenerAdapter() { + @Override + public void onTransitionEnd(Transition transition) { + setViewVisibility(mSharedElements, View.INVISIBLE); + ViewGroupOverlay overlay = getDecor().getOverlay(); + if (mSharedElementSnapshots != null) { + for (int i = 0; i < mSharedElementSnapshots.size(); i++) { + overlay.add(mSharedElementSnapshots.get(i)); + } + } + } + }); + getDecor().getViewTreeObserver() + .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + getDecor().getViewTreeObserver().removeOnPreDrawListener(this); + setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots); + return true; + } + }); + TransitionManager.beginDelayedTransition(getDecor(), transition); + getDecor().invalidate(); + } + + private static ArrayList<View> copySnapshots(ArrayList<View> snapshots) { + ArrayList<View> copy = new ArrayList<View>(snapshots.size()); + for (int i = 0; i < snapshots.size(); i++) { + View view = snapshots.get(i); + View viewCopy = new View(view.getContext()); + viewCopy.setBackground(view.getBackground()); + copy.add(viewCopy); } + return copy; } private void hideSharedElements() { - setViewVisibility(mSharedElements, View.INVISIBLE); + if (!mIsHidden) { + setViewVisibility(mSharedElements, View.INVISIBLE); + } + if (mSharedElementSnapshots != null) { + ViewGroupOverlay overlay = getDecor().getOverlay(); + for (int i = 0; i < mSharedElementSnapshots.size(); i++) { + overlay.remove(mSharedElementSnapshots.get(i)); + } + mSharedElementSnapshots = null; + } finishIfNecessary(); } public void startExit() { - beginTransitions(); + startTransition(new Runnable() { + @Override + public void run() { + beginTransitions(); + } + }); setViewVisibility(mTransitioningViews, View.INVISIBLE); } @@ -159,29 +214,25 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { } } }, options); + startTransition(new Runnable() { + @Override + public void run() { + startExitTransition(); + } + }); + } + + private void startExitTransition() { Transition sharedElementTransition = mSharedElements.isEmpty() ? null : getSharedElementTransition(); if (sharedElementTransition == null) { sharedElementTransitionComplete(); } - Transition transition = mergeTransitions(sharedElementTransition, getExitTransition()); - if (transition == null) { - mExitTransitionStarted = true; - } else { + Transition transition = mergeTransitions(sharedElementTransition, + getExitTransition()); + if (transition != null) { TransitionManager.beginDelayedTransition(getDecor(), transition); setViewVisibility(mTransitioningViews, View.INVISIBLE); - getDecor().getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - getDecor().getViewTreeObserver().removeOnPreDrawListener(this); - mExitTransitionStarted = true; - if (mExitSharedElementBundle != null) { - startSharedElementExit(); - } - notifyComplete(); - return true; - } - }); } } @@ -212,7 +263,7 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { if (viewsTransition == null) { exitTransitionComplete(); } else { - viewsTransition.addListener(new Transition.TransitionListenerAdapter() { + viewsTransition.addListener(new ContinueTransitionListener() { @Override public void onTransitionEnd(Transition transition) { exitTransitionComplete(); @@ -238,7 +289,7 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { if (sharedElementTransition == null) { sharedElementTransitionComplete(); } else { - sharedElementTransition.addListener(new Transition.TransitionListenerAdapter() { + sharedElementTransition.addListener(new ContinueTransitionListener() { @Override public void onTransitionEnd(Transition transition) { sharedElementTransitionComplete(); @@ -257,7 +308,6 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { Transition viewsTransition = getExitTransition(); Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); - mExitTransitionStarted = true; if (transition != null) { TransitionManager.beginDelayedTransition(getDecor(), transition); } @@ -269,15 +319,31 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { } protected boolean isReadyToNotify() { - return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady - && mExitTransitionStarted; + return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady; } private void sharedElementTransitionComplete() { - mSharedElementBundle = captureSharedElementState(); + mSharedElementBundle = mExitSharedElementBundle == null + ? captureSharedElementState() : captureExitSharedElementsState(); notifyComplete(); } + private Bundle captureExitSharedElementsState() { + Bundle bundle = new Bundle(); + Rect bounds = new Rect(); + for (int i = 0; i < mSharedElementNames.size(); i++) { + String name = mSharedElementNames.get(i); + Bundle sharedElementState = mExitSharedElementBundle.getBundle(name); + if (sharedElementState != null) { + bundle.putBundle(name, sharedElementState); + } else { + View view = mSharedElements.get(i); + captureSharedElementState(view, name, bundle, bounds); + } + } + return bundle; + } + protected void notifyComplete() { if (isReadyToNotify()) { if (!mSharedElementNotified) { @@ -294,8 +360,7 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { } private void finishIfNecessary() { - if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty() - || mSharedElements.get(0).getVisibility() == View.INVISIBLE)) { + if (mIsReturning && mExitNotified && mActivity != null && mSharedElementSnapshots == null) { mActivity.finish(); mActivity.overridePendingTransition(0, 0); mActivity = null; diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 76f9d97..b8f1962 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -1494,7 +1494,7 @@ final class FragmentManagerImpl extends FragmentManager { return false; } final BackStackRecord bss = mBackStack.remove(last); - bss.popFromBackStack(true); + bss.popFromBackStack(true, null); reportBackStackChanged(); } else { int index = -1; @@ -1538,9 +1538,10 @@ final class FragmentManagerImpl extends FragmentManager { states.add(mBackStack.remove(i)); } final int LAST = states.size()-1; + BackStackRecord.TransitionState state = null; for (int i=0; i<=LAST; i++) { if (DEBUG) Log.v(TAG, "Popping back stack state: " + states.get(i)); - states.get(i).popFromBackStack(i == LAST); + state = states.get(i).popFromBackStack(i == LAST, state); } reportBackStackChanged(); } diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java index 6e99899..7479ecd 100644 --- a/core/java/android/app/FragmentTransaction.java +++ b/core/java/android/app/FragmentTransaction.java @@ -1,5 +1,8 @@ package android.app; +import android.util.Pair; +import android.view.View; + /** * API for performing a set of Fragment operations. * @@ -169,6 +172,36 @@ public abstract class FragmentTransaction { public abstract FragmentTransaction setTransition(int transit); /** + * Set a {@link android.transition.Transition} resource id to use with this transaction. + * <var>transitionId</var> will be played for fragments when going forward and when popping + * the back stack. + * @param sceneRootId The ID of the element acting as the scene root for the transition. + * This should be a ViewGroup containing all Fragments in the transaction. + * @param transitionId The resource ID for the Transition used during the Fragment transaction. + */ + public abstract FragmentTransaction setCustomTransition(int sceneRootId, int transitionId); + + /** + * Used with {@link #setCustomTransition(int, int)} to map a View from a removed or hidden + * Fragment to a View from a shown or added Fragment. + * <var>sharedElement</var> must have a unique viewName in the View hierarchy. + * @param sharedElement A View in a disappearing Fragment to match with a View in an + * appearing Fragment. + * @param name The viewName for a View in an appearing Fragment to match to the shared + * element. + */ + public abstract FragmentTransaction setSharedElement(View sharedElement, String name); + + /** + * Used with {@link #setCustomTransition(int, int)} to map multiple Views from removed or hidden + * Fragments to a Views from a shown or added Fragments. Views in + * <var>sharedElements</var> must have unique viewNames in the View hierarchy. + * @param sharedElements Pairs of Views in disappearing Fragments to viewNames in + * appearing Fragments. + */ + public abstract FragmentTransaction setSharedElements(Pair<View, String>... sharedElements); + + /** * Set a custom style resource that will be used for resolving transit * animations. */ diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 276f936..b94fd41 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2867,16 +2867,16 @@ public class Notification implements Parcelable /** * Helper class for generating large-format notifications that include a large image attachment. * - * This class is a "rebuilder": It consumes a Builder object and modifies its behavior, like so: + * Here's how you'd set the <code>BigPictureStyle</code> on a notification: * <pre class="prettyprint"> - * Notification noti = new Notification.BigPictureStyle( - * new Notification.Builder() - * .setContentTitle("New photo from " + sender.toString()) - * .setContentText(subject) - * .setSmallIcon(R.drawable.new_post) - * .setLargeIcon(aBitmap)) - * .bigPicture(aBigBitmap) - * .build(); + * Notification notif = new Notification.Builder(mContext) + * .setContentTitle("New photo from " + sender.toString()) + * .setContentText(subject) + * .setSmallIcon(R.drawable.new_post) + * .setLargeIcon(aBitmap) + * .setStyle(new Notification.BigPictureStyle() + * .bigPicture(aBigBitmap)) + * .build(); * </pre> * * @see Notification#bigContentView @@ -2963,16 +2963,16 @@ public class Notification implements Parcelable /** * Helper class for generating large-format notifications that include a lot of text. * - * This class is a "rebuilder": It consumes a Builder object and modifies its behavior, like so: + * Here's how you'd set the <code>BigTextStyle</code> on a notification: * <pre class="prettyprint"> - * Notification noti = new Notification.BigTextStyle( - * new Notification.Builder() - * .setContentTitle("New mail from " + sender.toString()) - * .setContentText(subject) - * .setSmallIcon(R.drawable.new_mail) - * .setLargeIcon(aBitmap)) - * .bigText(aVeryLongString) - * .build(); + * Notification notif = new Notification.Builder(mContext) + * .setContentTitle("New mail from " + sender.toString()) + * .setContentText(subject) + * .setSmallIcon(R.drawable.new_mail) + * .setLargeIcon(aBitmap) + * .setStyle(new Notification.BigTextStyle() + * .bigText(aVeryLongString)) + * .build(); * </pre> * * @see Notification#bigContentView @@ -3057,19 +3057,19 @@ public class Notification implements Parcelable /** * Helper class for generating large-format notifications that include a list of (up to 5) strings. * - * This class is a "rebuilder": It consumes a Builder object and modifies its behavior, like so: + * Here's how you'd set the <code>InboxStyle</code> on a notification: * <pre class="prettyprint"> - * Notification noti = new Notification.InboxStyle( - * new Notification.Builder() - * .setContentTitle("5 New mails from " + sender.toString()) - * .setContentText(subject) - * .setSmallIcon(R.drawable.new_mail) - * .setLargeIcon(aBitmap)) - * .addLine(str1) - * .addLine(str2) - * .setContentTitle("") - * .setSummaryText("+3 more") - * .build(); + * Notification notif = new Notification.Builder(mContext) + * .setContentTitle("5 New mails from " + sender.toString()) + * .setContentText(subject) + * .setSmallIcon(R.drawable.new_mail) + * .setLargeIcon(aBitmap) + * .setStyle(new Notification.InboxStyle() + * .addLine(str1) + * .addLine(str2) + * .setContentTitle("") + * .setSummaryText("+3 more")) + * .build(); * </pre> * * @see Notification#bigContentView @@ -3619,14 +3619,16 @@ public class Notification implements Parcelable * .build();</pre> * * <p>The activity to launch needs to allow embedding, must be exported, and - * should have an empty task affinity. + * should have an empty task affinity. It is also recommended to use the device + * default light theme. * * <p>Example AndroidManifest.xml entry: * <pre class="prettyprint"> * <activity android:name="com.example.MyDisplayActivity" * android:exported="true" * android:allowEmbedded="true" - * android:taskAffinity="" /></pre> + * android:taskAffinity="" + * android:theme="@android:style/Theme.DeviceDefault.Light" /></pre> * * @param intent the {@link PendingIntent} for an activity * @return this object for method chaining diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index e80c761..99f68d0 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -1506,12 +1506,11 @@ public class DevicePolicyManager { * * @return false if the certBuffer cannot be parsed or installation is * interrupted, otherwise true - * @hide */ - public boolean installCaCert(byte[] certBuffer) { + public boolean installCaCert(ComponentName who, byte[] certBuffer) { if (mService != null) { try { - return mService.installCaCert(certBuffer); + return mService.installCaCert(who, certBuffer); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1521,13 +1520,14 @@ public class DevicePolicyManager { /** * Uninstalls the given certificate from the list of User CAs, if present. - * - * @hide */ - public void uninstallCaCert(byte[] certBuffer) { + public void uninstallCaCert(ComponentName who, byte[] certBuffer) { if (mService != null) { try { - mService.uninstallCaCert(certBuffer); + final String alias = getCaCertAlias(certBuffer); + mService.uninstallCaCert(who, alias); + } catch (CertificateException e) { + Log.w(TAG, "Unable to parse certificate", e); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1536,10 +1536,8 @@ public class DevicePolicyManager { /** * Returns whether there are any user-installed CA certificates. - * - * @hide */ - public static boolean hasAnyCaCertsInstalled() { + public boolean hasAnyCaCertsInstalled() { TrustedCertificateStore certStore = new TrustedCertificateStore(); Set<String> aliases = certStore.userAliases(); return aliases != null && !aliases.isEmpty(); @@ -1547,18 +1545,10 @@ public class DevicePolicyManager { /** * Returns whether this certificate has been installed as a User CA. - * - * @hide */ public boolean hasCaCertInstalled(byte[] certBuffer) { - TrustedCertificateStore certStore = new TrustedCertificateStore(); - String alias; - byte[] pemCert; try { - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - X509Certificate cert = (X509Certificate) certFactory.generateCertificate( - new ByteArrayInputStream(certBuffer)); - return certStore.getCertificateAlias(cert) != null; + return getCaCertAlias(certBuffer) != null; } catch (CertificateException ce) { Log.w(TAG, "Could not parse certificate", ce); } @@ -1566,6 +1556,17 @@ public class DevicePolicyManager { } /** + * Returns the alias of a given CA certificate in the certificate store, or null if it + * doesn't exist. + */ + private static String getCaCertAlias(byte[] certBuffer) throws CertificateException { + final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + final X509Certificate cert = (X509Certificate) certFactory.generateCertificate( + new ByteArrayInputStream(certBuffer)); + return new TrustedCertificateStore().getCertificateAlias(cert); + } + + /** * Called by an application that is administering the device to disable all cameras * on the device. After setting this, no applications will be able to access any cameras * on the device. diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index a1caa21..e935da7 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -115,8 +115,8 @@ interface IDevicePolicyManager { String getProfileOwnerName(int userHandle); void setProfileEnabled(in ComponentName who); - boolean installCaCert(in byte[] certBuffer); - void uninstallCaCert(in byte[] certBuffer); + boolean installCaCert(in ComponentName admin, in byte[] certBuffer); + void uninstallCaCert(in ComponentName admin, in String alias); void addPersistentPreferredActivity(in ComponentName admin, in IntentFilter filter, in ComponentName activity); void clearPackagePersistentPreferredActivities(in ComponentName admin, String packageName); diff --git a/core/java/android/net/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl index dd9c39f..b7af374 100644 --- a/core/java/android/net/INetworkManagementEventObserver.aidl +++ b/core/java/android/net/INetworkManagementEventObserver.aidl @@ -17,6 +17,7 @@ package android.net; import android.net.LinkAddress; +import android.net.RouteInfo; /** * Callback class for receiving events from an INetworkManagementService @@ -98,4 +99,14 @@ interface INetworkManagementEventObserver { * @param servers The IP addresses of the DNS servers. */ void interfaceDnsServerInfo(String iface, long lifetime, in String[] servers); + + /** + * A route has been added or updated. + */ + void routeUpdated(in RouteInfo route); + + /** + * A route has been removed. + */ + void routeRemoved(in RouteInfo route); } diff --git a/core/java/android/net/IpPrefix.java b/core/java/android/net/IpPrefix.java index a14d13f..f1fa3eb 100644 --- a/core/java/android/net/IpPrefix.java +++ b/core/java/android/net/IpPrefix.java @@ -18,6 +18,7 @@ package android.net; import android.os.Parcel; import android.os.Parcelable; +import android.util.Pair; import java.net.InetAddress; import java.net.UnknownHostException; @@ -46,9 +47,18 @@ public final class IpPrefix implements Parcelable { private final byte[] address; // network byte order private final int prefixLength; + private void checkAndMaskAddressAndPrefixLength() { + if (address.length != 4 && address.length != 16) { + throw new IllegalArgumentException( + "IpPrefix has " + address.length + " bytes which is neither 4 nor 16"); + } + NetworkUtils.maskRawAddress(address, prefixLength); + } + /** * Constructs a new {@code IpPrefix} from a byte array containing an IPv4 or IPv6 address in - * network byte order and a prefix length. + * network byte order and a prefix length. Silently truncates the address to the prefix length, + * so for example {@code 192.0.2.1/24} is silently converted to {@code 192.0.2.0/24}. * * @param address the IP address. Must be non-null and exactly 4 or 16 bytes long. * @param prefixLength the prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6). @@ -56,24 +66,46 @@ public final class IpPrefix implements Parcelable { * @hide */ public IpPrefix(byte[] address, int prefixLength) { - if (address.length != 4 && address.length != 16) { - throw new IllegalArgumentException( - "IpPrefix has " + address.length + " bytes which is neither 4 nor 16"); - } - if (prefixLength < 0 || prefixLength > (address.length * 8)) { - throw new IllegalArgumentException("IpPrefix with " + address.length + - " bytes has invalid prefix length " + prefixLength); - } this.address = address.clone(); this.prefixLength = prefixLength; - // TODO: Validate that the non-prefix bits are zero + checkAndMaskAddressAndPrefixLength(); } /** + * Constructs a new {@code IpPrefix} from an IPv4 or IPv6 address and a prefix length. Silently + * truncates the address to the prefix length, so for example {@code 192.0.2.1/24} is silently + * converted to {@code 192.0.2.0/24}. + * + * @param address the IP address. Must be non-null. + * @param prefixLength the prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6). * @hide */ public IpPrefix(InetAddress address, int prefixLength) { - this(address.getAddress(), prefixLength); + // We don't reuse the (byte[], int) constructor because it calls clone() on the byte array, + // which is unnecessary because getAddress() already returns a clone. + this.address = address.getAddress(); + this.prefixLength = prefixLength; + checkAndMaskAddressAndPrefixLength(); + } + + /** + * Constructs a new IpPrefix from a string such as "192.0.2.1/24" or "2001:db8::1/64". + * Silently truncates the address to the prefix length, so for example {@code 192.0.2.1/24} + * is silently converted to {@code 192.0.2.0/24}. + * + * @param prefix the prefix to parse + * + * @hide + */ + public IpPrefix(String prefix) { + // We don't reuse the (InetAddress, int) constructor because "error: call to this must be + // first statement in constructor". We could factor out setting the member variables to an + // init() method, but if we did, then we'd have to make the members non-final, or "error: + // cannot assign a value to final variable address". So we just duplicate the code here. + Pair<InetAddress, Integer> ipAndMask = NetworkUtils.parseIpAndMask(prefix); + this.address = ipAndMask.first.getAddress(); + this.prefixLength = ipAndMask.second; + checkAndMaskAddressAndPrefixLength(); } /** @@ -129,7 +161,7 @@ public final class IpPrefix implements Parcelable { } /** - * Returns the prefix length of this {@code IpAddress}. + * Returns the prefix length of this {@code IpPrefix}. * * @return the prefix length. */ @@ -138,6 +170,20 @@ public final class IpPrefix implements Parcelable { } /** + * Returns a string representation of this {@code IpPrefix}. + * + * @return a string such as {@code "192.0.2.0/24"} or {@code "2001:db8:1:2::"}. + */ + public String toString() { + try { + return InetAddress.getByAddress(address).getHostAddress() + "/" + prefixLength; + } catch(UnknownHostException e) { + // Cosmic rays? + throw new IllegalStateException("IpPrefix with invalid address! Shouldn't happen.", e); + } + } + + /** * Implement the Parcelable interface. */ public int describeContents() { diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java index 5246078..f9a25f9 100644 --- a/core/java/android/net/LinkAddress.java +++ b/core/java/android/net/LinkAddress.java @@ -18,6 +18,7 @@ package android.net; import android.os.Parcel; import android.os.Parcelable; +import android.util.Pair; import java.net.Inet4Address; import java.net.InetAddress; @@ -166,23 +167,9 @@ public class LinkAddress implements Parcelable { * @hide */ public LinkAddress(String address, int flags, int scope) { - InetAddress inetAddress = null; - int prefixLength = -1; - try { - String [] pieces = address.split("/", 2); - prefixLength = Integer.parseInt(pieces[1]); - inetAddress = InetAddress.parseNumericAddress(pieces[0]); - } catch (NullPointerException e) { // Null string. - } catch (ArrayIndexOutOfBoundsException e) { // No prefix length. - } catch (NumberFormatException e) { // Non-numeric prefix. - } catch (IllegalArgumentException e) { // Invalid IP address. - } - - if (inetAddress == null || prefixLength == -1) { - throw new IllegalArgumentException("Bad LinkAddress params " + address); - } - - init(inetAddress, prefixLength, flags, scope); + // This may throw an IllegalArgumentException; catching it is the caller's responsibility. + Pair<InetAddress, Integer> ipAndMask = NetworkUtils.parseIpAndMask(address); + init(ipAndMask.first, ipAndMask.second, flags, scope); } /** diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index 8eefa0f..e7184ed 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -31,6 +31,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Hashtable; import java.util.List; +import java.util.Objects; /** * Describes the properties of a network link. @@ -334,15 +335,17 @@ public final class LinkProperties implements Parcelable { } /** - * Adds a {@link RouteInfo} to this {@code LinkProperties}. If the {@link RouteInfo} - * had an interface name set and that differs from the interface set for this - * {@code LinkProperties} an {@link IllegalArgumentException} will be thrown. The - * proper course is to add either un-named or properly named {@link RouteInfo}. + * Adds a {@link RouteInfo} to this {@code LinkProperties}, if not present. If the + * {@link RouteInfo} had an interface name set and that differs from the interface set for this + * {@code LinkProperties} an {@link IllegalArgumentException} will be thrown. The proper + * course is to add either un-named or properly named {@link RouteInfo}. * * @param route A {@link RouteInfo} to add to this object. + * @return {@code false} if the route was already present, {@code true} if it was added. + * * @hide */ - public void addRoute(RouteInfo route) { + public boolean addRoute(RouteInfo route) { if (route != null) { String routeIface = route.getInterface(); if (routeIface != null && !routeIface.equals(mIfaceName)) { @@ -350,8 +353,28 @@ public final class LinkProperties implements Parcelable { "Route added with non-matching interface: " + routeIface + " vs. " + mIfaceName); } - mRoutes.add(routeWithInterface(route)); + route = routeWithInterface(route); + if (!mRoutes.contains(route)) { + mRoutes.add(route); + return true; + } } + return false; + } + + /** + * Removes a {@link RouteInfo} from this {@code LinkProperties}, if present. The route must + * specify an interface and the interface must match the interface of this + * {@code LinkProperties}, or it will not be removed. + * + * @return {@code true} if the route was removed, {@code false} if it was not present. + * + * @hide + */ + public boolean removeRoute(RouteInfo route) { + return route != null && + Objects.equals(mIfaceName, route.getInterface()) && + mRoutes.remove(route); } /** diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index b02f88e..15c0a71 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -24,6 +24,8 @@ import java.util.Collection; import java.util.Locale; import android.util.Log; +import android.util.Pair; + /** * Native methods for managing network interfaces. @@ -218,24 +220,17 @@ public class NetworkUtils { } /** - * Get InetAddress masked with prefixLength. Will never return null. - * @param IP address which will be masked with specified prefixLength - * @param prefixLength the prefixLength used to mask the IP + * Masks a raw IP address byte array with the specified prefix length. */ - public static InetAddress getNetworkPart(InetAddress address, int prefixLength) { - if (address == null) { - throw new RuntimeException("getNetworkPart doesn't accept null address"); - } - - byte[] array = address.getAddress(); - + public static void maskRawAddress(byte[] array, int prefixLength) { if (prefixLength < 0 || prefixLength > array.length * 8) { - throw new RuntimeException("getNetworkPart - bad prefixLength"); + throw new RuntimeException("IP address with " + array.length + + " bytes has invalid prefix length " + prefixLength); } int offset = prefixLength / 8; - int reminder = prefixLength % 8; - byte mask = (byte)(0xFF << (8 - reminder)); + int remainder = prefixLength % 8; + byte mask = (byte)(0xFF << (8 - remainder)); if (offset < array.length) array[offset] = (byte)(array[offset] & mask); @@ -244,6 +239,16 @@ public class NetworkUtils { for (; offset < array.length; offset++) { array[offset] = 0; } + } + + /** + * Get InetAddress masked with prefixLength. Will never return null. + * @param address the IP address to mask with + * @param prefixLength the prefixLength used to mask the IP + */ + public static InetAddress getNetworkPart(InetAddress address, int prefixLength) { + byte[] array = address.getAddress(); + maskRawAddress(array, prefixLength); InetAddress netPart = null; try { @@ -255,6 +260,30 @@ public class NetworkUtils { } /** + * Utility method to parse strings such as "192.0.2.5/24" or "2001:db8::cafe:d00d/64". + * @hide + */ + public static Pair<InetAddress, Integer> parseIpAndMask(String ipAndMaskString) { + InetAddress address = null; + int prefixLength = -1; + try { + String[] pieces = ipAndMaskString.split("/", 2); + prefixLength = Integer.parseInt(pieces[1]); + address = InetAddress.parseNumericAddress(pieces[0]); + } catch (NullPointerException e) { // Null string. + } catch (ArrayIndexOutOfBoundsException e) { // No prefix length. + } catch (NumberFormatException e) { // Non-numeric prefix. + } catch (IllegalArgumentException e) { // Invalid IP address. + } + + if (address == null || prefixLength == -1) { + throw new IllegalArgumentException("Invalid IP address and mask " + ipAndMaskString); + } + + return new Pair<InetAddress, Integer>(address, prefixLength); + } + + /** * Check if IP address type is consistent between two InetAddress. * @return true if both are the same type. False otherwise. */ diff --git a/core/java/android/net/PSKKeyManager.java b/core/java/android/net/PSKKeyManager.java new file mode 100644 index 0000000..92dd141 --- /dev/null +++ b/core/java/android/net/PSKKeyManager.java @@ -0,0 +1,186 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import java.net.Socket; +import javax.crypto.SecretKey; +import javax.net.ssl.SSLEngine; + +/** + * Provider of key material for pre-shared key (PSK) key exchange used in TLS-PSK cipher suites. + * + * <h3>Overview of TLS-PSK</h3> + * + * <p>TLS-PSK is a set of TLS/SSL cipher suites which rely on a symmetric pre-shared key (PSK) to + * secure the TLS/SSL connection and mutually authenticate its peers. These cipher suites may be + * a more natural fit compared to conventional public key based cipher suites in some scenarios + * where communication between peers is bootstrapped via a separate step (for example, a pairing + * step) and requires both peers to authenticate each other. In such scenarios a symmetric key (PSK) + * can be exchanged during the bootstrapping step, removing the need to generate and exchange public + * key pairs and X.509 certificates.</p> + * + * <p>When a TLS-PSK cipher suite is used, both peers have to use the same key for the TLS/SSL + * handshake to succeed. Thus, both peers are implicitly authenticated by a successful handshake. + * This removes the need to use a {@code TrustManager} in conjunction with this {@code KeyManager}. + * </p> + * + * <h3>Supporting multiple keys</h3> + * + * <p>A peer may have multiple keys to choose from. To help choose the right key, during the handshake + * the server can provide a <em>PSK identity hint</em> to the client, and the client can provide a + * <em>PSK identity</em> to the server. The contents of these two pieces of information are specific + * to application-level protocols.</p> + * + * <p><em>NOTE: Both the PSK identity hint and the PSK identity are transmitted in cleartext. + * Moreover, these data are received and processed prior to peer having been authenticated. Thus, + * they must not contain or leak key material or other sensitive information, and should be + * treated (e.g., parsed) with caution, as untrusted data.</em></p> + * + * <p>The high-level flow leading to peers choosing a key during TLS/SSL handshake is as follows: + * <ol> + * <li>Server receives a handshake request from client. + * <li>Server replies, optionally providing a PSK identity hint to client.</li> + * <li>Client chooses the key.</li> + * <li>Client provides a PSK identity of the chosen key to server.</li> + * <li>Server chooses the key.</li> + * </ol></p> + * + * <p>In the flow above, either peer can signal that they do not have a suitable key, in which case + * the the handshake will be aborted immediately. This may enable a network attacker who does not + * know the key to learn which PSK identity hints or PSK identities are supported. If this is a + * concern then a randomly generated key should be used in the scenario where no key is available. + * This will lead to the handshake aborting later, due to key mismatch -- same as in the scenario + * where a key is available -- making it appear to the attacker that all PSK identity hints and PSK + * identities are supported.</p> + * + * <h3>Maximum sizes</h3> + * + * <p>The maximum supported sizes are as follows: + * <ul> + * <li>256 bytes for keys (see {@link #MAX_KEY_LENGTH_BYTES}),</li> + * <li>128 bytes for PSK identity and PSK identity hint (in modified UTF-8 representation) (see + * {@link #MAX_IDENTITY_LENGTH_BYTES} and {@link #MAX_IDENTITY_HINT_LENGTH_BYTES}).</li> + * </ul></p> + * + * <h3>Example</h3> + * The following example illustrates how to create an {@code SSLContext} which enables the use of + * TLS-PSK in {@code SSLSocket}, {@code SSLServerSocket} and {@code SSLEngine} instances obtained + * from it. + * <pre> {@code + * PSKKeyManager myPskKeyManager = ...; + * + * SSLContext sslContext = SSLContext.getInstance("TLS"); + * sslContext.init( + * new KeyManager[] {myPskKeyManager}, + * new TrustManager[0], // No TrustManagers needed in TLS-PSK + * null // Use the default source of entropy + * ); + * + * SSLSocket sslSocket = (SSLSocket) sslContext.getSocketFactory().createSocket(...); + * // Enable a TLS-PSK cipher suite (no TLS-PSK cipher suites are enabled by default) + * sslSocket.setEnabledCipherSuites(new String[] {"TLS_PSK_WITH_AES_128_CBC_SHA"}); + * sslSocket.startHandshake(); + * }</pre> + */ +public interface PSKKeyManager extends com.android.org.conscrypt.PSKKeyManager { + // IMPLEMENTATION DETAILS: This class exists only because the default implemenetation of the + // TLS/SSL JSSE provider (currently Conscrypt) cannot depend on Android framework classes. + // As a result, this framework class simply extends the PSKKeyManager interface from Conscrypt + // without adding any new methods or fields. Moreover, for technical reasons (Conscrypt classes + // are "hidden") this class replaces the Javadoc of Conscrypt's PSKKeyManager. + + /** + * Maximum supported length (in bytes) for PSK identity hint (in modified UTF-8 representation). + */ + int MAX_IDENTITY_HINT_LENGTH_BYTES = + com.android.org.conscrypt.PSKKeyManager.MAX_IDENTITY_HINT_LENGTH_BYTES; + + /** Maximum supported length (in bytes) for PSK identity (in modified UTF-8 representation). */ + int MAX_IDENTITY_LENGTH_BYTES = + com.android.org.conscrypt.PSKKeyManager.MAX_IDENTITY_LENGTH_BYTES; + + /** Maximum supported length (in bytes) for PSK. */ + int MAX_KEY_LENGTH_BYTES = com.android.org.conscrypt.PSKKeyManager.MAX_KEY_LENGTH_BYTES; + + /** + * Gets the PSK identity hint to report to the client to help agree on the PSK for the provided + * socket. + * + * @return PSK identity hint to be provided to the client or {@code null} to provide no hint. + */ + @Override + String chooseServerKeyIdentityHint(Socket socket); + + /** + * Gets the PSK identity hint to report to the client to help agree on the PSK for the provided + * engine. + * + * @return PSK identity hint to be provided to the client or {@code null} to provide no hint. + */ + @Override + String chooseServerKeyIdentityHint(SSLEngine engine); + + /** + * Gets the PSK identity to report to the server to help agree on the PSK for the provided + * socket. + * + * @param identityHint identity hint provided by the server or {@code null} if none provided. + * + * @return PSK identity to provide to the server. {@code null} is permitted but will be + * converted into an empty string. + */ + @Override + String chooseClientKeyIdentity(String identityHint, Socket socket); + + /** + * Gets the PSK identity to report to the server to help agree on the PSK for the provided + * engine. + * + * @param identityHint identity hint provided by the server or {@code null} if none provided. + * + * @return PSK identity to provide to the server. {@code null} is permitted but will be + * converted into an empty string. + */ + @Override + String chooseClientKeyIdentity(String identityHint, SSLEngine engine); + + /** + * Gets the PSK to use for the provided socket. + * + * @param identityHint identity hint provided by the server to help select the key or + * {@code null} if none provided. + * @param identity identity provided by the client to help select the key. + * + * @return key or {@code null} to signal to peer that no suitable key is available and to abort + * the handshake. + */ + @Override + SecretKey getKey(String identityHint, String identity, Socket socket); + + /** + * Gets the PSK to use for the provided engine. + * + * @param identityHint identity hint provided by the server to help select the key or + * {@code null} if none provided. + * @param identity identity provided by the client to help select the key. + * + * @return key or {@code null} to signal to peer that no suitable key is available and to abort + * the handshake. + */ + @Override + SecretKey getKey(String identityHint, String identity, SSLEngine engine); +} diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index ba66e65..1e7d7f1 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -7963,8 +7963,7 @@ public final class ContactsContract { actualContext = ((ContextWrapper) actualContext).getBaseContext(); } final int intentFlags = (actualContext instanceof Activity) - ? Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET - : Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK; + ? 0 : Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK; // Launch pivot dialog through intent for now final Intent intent = new Intent(ACTION_QUICK_CONTACT).addFlags(intentFlags); diff --git a/core/java/android/service/fingerprint/FingerprintManager.java b/core/java/android/service/fingerprint/FingerprintManager.java index 2fcec52..b6137d1 100644 --- a/core/java/android/service/fingerprint/FingerprintManager.java +++ b/core/java/android/service/fingerprint/FingerprintManager.java @@ -22,12 +22,14 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; +import android.util.Slog; /** * A class that coordinates access to the fingerprint hardware. @@ -36,31 +38,40 @@ import android.util.Log; public class FingerprintManager { private static final String TAG = "FingerprintManager"; private static final boolean DEBUG = true; - private static final String FINGERPRINT_SERVICE_PACKAGE = "com.android.service.fingerprint"; - private static final String FINGERPRINT_SERVICE_CLASS = - "com.android.service.fingerprint.FingerprintService"; private static final int MSG_ENROLL_RESULT = 100; - private static final int MSG_SCANNED = 101; - private static final int MSG_ERROR = 102; - private static final int MSG_REMOVED = 103; + private static final int MSG_ACQUIRED = 101; + private static final int MSG_PROCESSED = 102; + private static final int MSG_ERROR = 103; + private static final int MSG_REMOVED = 104; + // Errors generated by layers above HAL public static final int FINGERPRINT_ERROR_NO_RECEIVER = -10; - public static final int FINGERPRINT_ERROR = -1; // One of the error messages below. - // Progress messages. - public static final int FINGERPRINT_SCANNED = 1; - public static final int FINGERPRINT_TEMPLATE_ENROLLING = 2; + // Message types. Must agree with HAL (fingerprint.h) + public static final int FINGERPRINT_ERROR = -1; + public static final int FINGERPRINT_ACQUIRED = 1; + public static final int FINGERPRINT_PROCESSED = 2; + public static final int FINGERPRINT_TEMPLATE_ENROLLING = 3; public static final int FINGERPRINT_TEMPLATE_REMOVED = 4; - // Error messages. Must agree with fingerprint HAL definitions. + // Error messages. Must agree with HAL (fingerprint.h) public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; - public static final int FINGERPRINT_ERROR_BAD_CAPTURE = 2; + public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; public static final int FINGERPRINT_ERROR_TIMEOUT = 3; public static final int FINGERPRINT_ERROR_NO_SPACE = 4; + // FINGERPRINT_ACQUIRED messages. Must agree with HAL (fingerprint.h) + public static final int FINGERPRINT_ACQUIRED_GOOD = 0; + public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; + public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; + public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 4; + public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 8; + public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 16; + private IFingerprintService mService; private FingerprintManagerReceiver mClientReceiver; private Context mContext; + private IBinder mToken = new Binder(); private Handler mHandler = new Handler() { public void handleMessage(android.os.Message msg) { @@ -69,8 +80,11 @@ public class FingerprintManager { case MSG_ENROLL_RESULT: mClientReceiver.onEnrollResult(msg.arg1, msg.arg2); break; - case MSG_SCANNED: - mClientReceiver.onScanned(msg.arg1, msg.arg2); + case MSG_ACQUIRED: + mClientReceiver.onAcquired(msg.arg1); + break; + case MSG_PROCESSED: + mClientReceiver.onProcessed(msg.arg1); break; case MSG_ERROR: mClientReceiver.onError(msg.arg1); @@ -82,45 +96,26 @@ public class FingerprintManager { } }; - public FingerprintManager(Context context) { + public FingerprintManager(Context context, IFingerprintService service) { mContext = context; - // Connect to service... - Intent intent = new Intent(); - intent.setClassName(FINGERPRINT_SERVICE_PACKAGE, FINGERPRINT_SERVICE_CLASS); - if (!context.bindServiceAsUser(intent, mFingerprintConnection, - Context.BIND_AUTO_CREATE, UserHandle.CURRENT_OR_SELF)) { - if (DEBUG) Log.v(TAG, "Can't bind to " + FINGERPRINT_SERVICE_CLASS); + mService = service; + if (mService == null) { + Slog.v(TAG, "FingerprintManagerService was null"); } } - private final ServiceConnection mFingerprintConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - if (DEBUG) Log.v(TAG, "Connected to FingerprintService"); - mService = IFingerprintService.Stub.asInterface(service); - try { - mService.startListening(mServiceReceiver, getCurrentUserId()); - } catch (RemoteException e) { - if (DEBUG) Log.v(TAG, "Failed to set callback", e); - } - } - - @Override - public void onServiceDisconnected(ComponentName name) { - if (DEBUG) Log.v(TAG, "Disconnected from FingerprintService"); - mService = null; - } - }; - private IFingerprintServiceReceiver mServiceReceiver = new IFingerprintServiceReceiver.Stub() { public void onEnrollResult(int fingerprintId, int remaining) { mHandler.obtainMessage(MSG_ENROLL_RESULT, fingerprintId, remaining).sendToTarget(); } - public void onScanned(int fingerprintId, int confidence) { - mHandler.obtainMessage(MSG_SCANNED, fingerprintId, confidence) - .sendToTarget();; + public void onAcquired(int acquireInfo) { + mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, 0).sendToTarget(); + } + + public void onProcessed(int fingerprintId) { + mHandler.obtainMessage(MSG_PROCESSED, fingerprintId, 0).sendToTarget(); } public void onError(int error) { @@ -151,12 +146,14 @@ public class FingerprintManager { */ public void enroll(long timeout) { if (mServiceReceiver == null) { - throw new IllegalStateException("enroll: Call registerCallback() first"); + sendError(FINGERPRINT_ERROR_NO_RECEIVER, 0, 0); + return; } if (mService != null) try { - mService.enroll(timeout, getCurrentUserId()); + mService.enroll(mToken, timeout, getCurrentUserId()); } catch (RemoteException e) { Log.v(TAG, "Remote exception while enrolling: ", e); + sendError(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0, 0); } } @@ -166,10 +163,19 @@ public class FingerprintManager { * @param fingerprintId */ public void remove(int fingerprintId) { - if (mService != null) try { - mService.remove(fingerprintId, getCurrentUserId()); - } catch (RemoteException e) { - Log.v(TAG, "Remote exception during remove of fingerprintId: " + fingerprintId, e); + if (mServiceReceiver == null) { + sendError(FINGERPRINT_ERROR_NO_RECEIVER, 0, 0); + return; + } + if (mService != null) { + try { + mService.remove(mToken, fingerprintId, getCurrentUserId()); + } catch (RemoteException e) { + Log.v(TAG, "Remote exception during remove of fingerprintId: " + fingerprintId, e); + } + } else { + Log.w(TAG, "remove(): Service not connected!"); + sendError(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0, 0); } } @@ -181,10 +187,13 @@ public class FingerprintManager { mClientReceiver = receiver; if (mService != null) { try { - mService.startListening(mServiceReceiver, getCurrentUserId()); + mService.startListening(mToken, mServiceReceiver, getCurrentUserId()); } catch (RemoteException e) { Log.v(TAG, "Remote exception in startListening(): ", e); } + } else { + Log.w(TAG, "startListening(): Service not connected!"); + sendError(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0, 0); } } @@ -201,15 +210,38 @@ public class FingerprintManager { * Stops the client from listening to fingerprint events. */ public void stopListening() { - mClientReceiver = null; if (mService != null) { try { - mService.stopListening(getCurrentUserId()); + mService.stopListening(mToken, getCurrentUserId()); + mClientReceiver = null; } catch (RemoteException e) { Log.v(TAG, "Remote exception in stopListening(): ", e); } } else { Log.w(TAG, "stopListening(): Service not connected!"); + sendError(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0, 0); + } + } + + public void enrollCancel() { + if (mServiceReceiver == null) { + sendError(FINGERPRINT_ERROR_NO_RECEIVER, 0, 0); + return; } + if (mService != null) { + try { + mService.enrollCancel(mToken, getCurrentUserId()); + mClientReceiver = null; + } catch (RemoteException e) { + Log.v(TAG, "Remote exception in enrollCancel(): ", e); + sendError(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0, 0); + } + } else { + Log.w(TAG, "enrollCancel(): Service not connected!"); + } + } + + private void sendError(int msg, int arg1, int arg2) { + mHandler.obtainMessage(msg, arg1, arg2); } }
\ No newline at end of file diff --git a/core/java/android/service/fingerprint/FingerprintManagerReceiver.java b/core/java/android/service/fingerprint/FingerprintManagerReceiver.java index 34f1655..e5193f5 100644 --- a/core/java/android/service/fingerprint/FingerprintManagerReceiver.java +++ b/core/java/android/service/fingerprint/FingerprintManagerReceiver.java @@ -30,18 +30,32 @@ public class FingerprintManagerReceiver { public void onEnrollResult(int fingerprintId, int remaining) { } /** - * Fingerprint scan detected. Most clients will use this function to detect a fingerprint + * Fingerprint touch detected, but not processed yet. Clients will use this message to + * determine a good or bad scan before the fingerprint is processed. This is meant for the + * client to provide feedback about the scan or alert the user that recognition is to follow. * - * @param fingerprintId is the finger the hardware has detected. - * @param confidence from 0 (no confidence) to 65535 (high confidence). Fingerprint 0 has - * special meaning - the finger wasn't recognized. + * @param acquiredInfo one of: + * {@link FingerprintManager#FINGERPRINT_ACQUIRED_GOOD}, + * {@link FingerprintManager#FINGERPRINT_ACQUIRED_PARTIAL}, + * {@link FingerprintManager#FINGERPRINT_ACQUIRED_INSUFFICIENT}, + * {@link FingerprintManager#FINGERPRINT_ACQUIRED_IMAGER_DIRTY}, + * {@link FingerprintManager#FINGERPRINT_ACQUIRED_TOO_SLOW}, + * {@link FingerprintManager#FINGERPRINT_ACQUIRED_TOO_FAST} */ - public void onScanned(int fingerprintId, int confidence) { } + public void onAcquired(int acquiredInfo) { } + + /** + * Fingerprint has been detected and processed. A non-zero return indicates a valid + * fingerprint was detected. + * + * @param fingerprintId the finger id, or 0 if not recognized. + */ + public void onProcessed(int fingerprintId) { } /** * An error was detected during scan or enrollment. One of * {@link FingerprintManager#FINGERPRINT_ERROR_HW_UNAVAILABLE}, - * {@link FingerprintManager#FINGERPRINT_ERROR_BAD_CAPTURE} or + * {@link FingerprintManager#FINGERPRINT_ERROR_UNABLE_TO_PROCESS} or * {@link FingerprintManager#FINGERPRINT_ERROR_TIMEOUT} * {@link FingerprintManager#FINGERPRINT_ERROR_NO_SPACE} * diff --git a/core/java/android/service/fingerprint/FingerprintService.java b/core/java/android/service/fingerprint/FingerprintService.java deleted file mode 100644 index c7fa7cd..0000000 --- a/core/java/android/service/fingerprint/FingerprintService.java +++ /dev/null @@ -1,219 +0,0 @@ -/** - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.service.fingerprint; - -import android.app.Service; -import android.content.ContentResolver; -import android.content.Intent; -import android.os.Handler; -import android.os.IBinder; -import android.os.RemoteException; -import android.provider.Settings; -import android.util.Slog; - -import java.io.PrintWriter; -import java.util.HashMap; - -/** - * A service to manage multiple clients that want to access the fingerprint HAL API. - * The service is responsible for maintaining a list of clients and dispatching all - * fingerprint -related events. - * - * @hide - */ -public class FingerprintService extends Service { - private final String TAG = FingerprintService.class.getSimpleName() + - "[" + getClass().getSimpleName() + "]"; - private static final boolean DEBUG = true; - HashMap<IFingerprintServiceReceiver, ClientData> mClients = - new HashMap<IFingerprintServiceReceiver, ClientData>(); - - private static final int MSG_NOTIFY = 10; - - Handler mHandler = new Handler() { - public void handleMessage(android.os.Message msg) { - switch (msg.what) { - case MSG_NOTIFY: - handleNotify(msg.arg1, msg.arg2, (Integer) msg.obj); - break; - - default: - Slog.w(TAG, "Unknown message:" + msg.what); - } - } - }; - - private static final int STATE_IDLE = 0; - private static final int STATE_LISTENING = 1; - private static final int STATE_ENROLLING = 2; - private static final int STATE_DELETING = 3; - private static final long MS_PER_SEC = 1000; - - private static final class ClientData { - public IFingerprintServiceReceiver receiver; - int state; - int userId; - } - - @Override - public final IBinder onBind(Intent intent) { - if (DEBUG) Slog.v(TAG, "onBind() intent = " + intent); - return new FingerprintServiceWrapper(); - } - - // JNI methods to communicate from FingerprintManagerService to HAL - native int nativeEnroll(int timeout); - native int nativeRemove(int fingerprintId); - - // JNI methods for communicating from HAL to clients - void notify(int msg, int arg1, int arg2) { - mHandler.obtainMessage(MSG_NOTIFY, msg, arg1, arg2).sendToTarget(); - } - - void handleNotify(int msg, int arg1, int arg2) { - for (int i = 0; i < mClients.size(); i++) { - ClientData clientData = mClients.get(i); - switch (msg) { - case FingerprintManager.FINGERPRINT_ERROR: { - if (clientData.state != STATE_IDLE) { - // FINGERPRINT_ERROR_HW_UNAVAILABLE - // FINGERPRINT_ERROR_BAD_CAPTURE - // FINGERPRINT_ERROR_TIMEOUT - // FINGERPRINT_ERROR_NO_SPACE - final int error = arg1; - clientData.state = STATE_IDLE; - if (clientData.receiver != null) { - try { - clientData.receiver.onError(error); - } catch (RemoteException e) { - Slog.e(TAG, "can't send message to client. Did it die?", e); - } - } - } - } - break; - case FingerprintManager.FINGERPRINT_SCANNED: { - final int fingerId = arg1; - final int confidence = arg2; - if (clientData.state == STATE_LISTENING && clientData.receiver != null) { - try { - clientData.receiver.onScanned(fingerId, confidence); - } catch (RemoteException e) { - Slog.e(TAG, "can't send message to client. Did it die?", e); - } - } - break; - } - case FingerprintManager.FINGERPRINT_TEMPLATE_ENROLLING: { - if (clientData.state == STATE_ENROLLING) { - final int fingerId = arg1; - final int remaining = arg2; - if (remaining == 0) { - FingerprintUtils.addFingerprintIdForUser(fingerId, - getContentResolver(), clientData.userId); - clientData.state = STATE_IDLE; // Nothing left to do - } - if (clientData.receiver != null) { - try { - clientData.receiver.onEnrollResult(fingerId, remaining); - } catch (RemoteException e) { - Slog.e(TAG, "can't send message to client. Did it die?", e); - } - } - } - break; - } - case FingerprintManager.FINGERPRINT_TEMPLATE_REMOVED: { - int fingerId = arg1; - if (fingerId == 0) throw new IllegalStateException("Got illegal id from HAL"); - if (clientData.state == STATE_DELETING) { - FingerprintUtils.removeFingerprintIdForUser(fingerId, getContentResolver(), - clientData.userId); - if (clientData.receiver != null) { - try { - clientData.receiver.onRemoved(fingerId); - } catch (RemoteException e) { - Slog.e(TAG, "can't send message to client. Did it die?", e); - } - } - } - } - break; - } - } - } - - int enroll(IFingerprintServiceReceiver receiver, long timeout, int userId) { - ClientData clientData = mClients.get(receiver); - if (clientData != null) { - if (clientData.userId != userId) throw new IllegalStateException("Bad user"); - clientData.state = STATE_ENROLLING; - return nativeEnroll((int) (timeout / MS_PER_SEC)); - } - return -1; - } - - int remove(IFingerprintServiceReceiver receiver, int fingerId, int userId) { - ClientData clientData = mClients.get(receiver); - if (clientData != null) { - if (clientData.userId != userId) throw new IllegalStateException("Bad user"); - clientData.state = STATE_DELETING; - // The fingerprint id will be removed when we get confirmation from the HAL - return nativeRemove(fingerId); - } - return -1; - } - - void startListening(IFingerprintServiceReceiver receiver, int userId) { - ClientData clientData = new ClientData(); - clientData.state = STATE_LISTENING; - clientData.receiver = receiver; - clientData.userId = userId; - mClients.put(receiver, clientData); - } - - void stopListening(IFingerprintServiceReceiver receiver, int userId) { - ClientData clientData = mClients.get(receiver); - if (clientData != null) { - clientData.state = STATE_IDLE; - clientData.userId = -1; - clientData.receiver = null; - } - mClients.remove(receiver); - } - - private final class FingerprintServiceWrapper extends IFingerprintService.Stub { - IFingerprintServiceReceiver mReceiver; - public int enroll(long timeout, int userId) { - return mReceiver != null ? FingerprintService.this.enroll(mReceiver, timeout, userId) - : FingerprintManager.FINGERPRINT_ERROR_NO_RECEIVER; - } - - public int remove(int fingerprintId, int userId) { - return FingerprintService.this.remove(mReceiver, fingerprintId, userId); - } - - public void startListening(IFingerprintServiceReceiver receiver, int userId) { - mReceiver = receiver; - FingerprintService.this.startListening(receiver, userId); - } - - public void stopListening(int userId) { - FingerprintService.this.stopListening(mReceiver, userId); - } - } -} diff --git a/core/java/android/service/fingerprint/FingerprintUtils.java b/core/java/android/service/fingerprint/FingerprintUtils.java index 81a2aac..f4b5526 100644 --- a/core/java/android/service/fingerprint/FingerprintUtils.java +++ b/core/java/android/service/fingerprint/FingerprintUtils.java @@ -18,10 +18,12 @@ package android.service.fingerprint; import android.content.ContentResolver; import android.provider.Settings; +import android.text.TextUtils; import android.util.Log; import java.util.Arrays; +public class FingerprintUtils { private static final boolean DEBUG = true; private static final String TAG = "FingerprintUtils"; @@ -30,13 +32,16 @@ class FingerprintUtils { String fingerIdsRaw = Settings.Secure.getStringForUser(res, Settings.Secure.USER_FINGERPRINT_IDS, userId); - String[] fingerStringIds = fingerIdsRaw.replace("[","").replace("]","").split(", "); - int result[] = new int[fingerStringIds.length]; - for (int i = 0; i < result.length; i++) { - try { - result[i] = Integer.decode(fingerStringIds[i]); - } catch (NumberFormatException e) { - if (DEBUG) Log.d(TAG, "Error when parsing finger id " + fingerStringIds[i]); + int result[] = {}; + if (!TextUtils.isEmpty(fingerIdsRaw)) { + String[] fingerStringIds = fingerIdsRaw.replace("[","").replace("]","").split(", "); + result = new int[fingerStringIds.length]; + for (int i = 0; i < result.length; i++) { + try { + result[i] = Integer.decode(fingerStringIds[i]); + } catch (NumberFormatException e) { + if (DEBUG) Log.d(TAG, "Error when parsing finger id " + fingerStringIds[i]); + } } } return result; diff --git a/core/java/android/service/fingerprint/IFingerprintService.aidl b/core/java/android/service/fingerprint/IFingerprintService.aidl index e92c20c..43d5e9a 100644 --- a/core/java/android/service/fingerprint/IFingerprintService.aidl +++ b/core/java/android/service/fingerprint/IFingerprintService.aidl @@ -22,17 +22,20 @@ import android.service.fingerprint.IFingerprintServiceReceiver; * Communication channel from client to the fingerprint service. * @hide */ -interface IFingerprintService { - // Returns 0 if successfully started, -1 otherwise - int enroll(long timeout, int userId); +oneway interface IFingerprintService { + // Any errors resulting from this call will be returned to the listener + void enroll(IBinder token, long timeout, int userId); + + // Any errors resulting from this call will be returned to the listener + void enrollCancel(IBinder token, int userId); - // Returns 0 if fingerprintId's template can be removed, -1 otherwise - int remove(int fingerprintId, int userId); + // Any errors resulting from this call will be returned to the listener + void remove(IBinder token, int fingerprintId, int userId); // Start listening for fingerprint events. This has the side effect of starting // the hardware if not already started. - oneway void startListening(IFingerprintServiceReceiver receiver, int userId); + void startListening(IBinder token, IFingerprintServiceReceiver receiver, int userId); // Stops listening for fingerprints - oneway void stopListening(int userId); + void stopListening(IBinder token, int userId); } diff --git a/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl index 4826b59..af4128f 100644 --- a/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl +++ b/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl @@ -24,7 +24,8 @@ import android.os.UserHandle; */ oneway interface IFingerprintServiceReceiver { void onEnrollResult(int fingerprintId, int remaining); - void onScanned(int fingerprintId, int confidence); + void onAcquired(int acquiredInfo); + void onProcessed(int fingerprintId); void onError(int error); void onRemoved(int fingerprintId); } diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java index 0a4f641..508769d 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -206,6 +206,10 @@ public abstract class Transition implements Cloneable { // like CircularPropagation EpicenterCallback mEpicenterCallback; + // For Fragment shared element transitions, linking views explicitly by mismatching + // viewNames. + ArrayMap<String, String> mNameOverrides; + /** * Constructs a Transition object with no target objects. A transition with * no targets defaults to running on all target objects in the scene hierarchy @@ -1347,6 +1351,21 @@ public abstract class Transition implements Cloneable { } else { captureHierarchy(sceneRoot, start); } + if (!start && mNameOverrides != null) { + int numOverrides = mNameOverrides.size(); + ArrayList<View> overriddenViews = new ArrayList<View>(numOverrides); + for (int i = 0; i < numOverrides; i++) { + String fromName = mNameOverrides.keyAt(i); + overriddenViews.add(mStartValues.nameValues.remove(fromName)); + } + for (int i = 0; i < numOverrides; i++) { + View view = overriddenViews.get(i); + if (view != null) { + String toName = mNameOverrides.valueAt(i); + mStartValues.nameValues.put(toName, view); + } + } + } } static void addViewValues(TransitionValuesMaps transitionValuesMaps, @@ -1400,10 +1419,12 @@ public abstract class Transition implements Cloneable { mStartValues.viewValues.clear(); mStartValues.idValues.clear(); mStartValues.itemIdValues.clear(); + mStartValues.nameValues.clear(); } else { mEndValues.viewValues.clear(); mEndValues.idValues.clear(); mEndValues.itemIdValues.clear(); + mEndValues.nameValues.clear(); } } @@ -1866,6 +1887,20 @@ public abstract class Transition implements Cloneable { return mCanRemoveViews; } + /** + * Sets the shared element names -- a mapping from a name at the start state to + * a different name at the end state. + * @hide + */ + public void setNameOverrides(ArrayMap<String, String> overrides) { + mNameOverrides = overrides; + } + + /** @hide */ + public ArrayMap<String, String> getNameOverrides() { + return mNameOverrides; + } + @Override public String toString() { return toString(""); diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java index 698b563..c04be4f 100644 --- a/core/java/android/transition/TransitionSet.java +++ b/core/java/android/transition/TransitionSet.java @@ -168,30 +168,106 @@ public class TransitionSet extends Transition { @Override public TransitionSet addTarget(View target) { + for (int i = 0; i < mTransitions.size(); i++) { + mTransitions.get(i).addTarget(target); + } return (TransitionSet) super.addTarget(target); } @Override public TransitionSet addTarget(int targetId) { + for (int i = 0; i < mTransitions.size(); i++) { + mTransitions.get(i).addTarget(targetId); + } return (TransitionSet) super.addTarget(targetId); } @Override + public TransitionSet addTarget(String targetName) { + for (int i = 0; i < mTransitions.size(); i++) { + mTransitions.get(i).addTarget(targetName); + } + return (TransitionSet) super.addTarget(targetName); + } + + @Override + public TransitionSet addTarget(Class targetType) { + for (int i = 0; i < mTransitions.size(); i++) { + mTransitions.get(i).addTarget(targetType); + } + return (TransitionSet) super.addTarget(targetType); + } + + @Override public TransitionSet addListener(TransitionListener listener) { return (TransitionSet) super.addListener(listener); } @Override public TransitionSet removeTarget(int targetId) { + for (int i = 0; i < mTransitions.size(); i++) { + mTransitions.get(i).removeTarget(targetId); + } return (TransitionSet) super.removeTarget(targetId); } @Override public TransitionSet removeTarget(View target) { + for (int i = 0; i < mTransitions.size(); i++) { + mTransitions.get(i).removeTarget(target); + } + return (TransitionSet) super.removeTarget(target); + } + + @Override + public TransitionSet removeTarget(Class target) { + for (int i = 0; i < mTransitions.size(); i++) { + mTransitions.get(i).removeTarget(target); + } + return (TransitionSet) super.removeTarget(target); + } + + @Override + public TransitionSet removeTarget(String target) { + for (int i = 0; i < mTransitions.size(); i++) { + mTransitions.get(i).removeTarget(target); + } return (TransitionSet) super.removeTarget(target); } @Override + public Transition excludeTarget(View target, boolean exclude) { + for (int i = 0; i < mTransitions.size(); i++) { + mTransitions.get(i).excludeTarget(target, exclude); + } + return super.excludeTarget(target, exclude); + } + + @Override + public Transition excludeTarget(String targetViewName, boolean exclude) { + for (int i = 0; i < mTransitions.size(); i++) { + mTransitions.get(i).excludeTarget(targetViewName, exclude); + } + return super.excludeTarget(targetViewName, exclude); + } + + @Override + public Transition excludeTarget(int targetId, boolean exclude) { + for (int i = 0; i < mTransitions.size(); i++) { + mTransitions.get(i).excludeTarget(targetId, exclude); + } + return super.excludeTarget(targetId, exclude); + } + + @Override + public Transition excludeTarget(Class type, boolean exclude) { + for (int i = 0; i < mTransitions.size(); i++) { + mTransitions.get(i).excludeTarget(type, exclude); + } + return super.excludeTarget(type, exclude); + } + + @Override public TransitionSet removeListener(TransitionListener listener) { return (TransitionSet) super.removeListener(listener); } diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java index 0f7638b..c6c8337 100644 --- a/core/java/android/transition/Visibility.java +++ b/core/java/android/transition/Visibility.java @@ -18,6 +18,9 @@ package android.transition; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; import android.view.View; import android.view.ViewGroup; @@ -272,15 +275,23 @@ public abstract class Visibility extends Transition { if (startView.getParent() == null) { // no parent - safe to use overlayView = startView; - } else if (startView.getParent() instanceof View && - startView.getParent().getParent() == null) { + } else if (startView.getParent() instanceof View) { View startParent = (View) startView.getParent(); - int id = startParent.getId(); - if (id != View.NO_ID && sceneRoot.findViewById(id) != null && mCanRemoveViews) { - // no parent, but its parent is unparented but the parent - // hierarchy has been replaced by a new hierarchy with the same id - // and it is safe to un-parent startView - overlayView = startView; + if (!isValidTarget(startParent)) { + if (startView.isAttachedToWindow()) { + overlayView = copyViewImage(startView); + } else { + overlayView = startView; + } + } else if (startParent.getParent() == null) { + int id = startParent.getId(); + if (id != View.NO_ID && sceneRoot.findViewById(id) != null + && mCanRemoveViews) { + // no parent, but its parent is unparented but the parent + // hierarchy has been replaced by a new hierarchy with the same id + // and it is safe to un-parent startView + overlayView = startView; + } } } } @@ -378,6 +389,26 @@ public abstract class Visibility extends Transition { return null; } + private View copyViewImage(View view) { + int width = view.getWidth(); + int height = view.getHeight(); + if (width <= 0 || height <= 0) { + return null; + } + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + view.draw(canvas); + final BitmapDrawable drawable = new BitmapDrawable(bitmap); + + View overlayView = new View(view.getContext()); + overlayView.setBackground(drawable); + int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); + int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY); + overlayView.measure(widthSpec, heightSpec); + overlayView.layout(0, 0, width, height); + return overlayView; + } + /** * The default implementation of this method returns a null Animator. Subclasses should * override this method to make targets disappear with the desired transition. The diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index 832d67a..446bbc9 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -404,6 +404,9 @@ class GLES20Canvas extends HardwareCanvas { @Override public void scale(float sx, float sy) { + // TODO: remove + if (sx > 1000000 || sy > 1000000) throw new IllegalArgumentException("invalid scales passed " + sx + ", " + sy); + nScale(mRenderer, sx, sy); } @@ -544,9 +547,9 @@ class GLES20Canvas extends HardwareCanvas { /////////////////////////////////////////////////////////////////////////// @Override - public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, - Paint paint) { - nDrawArc(mRenderer, oval.left, oval.top, oval.right, oval.bottom, + public void drawArc(float left, float top, float right, float bottom, + float startAngle, float sweepAngle, boolean useCenter, Paint paint) { + nDrawArc(mRenderer, left, top, right, bottom, startAngle, sweepAngle, useCenter, paint.mNativePaint); } @@ -771,8 +774,8 @@ class GLES20Canvas extends HardwareCanvas { } @Override - public void drawOval(RectF oval, Paint paint) { - nDrawOval(mRenderer, oval.left, oval.top, oval.right, oval.bottom, paint.mNativePaint); + public void drawOval(float left, float top, float right, float bottom, Paint paint) { + nDrawOval(mRenderer, left, top, right, bottom, paint.mNativePaint); } private static native void nDrawOval(long renderer, float left, float top, diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index dfd5cdf..82e5ddd 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -656,7 +656,7 @@ public final class ViewRootImpl implements ViewParent, } public boolean invokeFunctor(long functor, boolean waitForCompletion) { - if (mAttachInfo.mHardwareRenderer == null || !mAttachInfo.mHardwareRenderer.isEnabled()) { + if (mAttachInfo.mHardwareRenderer == null) { return false; } mAttachInfo.mHardwareRenderer.invokeFunctor(functor, waitForCompletion); diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java index abed082..321d9d3 100644 --- a/core/java/android/webkit/CookieManager.java +++ b/core/java/android/webkit/CookieManager.java @@ -17,6 +17,7 @@ package android.webkit; import android.net.WebAddress; +import android.webkit.ValueCallback; /** * Manages the cookies used by an application's {@link WebView} instances. @@ -72,7 +73,7 @@ public class CookieManager { * path and name will be replaced with the new cookie. The cookie being set * will be ignored if it is expired. * - * @param url the URL for which the cookie is set + * @param url the URL for which the cookie is to be set * @param value the cookie as a string, using the format of the 'Set-Cookie' * HTTP response header */ @@ -81,6 +82,29 @@ public class CookieManager { } /** + * Sets a cookie for the given URL. Any existing cookie with the same host, + * path and name will be replaced with the new cookie. The cookie being set + * will be ignored if it is expired. + * <p> + * This method is asynchronous. + * If a {@link ValueCallback} is provided, + * {@link ValueCallback#onReceiveValue(T) onReceiveValue()} will be called on the current + * thread's {@link android.os.Looper} once the operation is complete. + * The value provided to the callback indicates whether the cookie was set successfully. + * You can pass {@code null} as the callback if you don't need to know when the operation + * completes or whether it succeeded, and in this case it is safe to call the method from a + * thread without a Looper. + * + * @param url the URL for which the cookie is to be set + * @param value the cookie as a string, using the format of the 'Set-Cookie' + * HTTP response header + * @param callback a callback to be executed when the cookie has been set + */ + public void setCookie(String url, String value, ValueCallback<Boolean> callback) { + throw new MustOverrideException(); + } + + /** * Gets the cookies for the given URL. * * @param url the URL for which the cookies are requested @@ -120,19 +144,57 @@ public class CookieManager { /** * Removes all session cookies, which are cookies without an expiration * date. + * @deprecated use {@link #removeSessionCookies(ValueCallback)} instead. */ public void removeSessionCookie() { throw new MustOverrideException(); } /** + * Removes all session cookies, which are cookies without an expiration + * date. + * <p> + * This method is asynchronous. + * If a {@link ValueCallback} is provided, + * {@link ValueCallback#onReceiveValue(T) onReceiveValue()} will be called on the current + * thread's {@link android.os.Looper} once the operation is complete. + * The value provided to the callback indicates whether any cookies were removed. + * You can pass {@code null} as the callback if you don't need to know when the operation + * completes or whether any cookie were removed, and in this case it is safe to call the + * method from a thread without a Looper. + * @param callback a callback which is executed when the session cookies have been removed + */ + public void removeSessionCookies(ValueCallback<Boolean> callback) { + throw new MustOverrideException(); + } + + /** * Removes all cookies. + * @deprecated Use {@link #removeAllCookies(ValueCallback)} instead. */ + @Deprecated public void removeAllCookie() { throw new MustOverrideException(); } /** + * Removes all cookies. + * <p> + * This method is asynchronous. + * If a {@link ValueCallback} is provided, + * {@link ValueCallback#onReceiveValue(T) onReceiveValue()} will be called on the current + * thread's {@link android.os.Looper} once the operation is complete. + * The value provided to the callback indicates whether any cookies were removed. + * You can pass {@code null} as the callback if you don't need to know when the operation + * completes or whether any cookies were removed, and in this case it is safe to call the + * method from a thread without a Looper. + * @param callback a callback which is executed when the cookies have been removed + */ + public void removeAllCookies(ValueCallback<Boolean> callback) { + throw new MustOverrideException(); + } + + /** * Gets whether there are stored cookies. * * @return true if there are stored cookies @@ -153,7 +215,9 @@ public class CookieManager { /** * Removes all expired cookies. + * @deprecated The WebView handles removing expired cookies automatically. */ + @Deprecated public void removeExpiredCookie() { throw new MustOverrideException(); } |
