diff options
Diffstat (limited to 'core/java')
105 files changed, 8359 insertions, 1793 deletions
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java index 9818c33..04f62e3 100644 --- a/core/java/android/app/ActionBar.java +++ b/core/java/android/app/ActionBar.java @@ -949,10 +949,6 @@ public abstract class ActionBar { } /** @hide */ - public void captureSharedElements(Map<String, View> sharedElements) { - } - - /** @hide */ public ActionMode startActionMode(ActionMode.Callback callback) { return null; } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index b18eb98..599a608 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -18,7 +18,6 @@ package android.app; import android.annotation.NonNull; import android.transition.Scene; -import android.transition.Transition; import android.transition.TransitionManager; import android.util.ArrayMap; import android.util.SuperNotCalledException; @@ -773,6 +772,8 @@ public class Activity extends ContextThemeWrapper private Thread mUiThread; final Handler mHandler = new Handler(); + private ActivityOptions mCalledActivityOptions; + private EnterTransitionCoordinator mEnterTransitionCoordinator; /** Return the intent that started this activity. */ public Intent getIntent() { @@ -1026,6 +1027,9 @@ public class Activity extends ContextThemeWrapper mTitleReady = true; onTitleChanged(getTitle(), getTitleColor()); } + if (mEnterTransitionCoordinator != null) { + mEnterTransitionCoordinator.readyToEnter(); + } mCalled = true; } @@ -1106,6 +1110,7 @@ public class Activity extends ContextThemeWrapper protected void onResume() { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this); getApplication().dispatchActivityResumed(this); + mCalledActivityOptions = null; mCalled = true; } @@ -1398,8 +1403,9 @@ public class Activity extends ContextThemeWrapper protected void onStop() { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this); if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false); - if (mWindow != null) { - mWindow.restoreViewVisibilityAfterTransitionToCallee(); + if (mCalledActivityOptions != null) { + mCalledActivityOptions.dispatchActivityStopped(); + mCalledActivityOptions = null; } getApplication().dispatchActivityStopped(this); mTranslucentCallback = null; @@ -3484,7 +3490,7 @@ public class Activity extends ContextThemeWrapper public void startActivityForResult(Intent intent, int requestCode) { Bundle options = null; if (mWindow.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) { - options = ActivityOptions.makeSceneTransitionAnimation().toBundle(); + options = ActivityOptions.makeSceneTransitionAnimation(mWindow, null).toBundle(); } startActivityForResult(intent, requestCode, options); } @@ -3526,14 +3532,8 @@ public class Activity extends ContextThemeWrapper public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) { if (options != null) { ActivityOptions activityOptions = new ActivityOptions(options); - if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) { - if (mActionBar != null) { - ArrayMap<String, View> sharedElementMap = new ArrayMap<String, View>(); - mActionBar.captureSharedElements(sharedElementMap); - activityOptions.addSharedElements(sharedElementMap); - } - options = mWindow.startExitTransitionToCallee(options); - } + activityOptions.dispatchStartExit(); + mCalledActivityOptions = activityOptions; } if (mParent == null) { Instrumentation.ActivityResult ar = @@ -4314,11 +4314,10 @@ public class Activity extends ContextThemeWrapper } /** - * Call this when your activity is done and should be closed. The - * ActivityResult is propagated back to whoever launched you via - * onActivityResult(). + * Finishes the current activity and specifies whether to remove the task associated with this + * activity. */ - public void finish() { + private void finish(boolean finishTask) { if (mParent == null) { int resultCode; Intent resultData; @@ -4332,7 +4331,7 @@ public class Activity extends ContextThemeWrapper resultData.prepareToLeaveProcess(); } if (ActivityManagerNative.getDefault() - .finishActivity(mToken, resultCode, resultData)) { + .finishActivity(mToken, resultCode, resultData, finishTask)) { mFinished = true; } } catch (RemoteException e) { @@ -4344,6 +4343,15 @@ public class Activity extends ContextThemeWrapper } /** + * Call this when your activity is done and should be closed. The + * ActivityResult is propagated back to whoever launched you via + * onActivityResult(). + */ + public void finish() { + finish(false); + } + + /** * Finish this activity as well as all activities immediately below it * in the current task that have the same affinity. This is typically * used when an application can be launched on to another task (such as @@ -4391,16 +4399,15 @@ public class Activity extends ContextThemeWrapper * to reverse its exit Transition. When the exit Transition completes, * {@link #finish()} is called. If no entry Transition was used, finish() is called * immediately and the Activity exit Transition is run. - * @see android.view.Window#setTriggerEarlySceneTransition(boolean, boolean) - * @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.View, String) + * @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.Window, + * android.app.ActivityOptions.ActivityTransitionListener) */ public void finishWithTransition() { - mWindow.startExitTransitionToCaller(new Runnable() { - @Override - public void run() { - finish(); - } - }); + if (mEnterTransitionCoordinator != null) { + mEnterTransitionCoordinator.startExit(); + } else { + finish(); + } } /** @@ -4443,6 +4450,14 @@ public class Activity extends ContextThemeWrapper } /** + * Call this when your activity is done and should be closed and the task should be completely + * removed as a part of finishing the Activity. + */ + public void finishAndRemoveTask() { + finish(true); + } + + /** * Called when an activity you launched exits, giving you the requestCode * you started it with, the resultCode it returned, and any additional * data from it. The <var>resultCode</var> will be @@ -5346,6 +5361,21 @@ public class Activity extends ContextThemeWrapper } } + /** + * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.Window, + * android.app.ActivityOptions.ActivityTransitionListener)} was used to start an Activity, + * the Window will be triggered to enter with a Transition. <code>listener</code> allows + * The Activity to listen to events of the entering transition and control the mapping of + * shared elements. This requires {@link Window#FEATURE_CONTENT_TRANSITIONS}. + * + * @param listener Used to listen to events in the entering transition. + */ + public void setActivityTransitionListener(ActivityOptions.ActivityTransitionListener listener) { + if (mEnterTransitionCoordinator != null) { + mEnterTransitionCoordinator.setActivityTransitionListener(listener); + } + } + // ------------------ Internal API ------------------ final void setParent(Activity parent) { @@ -5413,34 +5443,12 @@ public class Activity extends ContextThemeWrapper } mWindowManager = mWindow.getWindowManager(); mCurrentConfig = config; - Window.SceneTransitionListener sceneTransitionListener - = new Window.SceneTransitionListener() { - @Override - public void nullPendingTransition() { - overridePendingTransition(0, 0); - } - - @Override - public void convertFromTranslucent() { - Activity.this.convertFromTranslucent(); - } - - @Override - public void convertToTranslucent() { - Activity.this.convertToTranslucent(null); - } - - @Override - public void sharedElementStart(Transition transition) { - Activity.this.onCaptureSharedElementStart(transition); - } - - @Override - public void sharedElementEnd() { - Activity.this.onCaptureSharedElementEnd(); + if (options != null) { + ActivityOptions activityOptions = new ActivityOptions(options); + if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) { + mEnterTransitionCoordinator = activityOptions.createEnterActivityTransition(this); } - }; - mWindow.setTransitionOptions(options, sceneTransitionListener); + } } /** @hide */ @@ -5628,26 +5636,6 @@ public class Activity extends ContextThemeWrapper } /** - * Called when setting up Activity Scene transitions when the start state for shared - * elements has been captured. Override this method to modify the start position of shared - * elements for the entry Transition. - * - * @param transition The <code>Transition</code> being used to change - * bounds of shared elements in the source Activity to - * the bounds defined by the entering Scene. - */ - public void onCaptureSharedElementStart(Transition transition) { - } - - /** - * Called when setting up Activity Scene transitions when the final state for - * shared elements state has been captured. Override this method to modify the destination - * position of shared elements for the entry Transition. - */ - public void onCaptureSharedElementEnd() { - } - - /** * @hide */ public final boolean isResumed() { diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 9f1b56e..10831f2 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -263,7 +263,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM if (data.readInt() != 0) { resultData = Intent.CREATOR.createFromParcel(data); } - boolean res = finishActivity(token, resultCode, resultData); + boolean finishTask = (data.readInt() != 0); + boolean res = finishActivity(token, resultCode, resultData, finishTask); reply.writeNoException(); reply.writeInt(res ? 1 : 0); return true; @@ -2029,7 +2030,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM data.enforceInterface(IActivityManager.descriptor); IBinder parentActivityToken = data.readStrongBinder(); IActivityContainerCallback callback = - (IActivityContainerCallback) data.readStrongBinder(); + IActivityContainerCallback.Stub.asInterface(data.readStrongBinder()); IActivityContainer activityContainer = createActivityContainer(parentActivityToken, callback); reply.writeNoException(); @@ -2342,7 +2343,7 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); return result != 0; } - public boolean finishActivity(IBinder token, int resultCode, Intent resultData) + public boolean finishActivity(IBinder token, int resultCode, Intent resultData, boolean finishTask) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -2355,6 +2356,7 @@ class ActivityManagerProxy implements IActivityManager } else { data.writeInt(0); } + data.writeInt(finishTask ? 1 : 0); mRemote.transact(FINISH_ACTIVITY_TRANSACTION, data, reply, 0); reply.readException(); boolean res = reply.readInt() != 0; @@ -4744,7 +4746,7 @@ class ActivityManagerProxy implements IActivityManager Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(parentActivityToken); - data.writeStrongBinder((IBinder)callback); + data.writeStrongBinder(callback == null ? null : callback.asBinder()); mRemote.transact(CREATE_ACTIVITY_CONTAINER_TRANSACTION, data, reply, 0); reply.readException(); final int result = reply.readInt(); diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 4384580..85464c47 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -20,16 +20,16 @@ import android.content.Context; import android.graphics.Bitmap; import android.os.Bundle; import android.os.Handler; -import android.os.IBinder; import android.os.IRemoteCallback; import android.os.RemoteException; import android.os.ResultReceiver; import android.transition.Transition; -import android.util.Log; +import android.util.ArrayMap; import android.util.Pair; import android.view.View; +import android.view.Window; -import java.util.ArrayList; +import java.util.List; import java.util.Map; /** @@ -108,23 +108,6 @@ public class ActivityOptions { private static final String KEY_TRANSITION_COMPLETE_LISTENER = "android:transitionCompleteListener"; - /** - * For Activity transitions, the called Activity's listener to receive calls - * when transitions complete. - */ - private static final String KEY_TRANSITION_TARGET_LISTENER = "android:transitionTargetListener"; - - /** - * The names of shared elements that are transitioned to the started Activity. - * This is also the name of shared elements that the started Activity accepted. - */ - private static final String KEY_SHARED_ELEMENT_NAMES = "android:shared_element_names"; - - /** - * The shared elements names of the views in the calling Activity. - */ - private static final String KEY_LOCAL_ELEMENT_NAMES = "android:local_element_names"; - /** @hide */ public static final int ANIM_NONE = 0; /** @hide */ @@ -138,11 +121,6 @@ public class ActivityOptions { /** @hide */ public static final int ANIM_SCENE_TRANSITION = 5; - private static final int MSG_SET_LISTENER = 100; - private static final int MSG_HIDE_SHARED_ELEMENTS = 101; - private static final int MSG_PREPARE_RESTORE = 102; - private static final int MSG_RESTORE = 103; - private String mPackageName; private int mAnimationType = ANIM_NONE; private int mCustomEnterResId; @@ -153,9 +131,7 @@ public class ActivityOptions { private int mStartWidth; private int mStartHeight; private IRemoteCallback mAnimationStartedListener; - private ResultReceiver mTransitionCompleteListener; - private ArrayList<String> mSharedElementNames; - private ArrayList<String> mLocalElementNames; + private ResultReceiver mExitReceiver; /** * Create an ActivityOptions specifying a custom animation to run when @@ -231,12 +207,6 @@ public class ActivityOptions { void onAnimationStarted(); } - /** @hide */ - public interface ActivityTransitionTarget { - void sharedElementTransitionComplete(Bundle transitionArgs); - void exitTransitionComplete(); - } - /** * Create an ActivityOptions specifying an animation where the new * activity is scaled from a small originating area of the screen to @@ -357,49 +327,53 @@ public class ActivityOptions { /** * Create an ActivityOptions to transition between Activities using cross-Activity scene * animations. This method carries the position of one shared element to the started Activity. + * The position of <code>sharedElement</code> will be used as the epicenter for the + * exit Transition. The position of the shared element in the launched Activity will be the + * epicenter of its entering Transition. * * <p>This requires {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS} to be * enabled on the calling Activity to cause an exit transition. The same must be in * the called Activity to get an entering transition.</p> + * @param window The window containing shared elements. * @param sharedElement The View to transition to the started Activity. sharedElement must * have a non-null sharedElementName. * @param sharedElementName The shared element name as used in the target Activity. This may * be null if it has the same name as sharedElement. * @return Returns a new ActivityOptions object that you can use to * supply these options as the options Bundle when starting an activity. + * @see android.transition.Transition#setEpicenterCallback( + * android.transition.Transition.EpicenterCallback) */ - public static ActivityOptions makeSceneTransitionAnimation(View sharedElement, - String sharedElementName) { - return makeSceneTransitionAnimation( - new Pair<View, String>(sharedElement, sharedElementName)); + public static ActivityOptions makeSceneTransitionAnimation(Window window, + View sharedElement, String sharedElementName) { + return makeSceneTransitionAnimation(window, + new SharedElementMappingListener(sharedElement, sharedElementName)); } /** * Create an ActivityOptions to transition between Activities using cross-Activity scene * animations. This method carries the position of multiple shared elements to the started - * Activity. + * Activity. The position of the first element in the value returned from + * {@link android.app.ActivityOptions.ActivityTransitionListener#getSharedElementsMapping()} + * will be used as the epicenter for the exit Transition. The position of the associated + * shared element in the launched Activity will be the epicenter of its entering Transition. * * <p>This requires {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS} to be * enabled on the calling Activity to cause an exit transition. The same must be in * the called Activity to get an entering transition.</p> - * @param sharedElements The View to transition to the started Activity along with the - * shared element name as used in the started Activity. The view - * must have a non-null sharedElementName. + * @param window The window containing shared elements. + * @param listener The listener to use to monitor activity transition events. * @return Returns a new ActivityOptions object that you can use to * supply these options as the options Bundle when starting an activity. + * @see android.transition.Transition#setEpicenterCallback( + * android.transition.Transition.EpicenterCallback) */ - public static ActivityOptions makeSceneTransitionAnimation( - Pair<View, String>... sharedElements) { + public static ActivityOptions makeSceneTransitionAnimation(Window window, + ActivityTransitionListener listener) { ActivityOptions opts = new ActivityOptions(); opts.mAnimationType = ANIM_SCENE_TRANSITION; - opts.mSharedElementNames = new ArrayList<String>(); - opts.mLocalElementNames = new ArrayList<String>(); - - if (sharedElements != null) { - for (Pair<View, String> sharedElement : sharedElements) { - opts.addSharedElement(sharedElement.first, sharedElement.second); - } - } + ExitTransitionCoordinator exit = new ExitTransitionCoordinator(window, listener); + opts.mExitReceiver = exit; return opts; } @@ -435,9 +409,7 @@ public class ActivityOptions { break; case ANIM_SCENE_TRANSITION: - mTransitionCompleteListener = opts.getParcelable(KEY_TRANSITION_COMPLETE_LISTENER); - mSharedElementNames = opts.getStringArrayList(KEY_SHARED_ELEMENT_NAMES); - mLocalElementNames = opts.getStringArrayList(KEY_LOCAL_ELEMENT_NAMES); + mExitReceiver = opts.getParcelable(KEY_TRANSITION_COMPLETE_LISTENER); break; } } @@ -493,50 +465,16 @@ public class ActivityOptions { } /** @hide */ - public ArrayList<String> getSharedElementNames() { return mSharedElementNames; } - - /** @hide */ - public ArrayList<String> getLocalElementNames() { return mLocalElementNames; } - - /** @hide */ - public void dispatchSceneTransitionStarted(final ActivityTransitionTarget target, - ArrayList<String> sharedElementNames) { - if (mTransitionCompleteListener != null) { - IRemoteCallback callback = new IRemoteCallback.Stub() { - @Override - public void sendResult(Bundle data) throws RemoteException { - if (data == null) { - target.exitTransitionComplete(); - } else { - target.sharedElementTransitionComplete(data); - } - } - }; - Bundle bundle = new Bundle(); - bundle.putBinder(KEY_TRANSITION_TARGET_LISTENER, callback.asBinder()); - bundle.putStringArrayList(KEY_SHARED_ELEMENT_NAMES, sharedElementNames); - mTransitionCompleteListener.send(MSG_SET_LISTENER, bundle); - } - } - - /** @hide */ - public void dispatchSharedElementsReady() { - if (mTransitionCompleteListener != null) { - mTransitionCompleteListener.send(MSG_HIDE_SHARED_ELEMENTS, null); - } - } - - /** @hide */ - public void dispatchPrepareRestore() { - if (mTransitionCompleteListener != null) { - mTransitionCompleteListener.send(MSG_PREPARE_RESTORE, null); + public void dispatchActivityStopped() { + if (mExitReceiver != null) { + mExitReceiver.send(ActivityTransitionCoordinator.MSG_ACTIVITY_STOPPED, null); } } /** @hide */ - public void dispatchRestore(Bundle sharedElementsArgs) { - if (mTransitionCompleteListener != null) { - mTransitionCompleteListener.send(MSG_RESTORE, sharedElementsArgs); + public void dispatchStartExit() { + if (mExitReceiver != null) { + mExitReceiver.send(ActivityTransitionCoordinator.MSG_START_EXIT_TRANSITION, null); } } @@ -557,6 +495,15 @@ public class ActivityOptions { } } + /** @hide */ + public EnterTransitionCoordinator createEnterActivityTransition(Activity activity) { + EnterTransitionCoordinator coordinator = null; + if (mAnimationType == ANIM_SCENE_TRANSITION) { + coordinator = new EnterTransitionCoordinator(activity, mExitReceiver); + } + return coordinator; + } + /** * Update the current values in this ActivityOptions from those supplied * in <var>otherOptions</var>. Any values @@ -566,8 +513,7 @@ public class ActivityOptions { if (otherOptions.mPackageName != null) { mPackageName = otherOptions.mPackageName; } - mSharedElementNames = null; - mLocalElementNames = null; + mExitReceiver = null; switch (otherOptions.mAnimationType) { case ANIM_CUSTOM: mAnimationType = otherOptions.mAnimationType; @@ -581,7 +527,6 @@ public class ActivityOptions { } } mAnimationStartedListener = otherOptions.mAnimationStartedListener; - mTransitionCompleteListener = null; break; case ANIM_SCALE_UP: mAnimationType = otherOptions.mAnimationType; @@ -596,7 +541,6 @@ public class ActivityOptions { } } mAnimationStartedListener = null; - mTransitionCompleteListener = null; break; case ANIM_THUMBNAIL_SCALE_UP: case ANIM_THUMBNAIL_SCALE_DOWN: @@ -611,15 +555,12 @@ public class ActivityOptions { } } mAnimationStartedListener = otherOptions.mAnimationStartedListener; - mTransitionCompleteListener = null; break; case ANIM_SCENE_TRANSITION: mAnimationType = otherOptions.mAnimationType; - mTransitionCompleteListener = otherOptions.mTransitionCompleteListener; + mExitReceiver = otherOptions.mExitReceiver; mThumbnail = null; mAnimationStartedListener = null; - mSharedElementNames = otherOptions.mSharedElementNames; - mLocalElementNames = otherOptions.mLocalElementNames; break; } } @@ -663,11 +604,9 @@ public class ActivityOptions { break; case ANIM_SCENE_TRANSITION: b.putInt(KEY_ANIM_TYPE, mAnimationType); - if (mTransitionCompleteListener != null) { - b.putParcelable(KEY_TRANSITION_COMPLETE_LISTENER, mTransitionCompleteListener); + if (mExitReceiver != null) { + b.putParcelable(KEY_TRANSITION_COMPLETE_LISTENER, mExitReceiver); } - b.putStringArrayList(KEY_SHARED_ELEMENT_NAMES, mSharedElementNames); - b.putStringArrayList(KEY_LOCAL_ELEMENT_NAMES, mLocalElementNames); break; } return b; @@ -687,130 +626,92 @@ public class ActivityOptions { return null; } - /** @hide */ - public void addSharedElements(Map<String, View> sharedElements) { - for (Map.Entry<String, View> entry : sharedElements.entrySet()) { - addSharedElement(entry.getValue(), entry.getKey()); - } - } - - /** @hide */ - public void updateSceneTransitionAnimation(Transition exitTransition, - Transition sharedElementTransition, SharedElementSource sharedElementSource) { - mTransitionCompleteListener = new ExitTransitionListener(exitTransition, - sharedElementTransition, sharedElementSource); - } - - private void addSharedElement(View view, String name) { - String sharedElementName = view.getSharedElementName(); - if (name == null) { - name = sharedElementName; - } - mSharedElementNames.add(name); - mLocalElementNames.add(sharedElementName); - } - - /** @hide */ - public interface SharedElementSource { - Bundle getSharedElementExitState(); - void acceptedSharedElements(ArrayList<String> sharedElementNames); - void hideSharedElements(); - void restore(Bundle sharedElementState); - void prepareForRestore(); - } - - private static class ExitTransitionListener extends ResultReceiver - implements Transition.TransitionListener { - private boolean mSharedElementNotified; - private IRemoteCallback mTransitionCompleteCallback; - private boolean mExitComplete; - private boolean mSharedElementComplete; - private SharedElementSource mSharedElementSource; - - public ExitTransitionListener(Transition exitTransition, Transition sharedElementTransition, - SharedElementSource sharedElementSource) { - super(null); - mSharedElementSource = sharedElementSource; - exitTransition.addListener(this); - sharedElementTransition.addListener(new Transition.TransitionListenerAdapter() { - @Override - public void onTransitionEnd(Transition transition) { - mSharedElementComplete = true; - notifySharedElement(); - transition.removeListener(this); - } - }); - } - - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - switch (resultCode) { - case MSG_SET_LISTENER: - IBinder listener = resultData.getBinder(KEY_TRANSITION_TARGET_LISTENER); - mTransitionCompleteCallback = IRemoteCallback.Stub.asInterface(listener); - ArrayList<String> sharedElementNames - = resultData.getStringArrayList(KEY_SHARED_ELEMENT_NAMES); - mSharedElementSource.acceptedSharedElements(sharedElementNames); - notifySharedElement(); - notifyExit(); - break; - case MSG_HIDE_SHARED_ELEMENTS: - mSharedElementSource.hideSharedElements(); - break; - case MSG_PREPARE_RESTORE: - mSharedElementSource.prepareForRestore(); - break; - case MSG_RESTORE: - mSharedElementSource.restore(resultData); - break; - } - } - - @Override - public void onTransitionStart(Transition transition) { - } - - @Override - public void onTransitionEnd(Transition transition) { - mExitComplete = true; - notifyExit(); - transition.removeListener(this); - } - - @Override - public void onTransitionCancel(Transition transition) { - onTransitionEnd(transition); - } - - @Override - public void onTransitionPause(Transition transition) { + /** + * Listener provided in + * {@link android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.Window, + * android.app.ActivityOptions.ActivityTransitionListener)} or in + * {@link android.app.Activity#setActivityTransitionListener( + * android.app.ActivityOptions.ActivityTransitionListener)} to monitor the Activity transitions. + * The events can be used to customize or override Activity Transition behavior. + */ + public static class ActivityTransitionListener { + /** + * Called when the enter Transition is ready to start, but hasn't started yet. If + * {@link android.view.Window#getEnterTransition()} is non-null, + * The entering views will be {@link View#INVISIBLE}. + */ + public void onEnterReady() {} + + /** + * Called when the remote exiting transition completes. + */ + public void onRemoteExitComplete() {} + + /** + * Called when the start state for shared elements is captured on enter. + */ + public void onCaptureSharedElementStart() {} + + /** + * Called when the end state for shared elements is captured on enter. + */ + public void onCaptureSharedElementEnd() {} + + /** + * Called when the enter Transition has been started. + * @param sharedElementNames The names of shared elements that were transferred. + * @param sharedElements The shared elements that were transferred. + */ + public void onStartEnterTransition(List<String> sharedElementNames, + List<View> sharedElements) {} + + /** + * Called when the exit Transition has been started. + * @param sharedElementNames The names of all shared elements that will be transferred. + * @param sharedElements All shared elements that will be transferred. + */ + public void onStartExitTransition(List<String> sharedElementNames, + List<View> sharedElements) {} + + /** + * Called when the exiting shared element transition completes. + */ + public void onSharedElementExitTransitionComplete() {} + + /** + * Called on exit when the shared element has been transferred. + * @param sharedElementNames The names of all shared elements that were transferred. + * @param sharedElements All shared elements that will were transferred. + */ + public void onSharedElementTransferred(List<String> sharedElementNames, + List<View> sharedElements) {} + + /** + * Called when the exit transition has completed. + */ + public void onExitTransitionComplete() {} + + /** + * Returns a mapping from a View in the View hierarchy to the shared element name used + * in the call. This is called twice -- once when the view is + * entering and again when it exits. A null return value indicates that the + * View hierachy can be trusted without any remapping. + * @return A map from a View in the hierarchy to the shared element name used in the + * call. + */ + public Pair<View, String>[] getSharedElementsMapping() { return null; } + } + + private static class SharedElementMappingListener extends ActivityTransitionListener { + Pair<View, String>[] mSharedElementsMapping = new Pair[1]; + + public SharedElementMappingListener(View view, String name) { + mSharedElementsMapping[0] = Pair.create(view, name); } @Override - public void onTransitionResume(Transition transition) { - } - - private void notifySharedElement() { - if (!mSharedElementNotified && mSharedElementComplete - && mTransitionCompleteCallback != null) { - mSharedElementNotified = true; - try { - Bundle sharedElementState = mSharedElementSource.getSharedElementExitState(); - mTransitionCompleteCallback.sendResult(sharedElementState); - } catch (RemoteException e) { - Log.w(TAG, "Couldn't notify that the transition ended", e); - } - } - } - - private void notifyExit() { - if (mExitComplete && mTransitionCompleteCallback != null) { - try { - mTransitionCompleteCallback.sendResult(null); - } catch (RemoteException e) { - Log.w(TAG, "Couldn't notify that the transition ended", e); - } - } + public Pair<View, String>[] getSharedElementsMapping() { + return mSharedElementsMapping; } } } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index cb07375..4562d6e 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -2363,7 +2363,7 @@ public final class ActivityThread { // manager to stop us. try { ActivityManagerNative.getDefault() - .finishActivity(r.token, Activity.RESULT_CANCELED, null); + .finishActivity(r.token, Activity.RESULT_CANCELED, null, false); } catch (RemoteException ex) { // Ignore } @@ -2984,7 +2984,7 @@ public final class ActivityThread { // just end this activity. try { ActivityManagerNative.getDefault() - .finishActivity(token, Activity.RESULT_CANCELED, null); + .finishActivity(token, Activity.RESULT_CANCELED, null, false); } catch (RemoteException ex) { } } diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java new file mode 100644 index 0000000..d8a356f --- /dev/null +++ b/core/java/android/app/ActivityTransitionCoordinator.java @@ -0,0 +1,736 @@ +/* + * 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.app; + +import android.graphics.Rect; +import android.os.Bundle; +import android.os.Handler; +import android.os.ResultReceiver; +import android.transition.Transition; +import android.transition.TransitionManager; +import android.transition.TransitionSet; +import android.util.ArrayMap; +import android.util.Pair; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.Window; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Base class for ExitTransitionCoordinator and EnterTransitionCoordinator, classes + * that manage activity transitions and the communications coordinating them between + * Activities. The ExitTransitionCoordinator is created in the + * ActivityOptions#makeSceneTransitionAnimation. The EnterTransitionCoordinator + * is created by ActivityOptions#createEnterActivityTransition by Activity when the window is + * attached. + * + * Typical startActivity goes like this: + * 1) ExitTransitionCoordinator created with ActivityOptions#makeSceneTransitionAnimation + * 2) Activity#startActivity called and that calls startExit() through + * ActivityOptions#dispatchStartExit + * - Exit transition starts by setting transitioning Views to INVISIBLE + * 3) Launched Activity starts, creating an EnterTransitionCoordinator. + * - The Window is made translucent + * - The Window background alpha is set to 0 + * - The transitioning views are made INVISIBLE + * - MSG_SET_LISTENER is sent back to the ExitTransitionCoordinator. + * 4) The shared element transition completes. + * - MSG_TAKE_SHARED_ELEMENTS is sent to the EnterTransitionCoordinator + * 5) The MSG_TAKE_SHARED_ELEMENTS is received by the EnterTransitionCoordinator. + * - Shared elements are made VISIBLE + * - Shared elements positions and size are set to match the end state of the calling + * Activity. + * - The shared element transition is started + * - If the window allows overlapping transitions, the views transition is started by setting + * the entering Views to VISIBLE and the background alpha is animated to opaque. + * - MSG_HIDE_SHARED_ELEMENTS is sent to the ExitTransitionCoordinator + * 6) MSG_HIDE_SHARED_ELEMENTS is received by the ExitTransitionCoordinator + * - The shared elements are made INVISIBLE + * 7) The exit transition completes in the calling Activity. + * - MSG_EXIT_TRANSITION_COMPLETE is sent to the EnterTransitionCoordinator. + * 8) The MSG_EXIT_TRANSITION_COMPLETE is received by the EnterTransitionCoordinator. + * - If the window doesn't allow overlapping enter transitions, the enter transition is started + * by setting entering views to VISIBLE and the background is animated to opaque. + * 9) The background opacity animation completes. + * - The window is made opaque + * 10) The calling Activity gets an onStop() call + * - onActivityStopped() is called and all exited Views are made VISIBLE. + * + * Typical finishWithTransition goes like this: + * 1) finishWithTransition() calls startExit() + * - The Window start transitioning to Translucent + * - If no background exists, a black background is substituted + * - MSG_PREPARE_RESTORE is sent to the ExitTransitionCoordinator + * - The shared elements in the scene are matched against those shared elements + * that were sent by comparing the names. + * - The exit transition is started by setting Views to INVISIBLE. + * 2) MSG_PREPARE_RESTORE is received by the EnterTransitionCoordinator + * - All transitioning views are made VISIBLE to reverse what was done when onActivityStopped() + * was called + * 3) The Window is made translucent and a callback is received + * - The background alpha is animated to 0 + * 4) The background alpha animation completes + * 5) The shared element transition completes + * - After both 4 & 5 complete, MSG_TAKE_SHARED_ELEMENTS is sent to the + * ExitTransitionCoordinator + * 6) MSG_TAKE_SHARED_ELEMENTS is received by ExitTransitionCoordinator + * - Shared elements are made VISIBLE + * - Shared elements positions and size are set to match the end state of the calling + * Activity. + * - The shared element transition is started + * - If the window allows overlapping transitions, the views transition is started by setting + * the entering Views to VISIBLE. + * - MSG_HIDE_SHARED_ELEMENTS is sent to the EnterTransitionCoordinator + * 7) MSG_HIDE_SHARED_ELEMENTS is received by the EnterTransitionCoordinator + * - The shared elements are made INVISIBLE + * 8) The exit transition completes in the finishing Activity. + * - MSG_EXIT_TRANSITION_COMPLETE is sent to the ExitTransitionCoordinator. + * - finish() is called on the exiting Activity + * 9) The MSG_EXIT_TRANSITION_COMPLETE is received by the ExitTransitionCoordinator. + * - If the window doesn't allow overlapping enter transitions, the enter transition is started + * by setting entering views to VISIBLE. + */ +abstract class ActivityTransitionCoordinator extends ResultReceiver { + private static final String TAG = "ActivityTransitionCoordinator"; + + /** + * The names of shared elements that are transitioned to the started Activity. + * This is also the name of shared elements that the started Activity accepted. + */ + public static final String KEY_SHARED_ELEMENT_NAMES = "android:shared_element_names"; + + public static final String KEY_SHARED_ELEMENT_STATE = "android:shared_element_state"; + + /** + * For Activity transitions, the called Activity's listener to receive calls + * when transitions complete. + */ + static final String KEY_TRANSITION_RESULTS_RECEIVER = "android:transitionTargetListener"; + + private static final String KEY_SCREEN_X = "shared_element:screenX"; + private static final String KEY_SCREEN_Y = "shared_element:screenY"; + private static final String KEY_TRANSLATION_Z = "shared_element:translationZ"; + private static final String KEY_WIDTH = "shared_element:width"; + private static final String KEY_HEIGHT = "shared_element:height"; + private static final String KEY_NAME = "shared_element:name"; + + /** + * Sent by the exiting coordinator (either EnterTransitionCoordinator + * or ExitTransitionCoordinator) after the shared elements have + * become stationary (shared element transition completes). This tells + * the remote coordinator to take control of the shared elements and + * that animations may begin. The remote Activity won't start entering + * until this message is received, but may wait for + * MSG_EXIT_TRANSITION_COMPLETE if allowOverlappingTransitions() is true. + */ + public static final int MSG_SET_LISTENER = 100; + + /** + * Sent by the entering coordinator to tell the exiting coordinator + * to hide its shared elements after it has started its shared + * element transition. This is temporary until the + * interlock of shared elements is figured out. + */ + public static final int MSG_HIDE_SHARED_ELEMENTS = 101; + + /** + * Sent by the EnterTransitionCoordinator to tell the + * ExitTransitionCoordinator to hide all of its exited views after + * MSG_ACTIVITY_STOPPED has caused them all to show. + */ + public static final int MSG_PREPARE_RESTORE = 102; + + /** + * Sent by the exiting Activity in ActivityOptions#dispatchActivityStopped + * to leave the Activity in a good state after it has been hidden. + */ + public static final int MSG_ACTIVITY_STOPPED = 103; + + /** + * Sent by the exiting coordinator (either EnterTransitionCoordinator + * or ExitTransitionCoordinator) after the shared elements have + * become stationary (shared element transition completes). This tells + * the remote coordinator to take control of the shared elements and + * that animations may begin. The remote Activity won't start entering + * until this message is received, but may wait for + * MSG_EXIT_TRANSITION_COMPLETE if allowOverlappingTransitions() is true. + */ + public static final int MSG_TAKE_SHARED_ELEMENTS = 104; + + /** + * Sent by the exiting coordinator (either + * EnterTransitionCoordinator or ExitTransitionCoordinator) after + * the exiting Views have finished leaving the scene. This will + * be ignored if allowOverlappingTransitions() is true on the + * remote coordinator. If it is false, it will trigger the enter + * transition to start. + */ + public static final int MSG_EXIT_TRANSITION_COMPLETE = 105; + + /** + * Sent by Activity#startActivity to begin the exit transition. + */ + public static final int MSG_START_EXIT_TRANSITION = 106; + + private Window mWindow; + private ArrayList<View> mSharedElements = new ArrayList<View>(); + private ArrayList<String> mTargetSharedNames = new ArrayList<String>(); + private ActivityOptions.ActivityTransitionListener mListener = + new ActivityOptions.ActivityTransitionListener(); + private ArrayList<View> mEnteringViews; + private ResultReceiver mRemoteResultReceiver; + private boolean mNotifiedSharedElementTransitionComplete; + private boolean mNotifiedExitTransitionComplete; + + private FixedEpicenterCallback mEpicenterCallback = new FixedEpicenterCallback(); + + private Transition.TransitionListener mSharedElementListener = + new Transition.TransitionListenerAdapter() { + @Override + public void onTransitionEnd(Transition transition) { + transition.removeListener(this); + onSharedElementTransitionEnd(); + } + }; + + private Transition.TransitionListener mExitListener = + new Transition.TransitionListenerAdapter() { + @Override + public void onTransitionEnd(Transition transition) { + transition.removeListener(this); + onExitTransitionEnd(); + } + }; + + public ActivityTransitionCoordinator(Window window) + { + super(new Handler()); + mWindow = window; + } + + // -------------------- ResultsReceiver Overrides ---------------------- + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + switch (resultCode) { + case MSG_SET_LISTENER: + ResultReceiver resultReceiver + = resultData.getParcelable(KEY_TRANSITION_RESULTS_RECEIVER); + setRemoteResultReceiver(resultReceiver); + onSetResultReceiver(); + break; + case MSG_HIDE_SHARED_ELEMENTS: + onHideSharedElements(); + break; + case MSG_PREPARE_RESTORE: + onPrepareRestore(); + break; + case MSG_EXIT_TRANSITION_COMPLETE: + onRemoteSceneExitComplete(); + break; + case MSG_TAKE_SHARED_ELEMENTS: + ArrayList<String> sharedElementNames + = resultData.getStringArrayList(KEY_SHARED_ELEMENT_NAMES); + Bundle sharedElementState = resultData.getBundle(KEY_SHARED_ELEMENT_STATE); + onTakeSharedElements(sharedElementNames, sharedElementState); + break; + case MSG_ACTIVITY_STOPPED: + onActivityStopped(); + break; + case MSG_START_EXIT_TRANSITION: + startExit(); + break; + } + } + + // -------------------- calls that can be overridden by subclasses -------------------- + + /** + * Called when MSG_SET_LISTENER is received. This will only be received by + * ExitTransitionCoordinator. + */ + protected void onSetResultReceiver() {} + + /** + * Called when MSG_HIDE_SHARED_ELEMENTS is received + */ + protected void onHideSharedElements() { + setViewVisibility(getSharedElements(), View.INVISIBLE); + mListener.onSharedElementTransferred(getSharedElementNames(), getSharedElements()); + } + + /** + * Called when MSG_PREPARE_RESTORE is called. This will only be received by + * ExitTransitionCoordinator. + */ + protected void onPrepareRestore() { + mListener.onEnterReady(); + } + + /** + * Called when MSG_EXIT_TRANSITION_COMPLETE is received -- the remote coordinator has + * completed its exit transition. This can be called by the ExitTransitionCoordinator when + * starting an Activity or EnterTransitionCoordinator when called with finishWithTransition. + */ + protected void onRemoteSceneExitComplete() { + if (!allowOverlappingTransitions()) { + Transition transition = beginTransition(mEnteringViews, false, true, true); + onStartEnterTransition(transition, mEnteringViews); + } + mListener.onRemoteExitComplete(); + } + + /** + * Called when MSG_TAKE_SHARED_ELEMENTS is received. This means that the shared elements are + * in a stable state and ready to move to the Window. + * @param sharedElementNames The names of the shared elements to move. + * @param state Contains the shared element states (size & position) + */ + protected void onTakeSharedElements(ArrayList<String> sharedElementNames, Bundle state) { + setSharedElements(); + reconcileSharedElements(sharedElementNames); + mEnteringViews.removeAll(mSharedElements); + setSharedElementState(state); + if (getViewsTransition() != null) { + setViewVisibility(mEnteringViews, View.INVISIBLE); + } + setViewVisibility(mSharedElements, View.VISIBLE); + Transition transition = beginTransition(mEnteringViews, true, allowOverlappingTransitions(), + true); + if (allowOverlappingTransitions()) { + onStartEnterTransition(transition, mEnteringViews); + } + mRemoteResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null); + } + + /** + * Called when MSG_ACTIVITY_STOPPED is received. This is received when Activity.onStop is + * called after running startActivity* is called using an Activity Transition. + */ + protected void onActivityStopped() {} + + /** + * Called when the start transition is ready to run. This may be immediately after + * MSG_TAKE_SHARED_ELEMENTS or MSG_EXIT_TRANSITION_COMPLETE, depending on whether + * overlapping transitions are allowed. + * @param transition The transition currently started. + * @param enteringViews The views entering the scene. This won't include shared elements. + */ + protected void onStartEnterTransition(Transition transition, ArrayList<View> enteringViews) { + if (getViewsTransition() != null) { + setViewVisibility(enteringViews, View.VISIBLE); + } + mEnteringViews = null; + mListener.onStartEnterTransition(getSharedElementNames(), getSharedElements()); + } + + /** + * Called when the exit transition has started. + * @param exitingViews The views leaving the scene. This won't include shared elements. + */ + protected void onStartExitTransition(ArrayList<View> exitingViews) {} + + /** + * Called during the exit when the shared element transition has completed. + */ + protected void onSharedElementTransitionEnd() { + Bundle bundle = new Bundle(); + int[] tempLoc = new int[2]; + for (int i = 0; i < mSharedElements.size(); i++) { + View sharedElement = mSharedElements.get(i); + String name = mTargetSharedNames.get(i); + captureSharedElementState(sharedElement, name, bundle, tempLoc); + } + Bundle allValues = new Bundle(); + allValues.putStringArrayList(KEY_SHARED_ELEMENT_NAMES, getSharedElementNames()); + allValues.putBundle(KEY_SHARED_ELEMENT_STATE, bundle); + sharedElementTransitionComplete(allValues); + mListener.onSharedElementExitTransitionComplete(); + } + + /** + * Called after the shared element transition is complete to pass the shared element state + * to the remote coordinator. + * @param bundle The Bundle to send to the coordinator containing the shared element state. + */ + protected abstract void sharedElementTransitionComplete(Bundle bundle); + + /** + * Called when the exit transition finishes. + */ + protected void onExitTransitionEnd() { + mListener.onExitTransitionComplete(); + } + + /** + * Called to start the exit transition. Launched from ActivityOptions#dispatchStartExit + */ + protected abstract void startExit(); + + /** + * A non-null transition indicates that the Views of the Window should be made INVISIBLE. + * @return The Transition used to cause transitioning views to either enter or exit the scene. + */ + protected abstract Transition getViewsTransition(); + + /** + * @return The Transition used to move the shared elements from the start position and size + * to the end position and size. + */ + protected abstract Transition getSharedElementTransition(); + + /** + * @return When the enter transition should overlap with the exit transition of the + * remote controller. + */ + protected abstract boolean allowOverlappingTransitions(); + + // called by subclasses + + protected void notifySharedElementTransitionComplete(Bundle sharedElements) { + if (!mNotifiedSharedElementTransitionComplete) { + mNotifiedSharedElementTransitionComplete = true; + mRemoteResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, sharedElements); + } + } + + protected void notifyExitTransitionComplete() { + if (!mNotifiedExitTransitionComplete) { + mNotifiedExitTransitionComplete = true; + mRemoteResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null); + } + } + + protected void notifyPrepareRestore() { + mRemoteResultReceiver.send(MSG_PREPARE_RESTORE, null); + } + + protected void setRemoteResultReceiver(ResultReceiver resultReceiver) { + mRemoteResultReceiver = resultReceiver; + } + + protected void notifySetListener() { + Bundle bundle = new Bundle(); + bundle.putParcelable(KEY_TRANSITION_RESULTS_RECEIVER, this); + mRemoteResultReceiver.send(MSG_SET_LISTENER, bundle); + } + + protected void setEnteringViews(ArrayList<View> views) { + mEnteringViews = views; + } + + protected void setSharedElements() { + Pair<View, String>[] sharedElements = mListener.getSharedElementsMapping(); + mSharedElements.clear(); + mTargetSharedNames.clear(); + if (sharedElements == null) { + ArrayMap<String, View> map = new ArrayMap<String, View>(); + setViewVisibility(mEnteringViews, View.VISIBLE); + getDecor().findSharedElements(map); + setViewVisibility(mEnteringViews, View.INVISIBLE); + for (int i = 0; i < map.size(); i++) { + View view = map.valueAt(i); + String name = map.keyAt(i); + mSharedElements.add(view); + mTargetSharedNames.add(name); + } + } else { + for (int i = 0; i < sharedElements.length; i++) { + Pair<View, String> viewStringPair = sharedElements[i]; + View view = viewStringPair.first; + String name = viewStringPair.second; + mSharedElements.add(view); + mTargetSharedNames.add(name); + } + } + } + + protected ArrayList<View> getSharedElements() { + return mSharedElements; + } + + protected ArrayList<String> getSharedElementNames() { + return mTargetSharedNames; + } + + protected Window getWindow() { + return mWindow; + } + + protected ViewGroup getDecor() { + return (mWindow == null) ? null : (ViewGroup) mWindow.getDecorView(); + } + + protected void startExitTransition(ArrayList<String> sharedElements) { + setSharedElements(); + reconcileSharedElements(sharedElements); + ArrayList<View> transitioningViews = captureTransitioningViews(); + beginTransition(transitioningViews, true, true, false); + onStartExitTransition(transitioningViews); + if (getViewsTransition() != null) { + setViewVisibility(transitioningViews, View.INVISIBLE); + } + mListener.onStartExitTransition(getSharedElementNames(), getSharedElements()); + } + + protected void clearConnections() { + mRemoteResultReceiver = null; + } + + // public API + + public void setActivityTransitionListener(ActivityOptions.ActivityTransitionListener listener) { + if (listener == null) { + mListener = new ActivityOptions.ActivityTransitionListener(); + } else { + mListener = listener; + } + } + + // private methods + + private Transition configureTransition(Transition transition) { + if (transition != null) { + transition = transition.clone(); + transition.setEpicenterCallback(mEpicenterCallback); + } + return transition; + } + + private void reconcileSharedElements(ArrayList<String> sharedElementNames) { + Rect epicenter = null; + for (int i = mTargetSharedNames.size() - 1; i >= 0; i--) { + if (!sharedElementNames.contains(mTargetSharedNames.get(i))) { + mTargetSharedNames.remove(i); + mSharedElements.remove(i); + } + } + if (!mSharedElements.isEmpty()) { + epicenter = calcEpicenter(mSharedElements.get(0)); + } + mEpicenterCallback.setEpicenter(epicenter); + } + + private void setSharedElementState(Bundle sharedElementState) { + if (sharedElementState != null) { + int[] tempLoc = new int[2]; + for (int i = 0; i < mSharedElements.size(); i++) { + View sharedElement = mSharedElements.get(i); + String name = mTargetSharedNames.get(i); + setSharedElementState(sharedElement, name, sharedElementState, tempLoc); + } + } + mListener.onCaptureSharedElementStart(); + getDecor().getViewTreeObserver().addOnPreDrawListener( + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + getDecor().getViewTreeObserver().removeOnPreDrawListener(this); + mListener.onCaptureSharedElementEnd(); + return true; + } + } + ); + } + + /** + * Sets the captured values from a previous + * {@link #captureSharedElementState(android.view.View, String, android.os.Bundle, int[])} + * @param view The View to apply placement changes to. + * @param name The shared element name given from the source Activity. + * @param transitionArgs A <code>Bundle</code> containing all placementinformation for named + * shared elements in the scene. + * @param tempLoc A temporary int[2] for capturing the current location of views. + */ + private static void setSharedElementState(View view, String name, Bundle transitionArgs, + int[] tempLoc) { + Bundle sharedElementBundle = transitionArgs.getBundle(name); + if (sharedElementBundle == null) { + return; + } + + float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z); + view.setTranslationZ(z); + + int x = sharedElementBundle.getInt(KEY_SCREEN_X); + int y = sharedElementBundle.getInt(KEY_SCREEN_Y); + int width = sharedElementBundle.getInt(KEY_WIDTH); + int height = sharedElementBundle.getInt(KEY_HEIGHT); + + int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); + int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY); + view.measure(widthSpec, heightSpec); + + ViewGroup parent = (ViewGroup) view.getParent(); + parent.getLocationOnScreen(tempLoc); + int left = x - tempLoc[0]; + int top = y - tempLoc[1]; + int right = left + width; + int bottom = top + height; + view.layout(left, top, right, bottom); + + view.requestLayout(); + } + + /** + * Captures placement information for Views with a shared element name for + * Activity Transitions. + * @param view The View to capture the placement information for. + * @param name The shared element name in the target Activity to apply the placement + * information for. + * @param transitionArgs Bundle to store shared element placement information. + * @param tempLoc A temporary int[2] for capturing the current location of views. + * @see #setSharedElementState(android.view.View, String, android.os.Bundle, int[]) + */ + private static void captureSharedElementState(View view, String name, Bundle transitionArgs, + int[] tempLoc) { + Bundle sharedElementBundle = new Bundle(); + view.getLocationOnScreen(tempLoc); + float scaleX = view.getScaleX(); + sharedElementBundle.putInt(KEY_SCREEN_X, tempLoc[0]); + int width = Math.round(view.getWidth() * scaleX); + sharedElementBundle.putInt(KEY_WIDTH, width); + + float scaleY = view.getScaleY(); + sharedElementBundle.putInt(KEY_SCREEN_Y, tempLoc[1]); + int height= Math.round(view.getHeight() * scaleY); + sharedElementBundle.putInt(KEY_HEIGHT, height); + + sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ()); + + sharedElementBundle.putString(KEY_NAME, view.getSharedElementName()); + + transitionArgs.putBundle(name, sharedElementBundle); + } + + private static Rect calcEpicenter(View view) { + int[] loc = new int[2]; + view.getLocationOnScreen(loc); + int left = loc[0] + Math.round(view.getTranslationX()); + int top = loc[1] + Math.round(view.getTranslationY()); + int right = left + view.getWidth(); + int bottom = top + view.getHeight(); + return new Rect(left, top, right, bottom); + } + + public static void setViewVisibility(Collection<View> views, int visibility) { + if (views != null) { + for (View view : views) { + view.setVisibility(visibility); + } + } + } + + private static Transition addTransitionTargets(Transition transition, Collection<View> views) { + if (transition == null || views == null || views.isEmpty()) { + return null; + } + TransitionSet set = new TransitionSet(); + set.addTransition(transition.clone()); + if (views != null) { + for (View view: views) { + set.addTarget(view); + } + } + return set; + } + + private ArrayList<View> captureTransitioningViews() { + if (getViewsTransition() == null) { + return null; + } + ArrayList<View> transitioningViews = new ArrayList<View>(); + getDecor().captureTransitioningViews(transitioningViews); + transitioningViews.removeAll(getSharedElements()); + return transitioningViews; + } + + private Transition getSharedElementTransition(boolean isEnter) { + Transition transition = getSharedElementTransition(); + if (transition == null) { + return null; + } + transition = configureTransition(transition); + if (!isEnter) { + transition.addListener(mSharedElementListener); + } + return transition; + } + + private Transition getViewsTransition(ArrayList<View> transitioningViews, boolean isEnter) { + Transition transition = getViewsTransition(); + if (transition == null) { + return null; + } + transition = configureTransition(transition); + if (!isEnter) { + transition.addListener(mExitListener); + } + return addTransitionTargets(transition, transitioningViews); + } + + private Transition beginTransition(ArrayList<View> transitioningViews, + boolean transitionSharedElement, boolean transitionViews, boolean isEnter) { + Transition sharedElementTransition = null; + if (transitionSharedElement) { + sharedElementTransition = getSharedElementTransition(isEnter); + if (!isEnter && sharedElementTransition == null) { + onSharedElementTransitionEnd(); + } + } + Transition viewsTransition = null; + if (transitionViews) { + viewsTransition = getViewsTransition(transitioningViews, isEnter); + if (!isEnter && viewsTransition == null) { + onExitTransitionEnd(); + } + } + + Transition transition = null; + if (sharedElementTransition == null) { + transition = viewsTransition; + } else if (viewsTransition == null) { + transition = sharedElementTransition; + } else { + TransitionSet set = new TransitionSet(); + set.addTransition(sharedElementTransition); + set.addTransition(viewsTransition); + transition = set; + } + if (transition != null) { + TransitionManager.beginDelayedTransition(getDecor(), transition); + if (transitionSharedElement && !mSharedElements.isEmpty()) { + mSharedElements.get(0).invalidate(); + } else if (transitionViews && !transitioningViews.isEmpty()) { + transitioningViews.get(0).invalidate(); + } + } + return transition; + } + + private static class FixedEpicenterCallback extends Transition.EpicenterCallback { + private Rect mEpicenter; + + public void setEpicenter(Rect epicenter) { mEpicenter = epicenter; } + + @Override + public Rect getEpicenter(Transition transition) { + return mEpicenter; + } + } +} diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java index 113f123..edf21dd 100644 --- a/core/java/android/app/ActivityView.java +++ b/core/java/android/app/ActivityView.java @@ -33,17 +33,20 @@ import android.view.MotionEvent; import android.view.Surface; import android.view.TextureView; import android.view.TextureView.SurfaceTextureListener; -import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; +import dalvik.system.CloseGuard; + +import java.lang.ref.WeakReference; /** @hide */ public class ActivityView extends ViewGroup { - private final String TAG = "ActivityView"; - private final boolean DEBUG = false; + private static final String TAG = "ActivityView"; + private static final boolean DEBUG = false; + DisplayMetrics mMetrics; private final TextureView mTextureView; - private IActivityContainer mActivityContainer; + private ActivityContainerWrapper mActivityContainer; private Activity mActivity; private int mWidth; private int mHeight; @@ -75,68 +78,33 @@ public class ActivityView extends ViewGroup { throw new IllegalStateException("The ActivityView's Context is not an Activity."); } - mTextureView = new TextureView(context); - mTextureView.setSurfaceTextureListener(new ActivityViewSurfaceTextureListener()); - addView(mTextureView); - if (DEBUG) Log.v(TAG, "ctor()"); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - mTextureView.layout(0, 0, r - l, b - t); - } - - @Override - protected void onAttachedToWindow() { - if (DEBUG) Log.v(TAG, "onAttachedToWindow()"); - super.onAttachedToWindow(); try { - final IBinder token = mActivity.getActivityToken(); - mActivityContainer = - ActivityManagerNative.getDefault().createActivityContainer(token, null); + mActivityContainer = new ActivityContainerWrapper( + ActivityManagerNative.getDefault().createActivityContainer( + mActivity.getActivityToken(), new ActivityContainerCallback(this))); } catch (RemoteException e) { - throw new IllegalStateException("ActivityView: Unable to create ActivityContainer. " + throw new RuntimeException("ActivityView: Unable to create ActivityContainer. " + e); } - attachToSurfaceWhenReady(); - } + mTextureView = new TextureView(context); + mTextureView.setSurfaceTextureListener(new ActivityViewSurfaceTextureListener()); + addView(mTextureView); - @Override - protected void onDetachedFromWindow() { - if (DEBUG) Log.v(TAG, "onDetachedFromWindow(): mActivityContainer=" + mActivityContainer); - super.onDetachedFromWindow(); - if (mActivityContainer != null) { - detach(); - try { - ActivityManagerNative.getDefault().deleteActivityContainer(mActivityContainer); - } catch (RemoteException e) { - } - mActivityContainer = null; - } + WindowManager wm = (WindowManager)mActivity.getSystemService(Context.WINDOW_SERVICE); + mMetrics = new DisplayMetrics(); + wm.getDefaultDisplay().getMetrics(mMetrics); + + if (DEBUG) Log.v(TAG, "ctor()"); } @Override - protected void onWindowVisibilityChanged(int visibility) { - if (DEBUG) Log.v(TAG, "onWindowVisibilityChanged(): visibility=" + visibility); - super.onWindowVisibilityChanged(visibility); - switch (visibility) { - case View.VISIBLE: - attachToSurfaceWhenReady(); - break; - case View.INVISIBLE: - break; - case View.GONE: - break; - } + protected void onLayout(boolean changed, int l, int t, int r, int b) { + mTextureView.layout(0, 0, r - l, b - t); } private boolean injectInputEvent(InputEvent event) { - try { - return mActivityContainer != null && mActivityContainer.injectEvent(event); - } catch (RemoteException e) { - return false; - } + return mActivityContainer != null && mActivityContainer.injectEvent(event); } @Override @@ -154,40 +122,45 @@ public class ActivityView extends ViewGroup { return super.onGenericMotionEvent(event); } + @Override + public void onAttachedToWindow() { + if (DEBUG) Log.v(TAG, "onAttachedToWindow(): mActivityContainer=" + mActivityContainer + + " mSurface=" + mSurface); + } + + @Override + public void onDetachedFromWindow() { + if (DEBUG) Log.v(TAG, "onDetachedFromWindow(): mActivityContainer=" + mActivityContainer + + " mSurface=" + mSurface); + } + public boolean isAttachedToDisplay() { return mSurface != null; } public void startActivity(Intent intent) { + if (mActivityContainer == null) { + throw new IllegalStateException("Attempt to call startActivity after release"); + } if (DEBUG) Log.v(TAG, "startActivity(): intent=" + intent + " " + (isAttachedToDisplay() ? "" : "not") + " attached"); if (mSurface != null) { - try { - mActivityContainer.startActivity(intent); - } catch (RemoteException e) { - throw new IllegalStateException("ActivityView: Unable to startActivity. " + e); - } + mActivityContainer.startActivity(intent); } else { mQueuedIntent = intent; mQueuedPendingIntent = null; } } - private void startActivityIntentSender(IIntentSender iIntentSender) { - try { - mActivityContainer.startActivityIntentSender(iIntentSender); - } catch (RemoteException e) { - throw new IllegalStateException( - "ActivityView: Unable to startActivity from IntentSender. " + e); - } - } - public void startActivity(IntentSender intentSender) { + if (mActivityContainer == null) { + throw new IllegalStateException("Attempt to call startActivity after release"); + } if (DEBUG) Log.v(TAG, "startActivityIntentSender(): intentSender=" + intentSender + " " + (isAttachedToDisplay() ? "" : "not") + " attached"); final IIntentSender iIntentSender = intentSender.getTarget(); if (mSurface != null) { - startActivityIntentSender(iIntentSender); + mActivityContainer.startActivityIntentSender(iIntentSender); } else { mQueuedPendingIntent = iIntentSender; mQueuedIntent = null; @@ -195,84 +168,102 @@ public class ActivityView extends ViewGroup { } public void startActivity(PendingIntent pendingIntent) { + if (mActivityContainer == null) { + throw new IllegalStateException("Attempt to call startActivity after release"); + } if (DEBUG) Log.v(TAG, "startActivityPendingIntent(): PendingIntent=" + pendingIntent + " " + (isAttachedToDisplay() ? "" : "not") + " attached"); final IIntentSender iIntentSender = pendingIntent.getTarget(); if (mSurface != null) { - startActivityIntentSender(iIntentSender); + mActivityContainer.startActivityIntentSender(iIntentSender); } else { mQueuedPendingIntent = iIntentSender; mQueuedIntent = null; } } + public void release() { + if (DEBUG) Log.v(TAG, "release() mActivityContainer=" + mActivityContainer + + " mSurface=" + mSurface); + if (mActivityContainer == null) { + Log.e(TAG, "Duplicate call to release"); + return; + } + mActivityContainer.release(); + mActivityContainer = null; + + if (mSurface != null) { + mSurface.release(); + mSurface = null; + } + + mTextureView.setSurfaceTextureListener(null); + } + private void attachToSurfaceWhenReady() { final SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture(); - if (mActivityContainer == null || surfaceTexture == null || mSurface != null) { + if (surfaceTexture == null || mSurface != null) { // Either not ready to attach, or already attached. return; } - WindowManager wm = (WindowManager)mActivity.getSystemService(Context.WINDOW_SERVICE); - DisplayMetrics metrics = new DisplayMetrics(); - wm.getDefaultDisplay().getMetrics(metrics); - mSurface = new Surface(surfaceTexture); try { - mActivityContainer.attachToSurface(mSurface, mWidth, mHeight, metrics.densityDpi); + mActivityContainer.setSurface(mSurface, mWidth, mHeight, mMetrics.densityDpi); } catch (RemoteException e) { mSurface.release(); mSurface = null; - throw new IllegalStateException( - "ActivityView: Unable to create ActivityContainer. " + e); + throw new RuntimeException("ActivityView: Unable to create ActivityContainer. " + e); } if (DEBUG) Log.v(TAG, "attachToSurfaceWhenReady: " + (mQueuedIntent != null || mQueuedPendingIntent != null ? "" : "no") + " queued intent"); if (mQueuedIntent != null) { - startActivity(mQueuedIntent); + mActivityContainer.startActivity(mQueuedIntent); mQueuedIntent = null; } else if (mQueuedPendingIntent != null) { - startActivityIntentSender(mQueuedPendingIntent); + mActivityContainer.startActivityIntentSender(mQueuedPendingIntent); mQueuedPendingIntent = null; } } - private void detach() { - if (DEBUG) Log.d(TAG, "detach: attached=" + isAttachedToDisplay()); - if (mSurface != null) { - try { - mActivityContainer.detachFromDisplay(); - } catch (RemoteException e) { - } - mSurface.release(); - mSurface = null; - } - } - private class ActivityViewSurfaceTextureListener implements SurfaceTextureListener { @Override public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { + if (mActivityContainer == null) { + return; + } if (DEBUG) Log.d(TAG, "onSurfaceTextureAvailable: width=" + width + " height=" + height); mWidth = width; mHeight = height; - if (mActivityContainer != null) { - attachToSurfaceWhenReady(); - } + attachToSurfaceWhenReady(); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) { + if (mActivityContainer == null) { + return; + } if (DEBUG) Log.d(TAG, "onSurfaceTextureSizeChanged: w=" + width + " h=" + height); } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { + if (mActivityContainer == null) { + return true; + } if (DEBUG) Log.d(TAG, "onSurfaceTextureDestroyed"); - detach(); + mSurface.release(); + mSurface = null; + try { + mActivityContainer.setSurface(null, mWidth, mHeight, mMetrics.densityDpi); + } catch (RemoteException e) { + throw new RuntimeException( + "ActivityView: Unable to set surface of ActivityContainer. " + e); + } return true; } @@ -282,4 +273,96 @@ public class ActivityView extends ViewGroup { } } + + private static class ActivityContainerCallback extends IActivityContainerCallback.Stub { + private final WeakReference<ActivityView> mActivityViewWeakReference; + + ActivityContainerCallback(ActivityView activityView) { + mActivityViewWeakReference = new WeakReference<ActivityView>(activityView); + } + + @Override + public void setVisible(IBinder container, boolean visible) { + if (DEBUG) Log.v(TAG, "setVisible(): container=" + container + " visible=" + visible + + " ActivityView=" + mActivityViewWeakReference.get()); + } + } + + private static class ActivityContainerWrapper { + private final IActivityContainer mIActivityContainer; + private final CloseGuard mGuard = CloseGuard.get(); + + ActivityContainerWrapper(IActivityContainer container) { + mIActivityContainer = container; + mGuard.open("release"); + } + + void attachToDisplay(int displayId) { + try { + mIActivityContainer.attachToDisplay(displayId); + } catch (RemoteException e) { + } + } + + void setSurface(Surface surface, int width, int height, int density) + throws RemoteException { + mIActivityContainer.setSurface(surface, width, height, density); + } + + int startActivity(Intent intent) { + try { + return mIActivityContainer.startActivity(intent); + } catch (RemoteException e) { + throw new RuntimeException("ActivityView: Unable to startActivity. " + e); + } + } + + int startActivityIntentSender(IIntentSender intentSender) { + try { + return mIActivityContainer.startActivityIntentSender(intentSender); + } catch (RemoteException e) { + throw new RuntimeException( + "ActivityView: Unable to startActivity from IntentSender. " + e); + } + } + + int getDisplayId() { + try { + return mIActivityContainer.getDisplayId(); + } catch (RemoteException e) { + return -1; + } + } + + boolean injectEvent(InputEvent event) { + try { + return mIActivityContainer.injectEvent(event); + } catch (RemoteException e) { + return false; + } + } + + void release() { + if (DEBUG) Log.v(TAG, "ActivityContainerWrapper: release called"); + try { + mIActivityContainer.release(); + mGuard.close(); + } catch (RemoteException e) { + } + } + + @Override + protected void finalize() throws Throwable { + if (DEBUG) Log.v(TAG, "ActivityContainerWrapper: finalize called"); + try { + if (mGuard != null) { + mGuard.warnIfOpen(); + release(); + } + } finally { + super.finalize(); + } + } + + } } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index f444680..fe532bf 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -67,11 +67,12 @@ import android.location.ILocationManager; import android.location.LocationManager; import android.media.AudioManager; import android.media.MediaRouter; -import android.media.session.MediaSessionManager; +import android.media.session.SessionManager; import android.net.ConnectivityManager; import android.net.IConnectivityManager; import android.net.INetworkPolicyManager; import android.net.NetworkPolicyManager; +import android.net.NetworkScoreManager; import android.net.Uri; import android.net.nsd.INsdManager; import android.net.nsd.NsdManager; @@ -638,7 +639,7 @@ class ContextImpl extends Context { registerService(MEDIA_SESSION_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { - return new MediaSessionManager(ctx); + return new SessionManager(ctx); } }); registerService(TRUST_SERVICE, new ServiceFetcher() { @@ -654,6 +655,12 @@ class ContextImpl extends Context { ITvInputManager service = ITvInputManager.Stub.asInterface(iBinder); return new TvInputManager(service, UserHandle.myUserId()); }}); + + registerService(NETWORK_SCORE_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + return new NetworkScoreManager(ctx); + } + }); } static ContextImpl getImpl(Context context) { @@ -1858,17 +1865,26 @@ class ContextImpl extends Context { } private String uriModeFlagToString(int uriModeFlags) { - switch (uriModeFlags) { - case Intent.FLAG_GRANT_READ_URI_PERMISSION | - Intent.FLAG_GRANT_WRITE_URI_PERMISSION: - return "read and write"; - case Intent.FLAG_GRANT_READ_URI_PERMISSION: - return "read"; - case Intent.FLAG_GRANT_WRITE_URI_PERMISSION: - return "write"; + StringBuilder builder = new StringBuilder(); + if ((uriModeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) { + builder.append("read and "); + } + if ((uriModeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) { + builder.append("write and "); + } + if ((uriModeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0) { + builder.append("persistable and "); + } + if ((uriModeFlags & Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) != 0) { + builder.append("prefix and "); + } + + if (builder.length() > 5) { + builder.setLength(builder.length() - 5); + return builder.toString(); + } else { + throw new IllegalArgumentException("Unknown permission mode flags: " + uriModeFlags); } - throw new IllegalArgumentException( - "Unknown permission mode flags: " + uriModeFlags); } private void enforceForUri( diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java new file mode 100644 index 0000000..aa097e0 --- /dev/null +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -0,0 +1,292 @@ +/* + * 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.app; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.ResultReceiver; +import android.transition.Transition; +import android.util.ArrayMap; +import android.util.Pair; +import android.view.View; +import android.view.ViewTreeObserver; +import android.view.Window; + +import java.util.ArrayList; + +/** + * This ActivityTransitionCoordinator is created by the Activity to manage + * the enter scene and shared element transfer as well as Activity#finishWithTransition + * exiting the Scene and transferring shared elements back to the called Activity. + */ +class EnterTransitionCoordinator extends ActivityTransitionCoordinator + implements ViewTreeObserver.OnPreDrawListener { + private static final String TAG = "EnterTransitionCoordinator"; + + // The background fade in/out duration. 150ms is pretty quick, but not abrupt. + private static final int FADE_BACKGROUND_DURATION_MS = 150; + + /** + * The shared element names sent by the ExitTransitionCoordinator and may be + * shared when exiting back. + */ + private ArrayList<String> mEnteringSharedElementNames; + + /** + * The Activity that has created this coordinator. This is used solely to make the + * Window translucent/opaque. + */ + private Activity mActivity; + + /** + * True if the Window was opaque at the start and we should make it opaque again after + * enter transitions have completed. + */ + private boolean mWasOpaque; + + /** + * During exit, is the background alpha == 0? + */ + private boolean mBackgroundFadedOut; + + /** + * During exit, has the shared element transition completed? + */ + private boolean mSharedElementTransitionComplete; + + /** + * Has the exit started? We don't want to accidentally exit multiple times. e.g. when + * back is hit twice during the exit animation. + */ + private boolean mExitTransitionStarted; + + /** + * Has the exit transition ended? + */ + private boolean mExitTransitionComplete; + + /** + * We only want to make the Window transparent and set the background alpha once. After that, + * the Activity won't want the same enter transition. + */ + private boolean mMadeReady; + + /** + * True if Window.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS) -- this means that + * enter and exit transitions should be active. + */ + private boolean mSupportsTransition; + + /** + * Background alpha animations may complete prior to receiving the callback for + * onTranslucentConversionComplete. If so, we need to immediately call to make the Window + * opaque. + */ + private boolean mMakeOpaque; + + public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver) { + super(activity.getWindow()); + mActivity = activity; + setRemoteResultReceiver(resultReceiver); + } + + public void readyToEnter() { + if (!mMadeReady) { + mMadeReady = true; + mSupportsTransition = getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS); + if (mSupportsTransition) { + Window window = getWindow(); + window.getDecorView().getViewTreeObserver().addOnPreDrawListener(this); + mActivity.overridePendingTransition(0, 0); + mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() { + @Override + public void onTranslucentConversionComplete(boolean drawComplete) { + mWasOpaque = true; + if (mMakeOpaque) { + mActivity.convertFromTranslucent(); + } + } + }); + Drawable background = getDecor().getBackground(); + if (background != null) { + window.setBackgroundDrawable(null); + background.setAlpha(0); + window.setBackgroundDrawable(background); + } + } + } + } + + @Override + protected void onRemoteSceneExitComplete() { + super.onRemoteSceneExitComplete(); + } + + @Override + protected void onTakeSharedElements(ArrayList<String> sharedElementNames, Bundle state) { + mEnteringSharedElementNames = new ArrayList<String>(); + mEnteringSharedElementNames.addAll(sharedElementNames); + super.onTakeSharedElements(sharedElementNames, state); + } + + @Override + protected void sharedElementTransitionComplete(Bundle bundle) { + notifySharedElementTransitionComplete(bundle); + } + + @Override + public boolean onPreDraw() { + getWindow().getDecorView().getViewTreeObserver().removeOnPreDrawListener(this); + setEnteringViews(readyEnteringViews()); + notifySetListener(); + onPrepareRestore(); + return false; + } + + @Override + public void startExit() { + if (!mExitTransitionStarted) { + mExitTransitionStarted = true; + startExitTransition(mEnteringSharedElementNames); + } + } + + @Override + protected Transition getViewsTransition() { + if (!mSupportsTransition) { + return null; + } + return getWindow().getEnterTransition(); + } + + @Override + protected Transition getSharedElementTransition() { + if (!mSupportsTransition) { + return null; + } + return getWindow().getSharedElementEnterTransition(); + } + + @Override + protected void onStartEnterTransition(Transition transition, ArrayList<View> enteringViews) { + Drawable background = getDecor().getBackground(); + if (background != null) { + ObjectAnimator animator = ObjectAnimator.ofInt(background, "alpha", 255); + animator.setDuration(FADE_BACKGROUND_DURATION_MS); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mMakeOpaque = true; + if (mWasOpaque) { + mActivity.convertFromTranslucent(); + } + } + }); + animator.start(); + } else if (mWasOpaque) { + transition.addListener(new Transition.TransitionListenerAdapter() { + @Override + public void onTransitionEnd(Transition transition) { + mMakeOpaque = true; + mActivity.convertFromTranslucent(); + } + }); + } + super.onStartEnterTransition(transition, enteringViews); + } + + public ArrayList<View> readyEnteringViews() { + ArrayList<View> enteringViews = new ArrayList<View>(); + getDecor().captureTransitioningViews(enteringViews); + if (getViewsTransition() != null) { + setViewVisibility(enteringViews, View.INVISIBLE); + } + return enteringViews; + } + + @Override + protected void startExitTransition(ArrayList<String> sharedElements) { + notifyPrepareRestore(); + + if (getDecor().getBackground() == null) { + ColorDrawable black = new ColorDrawable(0xFF000000); + getWindow().setBackgroundDrawable(black); + } + if (mWasOpaque) { + mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() { + @Override + public void onTranslucentConversionComplete(boolean drawComplete) { + fadeOutBackground(); + } + }); + } else { + fadeOutBackground(); + } + + super.startExitTransition(sharedElements); + } + + private void fadeOutBackground() { + ObjectAnimator animator = ObjectAnimator.ofInt(getDecor().getBackground(), + "alpha", 0); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mBackgroundFadedOut = true; + if (mSharedElementTransitionComplete) { + EnterTransitionCoordinator.super.onSharedElementTransitionEnd(); + } + } + }); + animator.setDuration(FADE_BACKGROUND_DURATION_MS); + animator.start(); + } + + @Override + protected void onExitTransitionEnd() { + mExitTransitionComplete = true; + exitAfterSharedElementTransition(); + super.onExitTransitionEnd(); + clearConnections(); + } + + @Override + protected void onSharedElementTransitionEnd() { + mSharedElementTransitionComplete = true; + if (mBackgroundFadedOut) { + super.onSharedElementTransitionEnd(); + } + } + + @Override + protected boolean allowOverlappingTransitions() { + return getWindow().getAllowEnterTransitionOverlap(); + } + + private void exitAfterSharedElementTransition() { + if (mSharedElementTransitionComplete && mExitTransitionComplete) { + mActivity.finish(); + if (mSupportsTransition) { + mActivity.overridePendingTransition(0, 0); + } + notifyExitTransitionComplete(); + } + } +} diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java new file mode 100644 index 0000000..d920787 --- /dev/null +++ b/core/java/android/app/ExitTransitionCoordinator.java @@ -0,0 +1,171 @@ +/* + * 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.app; + +import android.os.Bundle; +import android.transition.Transition; +import android.util.Pair; +import android.view.View; +import android.view.Window; + +import java.util.ArrayList; + +/** + * This ActivityTransitionCoordinator is created in ActivityOptions#makeSceneTransitionAnimation + * to govern the exit of the Scene and the shared elements when calling an Activity as well as + * the reentry of the Scene when coming back from the called Activity. + */ +class ExitTransitionCoordinator extends ActivityTransitionCoordinator { + private static final String TAG = "ExitTransitionCoordinator"; + + /** + * The Views that have exited and need to be restored to VISIBLE when returning to the + * normal state. + */ + private ArrayList<View> mTransitioningViews; + + /** + * Has the exit started? We don't want to accidentally exit multiple times. + */ + private boolean mExitStarted; + + /** + * Has the called Activity's ResultReceiver been set? + */ + private boolean mIsResultReceiverSet; + + /** + * Has the exit transition completed? If so, we can notify as soon as the ResultReceiver + * has been set. + */ + private boolean mExitComplete; + + /** + * Has the shared element transition completed? If so, we can notify as soon as the + * ResultReceiver has been set. + */ + private Bundle mSharedElements; + + /** + * Has the shared element transition completed? + */ + private boolean mSharedElementsComplete; + + public ExitTransitionCoordinator(Window window, + ActivityOptions.ActivityTransitionListener listener) { + super(window); + setActivityTransitionListener(listener); + } + + @Override + protected void onSetResultReceiver() { + mIsResultReceiverSet = true; + notifyCompletions(); + } + + @Override + protected void onPrepareRestore() { + makeTransitioningViewsInvisible(); + setEnteringViews(mTransitioningViews); + mTransitioningViews = null; + super.onPrepareRestore(); + } + + @Override + protected void onTakeSharedElements(ArrayList<String> sharedElementNames, Bundle state) { + super.onTakeSharedElements(sharedElementNames, state); + clearConnections(); + } + + @Override + protected void onActivityStopped() { + if (getViewsTransition() != null) { + setViewVisibility(mTransitioningViews, View.VISIBLE); + } + super.onActivityStopped(); + } + + @Override + protected void sharedElementTransitionComplete(Bundle bundle) { + mSharedElements = bundle; + mSharedElementsComplete = true; + notifyCompletions(); + } + + @Override + protected void onExitTransitionEnd() { + mExitComplete = true; + notifyCompletions(); + super.onExitTransitionEnd(); + } + + private void notifyCompletions() { + if (mIsResultReceiverSet && mSharedElementsComplete) { + if (mSharedElements != null) { + notifySharedElementTransitionComplete(mSharedElements); + mSharedElements = null; + } + if (mExitComplete) { + notifyExitTransitionComplete(); + } + } + } + + @Override + public void startExit() { + if (!mExitStarted) { + mExitStarted = true; + setSharedElements(); + startExitTransition(getSharedElementNames()); + } + } + + @Override + protected Transition getViewsTransition() { + if (!getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) { + return null; + } + return getWindow().getExitTransition(); + } + + @Override + protected Transition getSharedElementTransition() { + if (!getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) { + return null; + } + return getWindow().getSharedElementExitTransition(); + } + + private void makeTransitioningViewsInvisible() { + if (getViewsTransition() != null) { + setViewVisibility(mTransitioningViews, View.INVISIBLE); + } + } + + @Override + protected void onStartExitTransition(ArrayList<View> exitingViews) { + mTransitioningViews = new ArrayList<View>(); + if (exitingViews != null) { + mTransitioningViews.addAll(exitingViews); + } + mTransitioningViews.addAll(getSharedElements()); + } + + @Override + protected boolean allowOverlappingTransitions() { + return getWindow().getAllowExitTransitionOverlap(); + } +} diff --git a/core/java/android/app/IActivityContainer.aidl b/core/java/android/app/IActivityContainer.aidl index 5b80e06..cc3b10c 100644 --- a/core/java/android/app/IActivityContainer.aidl +++ b/core/java/android/app/IActivityContainer.aidl @@ -26,10 +26,10 @@ import android.view.Surface; /** @hide */ interface IActivityContainer { void attachToDisplay(int displayId); - void attachToSurface(in Surface surface, int width, int height, int density); - void detachFromDisplay(); + void setSurface(in Surface surface, int width, int height, int density); int startActivity(in Intent intent); int startActivityIntentSender(in IIntentSender intentSender); int getDisplayId(); boolean injectEvent(in InputEvent event); + void release(); } diff --git a/core/java/android/app/IActivityContainerCallback.aidl b/core/java/android/app/IActivityContainerCallback.aidl index 55c2001..7f6d2c3 100644 --- a/core/java/android/app/IActivityContainerCallback.aidl +++ b/core/java/android/app/IActivityContainerCallback.aidl @@ -20,5 +20,5 @@ import android.os.IBinder; /** @hide */ interface IActivityContainerCallback { - oneway void onLastActivityRemoved(IBinder container); + oneway void setVisible(IBinder container, boolean visible); } diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index bfbd339..52003f1 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -79,7 +79,7 @@ public interface IActivityManager extends IInterface { int flagsMask, int flagsValues, Bundle options) throws RemoteException; public boolean startNextMatchingActivity(IBinder callingActivity, Intent intent, Bundle options) throws RemoteException; - public boolean finishActivity(IBinder token, int code, Intent data) + public boolean finishActivity(IBinder token, int code, Intent data, boolean finishTask) throws RemoteException; public void finishSubActivity(IBinder token, String resultWho, int requestCode) throws RemoteException; public boolean finishActivityAffinity(IBinder token) throws RemoteException; diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index bb6eeda..8681f5c 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -23,6 +23,7 @@ import android.app.Notification; import android.content.ComponentName; import android.content.Intent; import android.service.notification.INotificationListener; +import android.service.notification.ZenModeConfig; /** {@hide} */ interface INotificationManager @@ -49,4 +50,7 @@ interface INotificationManager StatusBarNotification[] getActiveNotificationsFromListener(in INotificationListener token, in String[] keys); String[] getActiveNotificationKeysFromListener(in INotificationListener token); + + ZenModeConfig getZenModeConfig(); + boolean setZenModeConfig(in ZenModeConfig config); }
\ No newline at end of file diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index a6a04d1..5cf61a8 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -41,8 +41,6 @@ public class StatusBarManager { @Deprecated public static final int DISABLE_NOTIFICATION_TICKER = View.STATUS_BAR_DISABLE_NOTIFICATION_TICKER; - public static final int DISABLE_PRIVATE_NOTIFICATIONS - = View.STATUS_BAR_DISABLE_NOTIFICATION_TICKER; public static final int DISABLE_SYSTEM_INFO = View.STATUS_BAR_DISABLE_SYSTEM_INFO; public static final int DISABLE_HOME = View.STATUS_BAR_DISABLE_HOME; public static final int DISABLE_RECENT = View.STATUS_BAR_DISABLE_RECENT; diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 725f808..d7170e8 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -26,6 +26,7 @@ import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.os.Bundle; import android.os.Handler; import android.os.Process; import android.os.RemoteCallback; @@ -1815,6 +1816,27 @@ public class DevicePolicyManager { /** * @hide + * @param userId the userId of a managed profile profile. + * + * @return whether or not the managed profile is enabled. + * @throws IllegalArgumentException if the userId is invalid. + */ + public boolean isProfileEnabled(int userId) throws IllegalArgumentException { + if (mService != null) { + try { + return mService.isProfileEnabled(userId); + } catch (RemoteException re) { + Log.w(TAG, "Failed to get status for owner profile."); + throw new IllegalArgumentException( + "Failed to get status for owner profile.", re); + } + } + return true; + } + + + /** + * @hide * @return the human readable name of the organisation associated with this DPM or null if * one is not set. * @throws IllegalArgumentException if the userId is invalid. @@ -1880,4 +1902,59 @@ public class DevicePolicyManager { } } } + + /** + * Called by a profile or device owner to set the application restrictions for a given target + * application running in the managed profile. + * + * <p>The provided {@link Bundle} consists of key-value pairs, where the types of values may be + * {@link Boolean}, {@link String}, or {@link String}[]. The recommended format for key strings + * is "com.example.packagename/example-setting" to avoid naming conflicts with library + * components such as {@link android.webkit.WebView}. + * + * <p>The application restrictions are only made visible to the target application and the + * profile or device owner. + * + * <p>The calling device admin must be a profile or device owner; if it is not, a security + * exception will be thrown. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param packageName The name of the package to update restricted settings for. + * @param settings A {@link Bundle} to be parsed by the receiving application, conveying a new + * set of active restrictions. + */ + public void setApplicationRestrictions(ComponentName admin, String packageName, + Bundle settings) { + if (mService != null) { + try { + mService.setApplicationRestrictions(admin, packageName, settings); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + + /** + * Called by a profile or device owner to get the application restrictions for a given target + * application running in the managed profile. + * + * <p>The calling device admin must be a profile or device owner; if it is not, a security + * exception will be thrown. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param packageName The name of the package to fetch restricted settings of. + * @return {@link Bundle} of settings corresponding to what was set last time + * {@link DevicePolicyManager#setApplicationRestrictions} was called, or an empty {@link Bundle} + * if no restrictions have been set. + */ + public Bundle getApplicationRestrictions(ComponentName admin, String packageName) { + if (mService != null) { + try { + return mService.getApplicationRestrictions(admin, packageName); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + return null; + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index e4b2adc..85ba58b 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -19,6 +19,7 @@ package android.app.admin; import android.content.ComponentName; import android.content.IntentFilter; +import android.os.Bundle; import android.os.RemoteCallback; /** @@ -108,10 +109,14 @@ interface IDevicePolicyManager { String getProfileOwner(int userHandle); String getProfileOwnerName(int userHandle); void setProfileEnabled(in ComponentName who); + boolean isProfileEnabled(int userHandle); boolean installCaCert(in byte[] certBuffer); void uninstallCaCert(in byte[] certBuffer); void addPersistentPreferredActivity(in ComponentName admin, in IntentFilter filter, in ComponentName activity); void clearPackagePersistentPreferredActivities(in ComponentName admin, String packageName); + + void setApplicationRestrictions(in ComponentName who, in String packageName, in Bundle settings); + Bundle getApplicationRestrictions(in ComponentName who, in String packageName); } diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index f3c803d..5b41394 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -1641,7 +1641,7 @@ public abstract class ContentResolver { * * @see #getPersistedUriPermissions() */ - public void takePersistableUriPermission(Uri uri, int modeFlags) { + public void takePersistableUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags) { try { ActivityManagerNative.getDefault().takePersistableUriPermission(uri, modeFlags); } catch (RemoteException e) { @@ -1656,7 +1656,7 @@ public abstract class ContentResolver { * * @see #getPersistedUriPermissions() */ - public void releasePersistableUriPermission(Uri uri, int modeFlags) { + public void releasePersistableUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags) { try { ActivityManagerNative.getDefault().releasePersistableUriPermission(uri, modeFlags); } catch (RemoteException e) { diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 15cb9e9..cbb6cf5 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2387,10 +2387,10 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve a - * {@link android.media.session.MediaSessionManager} for managing media Sessions. + * {@link android.media.session.SessionManager} for managing media Sessions. * * @see #getSystemService - * @see android.media.session.MediaSessionManager + * @see android.media.session.SessionManager */ public static final String MEDIA_SESSION_SERVICE = "media_session"; @@ -2643,6 +2643,14 @@ public abstract class Context { public static final String TV_INPUT_SERVICE = "tv_input"; /** + * {@link android.net.NetworkScoreManager} for managing network scoring. + * @see #getSystemService + * @see android.net.NetworkScoreManager + * @hide + */ + public static final String NETWORK_SCORE_SERVICE = "network_score"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * @@ -2783,9 +2791,13 @@ public abstract class Context { * @param uri The Uri you would like to grant access to. * @param modeFlags The desired access modes. Any combination of * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION - * Intent.FLAG_GRANT_READ_URI_PERMISSION} or + * Intent.FLAG_GRANT_READ_URI_PERMISSION}, * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION - * Intent.FLAG_GRANT_WRITE_URI_PERMISSION}. + * Intent.FLAG_GRANT_WRITE_URI_PERMISSION}, + * {@link Intent#FLAG_GRANT_PERSISTABLE_URI_PERMISSION + * Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION}, or + * {@link Intent#FLAG_GRANT_PREFIX_URI_PERMISSION + * Intent.FLAG_GRANT_PREFIX_URI_PERMISSION}. * * @see #revokeUriPermission */ @@ -2798,7 +2810,8 @@ public abstract class Context { * Uri will match all previously granted Uris that are the same or a * sub-path of the given Uri. That is, revoking "content://foo/target" will * revoke both "content://foo/target" and "content://foo/target/sub", but not - * "content://foo". + * "content://foo". It will not remove any prefix grants that exist at a + * higher level. * * @param uri The Uri you would like to revoke access to. * @param modeFlags The desired access modes. Any combination of @@ -2809,7 +2822,7 @@ public abstract class Context { * * @see #grantUriPermission */ - public abstract void revokeUriPermission(Uri uri, @Intent.GrantUriMode int modeFlags); + public abstract void revokeUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags); /** * Determine whether a particular process and user ID has been granted @@ -2833,7 +2846,7 @@ public abstract class Context { * @see #checkCallingUriPermission */ public abstract int checkUriPermission(Uri uri, int pid, int uid, - @Intent.GrantUriMode int modeFlags); + @Intent.AccessUriMode int modeFlags); /** * Determine whether the calling process and user ID has been @@ -2856,7 +2869,7 @@ public abstract class Context { * * @see #checkUriPermission(Uri, int, int, int) */ - public abstract int checkCallingUriPermission(Uri uri, @Intent.GrantUriMode int modeFlags); + public abstract int checkCallingUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags); /** * Determine whether the calling process of an IPC <em>or you</em> has been granted @@ -2876,7 +2889,7 @@ public abstract class Context { * @see #checkCallingUriPermission */ public abstract int checkCallingOrSelfUriPermission(Uri uri, - @Intent.GrantUriMode int modeFlags); + @Intent.AccessUriMode int modeFlags); /** * Check both a Uri and normal permission. This allows you to perform @@ -2902,7 +2915,7 @@ public abstract class Context { */ public abstract int checkUriPermission(@Nullable Uri uri, @Nullable String readPermission, @Nullable String writePermission, int pid, int uid, - @Intent.GrantUriMode int modeFlags); + @Intent.AccessUriMode int modeFlags); /** * If a particular process and user ID has not been granted @@ -2924,7 +2937,7 @@ public abstract class Context { * @see #checkUriPermission(Uri, int, int, int) */ public abstract void enforceUriPermission( - Uri uri, int pid, int uid, @Intent.GrantUriMode int modeFlags, String message); + Uri uri, int pid, int uid, @Intent.AccessUriMode int modeFlags, String message); /** * If the calling process and user ID has not been granted @@ -2946,7 +2959,7 @@ public abstract class Context { * @see #checkCallingUriPermission(Uri, int) */ public abstract void enforceCallingUriPermission( - Uri uri, @Intent.GrantUriMode int modeFlags, String message); + Uri uri, @Intent.AccessUriMode int modeFlags, String message); /** * If the calling process of an IPC <em>or you</em> has not been @@ -2965,7 +2978,7 @@ public abstract class Context { * @see #checkCallingOrSelfUriPermission(Uri, int) */ public abstract void enforceCallingOrSelfUriPermission( - Uri uri, @Intent.GrantUriMode int modeFlags, String message); + Uri uri, @Intent.AccessUriMode int modeFlags, String message); /** * Enforce both a Uri and normal permission. This allows you to perform @@ -2990,7 +3003,7 @@ public abstract class Context { */ public abstract void enforceUriPermission( @Nullable Uri uri, @Nullable String readPermission, - @Nullable String writePermission, int pid, int uid, @Intent.GrantUriMode int modeFlags, + @Nullable String writePermission, int pid, int uid, @Intent.AccessUriMode int modeFlags, @Nullable String message); /** @hide */ diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index a7d5606..c0f04af 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -18,6 +18,7 @@ package android.content; import android.content.pm.ApplicationInfo; import android.util.ArraySet; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -864,8 +865,9 @@ public class Intent implements Parcelable, Cloneable { } // Migrate any clip data and flags from target. - int permFlags = target.getFlags() - & (FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION); + int permFlags = target.getFlags() & (FLAG_GRANT_READ_URI_PERMISSION + | FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_PERSISTABLE_URI_PERMISSION + | FLAG_GRANT_PREFIX_URI_PERMISSION); if (permFlags != 0) { ClipData targetClipData = target.getClipData(); if (targetClipData == null && target.getData() != null) { @@ -2306,6 +2308,16 @@ public class Intent implements Parcelable, Cloneable { = "android.intent.action.ADVANCED_SETTINGS"; /** + * Broadcast Action: Sent after application restrictions are changed. + * + * <p class="note">This is a protected intent that can only be sent + * by the system.</p> + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_APPLICATION_RESTRICTIONS_CHANGED = + "android.intent.action.APPLICATION_RESTRICTIONS_CHANGED"; + + /** * Broadcast Action: An outgoing call is about to be placed. * * <p>The Intent will have the following extra value:</p> @@ -2688,9 +2700,11 @@ public class Intent implements Parcelable, Cloneable { * take the persistable permissions using * {@link ContentResolver#takePersistableUriPermission(Uri, int)}. * <p> - * Callers can restrict document selection to a specific kind of data, such - * as photos, by setting one or more MIME types in - * {@link #EXTRA_MIME_TYPES}. + * Callers must indicate the acceptable document MIME types through + * {@link #setType(String)}. For example, to select photos, use + * {@code image/*}. If multiple disjoint MIME types are acceptable, define + * them in {@link #EXTRA_MIME_TYPES} and {@link #setType(String)} to + * {@literal *}/*. * <p> * If the caller can handle multiple returned items (the user performing * multiple selection), then you can specify {@link #EXTRA_ALLOW_MULTIPLE} @@ -2700,9 +2714,10 @@ public class Intent implements Parcelable, Cloneable { * returned URIs can be opened with * {@link ContentResolver#openFileDescriptor(Uri, String)}. * <p> - * Output: The URI of the item that was picked. This must be a - * {@code content://} URI so that any receiver can access it. If multiple - * documents were selected, they are returned in {@link #getClipData()}. + * Output: The URI of the item that was picked, returned in + * {@link #getData()}. This must be a {@code content://} URI so that any + * receiver can access it. If multiple documents were selected, they are + * returned in {@link #getClipData()}. * * @see DocumentsContract * @see #ACTION_CREATE_DOCUMENT @@ -2744,6 +2759,24 @@ public class Intent implements Parcelable, Cloneable { @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT"; + /** + * Activity Action: Allow the user to pick a directory. When invoked, the + * system will display the various {@link DocumentsProvider} instances + * installed on the device, letting the user navigate through them. Apps can + * fully manage documents within the returned directory. + * <p> + * To gain access to descendant (child, grandchild, etc) documents, use + * {@link DocumentsContract#buildDocumentViaUri(Uri, String)} and + * {@link DocumentsContract#buildChildDocumentsViaUri(Uri, String)} using + * the returned directory URI. + * <p> + * Output: The URI representing the selected directory. + * + * @see DocumentsContract + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_PICK_DIRECTORY = "android.intent.action.PICK_DIRECTORY"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent categories (see addCategory()). @@ -3322,6 +3355,7 @@ public class Intent implements Parcelable, Cloneable { * @see #ACTION_GET_CONTENT * @see #ACTION_OPEN_DOCUMENT * @see #ACTION_CREATE_DOCUMENT + * @see #ACTION_PICK_DIRECTORY */ public static final String EXTRA_LOCAL_ONLY = "android.intent.extra.LOCAL_ONLY"; @@ -3415,11 +3449,29 @@ public class Intent implements Parcelable, Cloneable { // Intent flags (see mFlags variable). /** @hide */ - @IntDef(flag = true, - value = {FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION}) + @IntDef(flag = true, value = { + FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION, + FLAG_GRANT_PERSISTABLE_URI_PERMISSION, FLAG_GRANT_PREFIX_URI_PERMISSION }) @Retention(RetentionPolicy.SOURCE) public @interface GrantUriMode {} + /** @hide */ + @IntDef(flag = true, value = { + FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION }) + @Retention(RetentionPolicy.SOURCE) + public @interface AccessUriMode {} + + /** + * Test if given mode flags specify an access mode, which must be at least + * read and/or write. + * + * @hide + */ + public static boolean isAccessUriMode(int modeFlags) { + return (modeFlags & (Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION)) != 0; + } + /** * If set, the recipient of this Intent will be granted permission to * perform read operations on the URI in the Intent's data and any URIs @@ -3481,6 +3533,17 @@ public class Intent implements Parcelable, Cloneable { public static final int FLAG_GRANT_PERSISTABLE_URI_PERMISSION = 0x00000040; /** + * When combined with {@link #FLAG_GRANT_READ_URI_PERMISSION} and/or + * {@link #FLAG_GRANT_WRITE_URI_PERMISSION}, the URI permission grant + * applies to any URI that is a prefix match against the original granted + * URI. (Without this flag, the URI must match exactly for access to be + * granted.) Another URI is considered a prefix match only when scheme, + * authority, and all path segments defined by the prefix are an exact + * match. + */ + public static final int FLAG_GRANT_PREFIX_URI_PERMISSION = 0x00000080; + + /** * If set, the new activity is not kept in the history stack. As soon as * the user navigates away from it, the activity is finished. This may also * be set with the {@link android.R.styleable#AndroidManifestActivity_noHistory @@ -3800,9 +3863,9 @@ public class Intent implements Parcelable, Cloneable { /** * @hide Flags that can't be changed with PendingIntent. */ - public static final int IMMUTABLE_FLAGS = - FLAG_GRANT_READ_URI_PERMISSION - | FLAG_GRANT_WRITE_URI_PERMISSION; + public static final int IMMUTABLE_FLAGS = FLAG_GRANT_READ_URI_PERMISSION + | FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_PERSISTABLE_URI_PERMISSION + | FLAG_GRANT_PREFIX_URI_PERMISSION; // --------------------------------------------------------------------- // --------------------------------------------------------------------- @@ -6340,6 +6403,8 @@ public class Intent implements Parcelable, Cloneable { * * @see #FLAG_GRANT_READ_URI_PERMISSION * @see #FLAG_GRANT_WRITE_URI_PERMISSION + * @see #FLAG_GRANT_PERSISTABLE_URI_PERMISSION + * @see #FLAG_GRANT_PREFIX_URI_PERMISSION * @see #FLAG_DEBUG_LOG_RESOLUTION * @see #FLAG_FROM_BACKGROUND * @see #FLAG_ACTIVITY_BROUGHT_TO_FRONT @@ -7371,9 +7436,10 @@ public class Intent implements Parcelable, Cloneable { // Since we migrated in child, we need to promote ClipData // and flags to ourselves to grant. setClipData(target.getClipData()); - addFlags(target.getFlags() - & (FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION - | FLAG_GRANT_PERSISTABLE_URI_PERMISSION)); + addFlags(target.getFlags() & (FLAG_GRANT_READ_URI_PERMISSION + | FLAG_GRANT_WRITE_URI_PERMISSION + | FLAG_GRANT_PERSISTABLE_URI_PERMISSION + | FLAG_GRANT_PREFIX_URI_PERMISSION)); return true; } else { return false; diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 9916476..c53e545 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -198,7 +198,7 @@ public class ActivityInfo extends ComponentInfo /** * @hide Bit in {@link #flags}: If set, this component will only be seen * by the primary user. Only works with broadcast receivers. Set from the - * {@link android.R.attr#primaryUserOnly} attribute. + * android.R.attr#primaryUserOnly attribute. */ public static final int FLAG_PRIMARY_USER_ONLY = 0x20000000; /** @@ -210,6 +210,13 @@ public class ActivityInfo extends ComponentInfo */ public static final int FLAG_SINGLE_USER = 0x40000000; /** + * @hide Bit in {@link #flags}: If set, this activity may be launched into an + * owned ActivityContainer such as that within an ActivityView. If not set and + * this activity is launched into such a container a SecurityExcception will be + * thrown. Set from the {@link android.R.attr#allowEmbedded} attribute. + */ + public static final int FLAG_ALLOW_EMBEDDED = 0x80000000; + /** * Options that have been set in the activity declaration in the * manifest. * These include: diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 8d8d220..8b6ca83 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -2452,6 +2452,12 @@ public class PackageParser { a.info.flags |= ActivityInfo.FLAG_PERSISTABLE; } + if (sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestActivity_allowEmbedded, + false)) { + a.info.flags |= ActivityInfo.FLAG_ALLOW_EMBEDDED; + } + if (!receiver) { if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestActivity_hardwareAccelerated, diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 9852776..528e119 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -1030,6 +1030,195 @@ public final class CameraCharacteristics extends CameraMetadata { new Key<Integer>("android.sensor.info.whiteLevel", int.class); /** + * <p>The standard reference illuminant used as the scene light source when + * calculating the {@link CameraCharacteristics#SENSOR_COLOR_TRANSFORM1 android.sensor.colorTransform1}, + * {@link CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM1 android.sensor.calibrationTransform1}, and + * {@link CameraCharacteristics#SENSOR_FORWARD_MATRIX1 android.sensor.forwardMatrix1} matrices.</p> + * <p>The values in this tag correspond to the values defined for the + * EXIF LightSource tag. These illuminants are standard light sources + * that are often used calibrating camera devices.</p> + * <p>If this tag is present, then {@link CameraCharacteristics#SENSOR_COLOR_TRANSFORM1 android.sensor.colorTransform1}, + * {@link CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM1 android.sensor.calibrationTransform1}, and + * {@link CameraCharacteristics#SENSOR_FORWARD_MATRIX1 android.sensor.forwardMatrix1} will also be present.</p> + * <p>Some devices may choose to provide a second set of calibration + * information for improved quality, including + * {@link CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2 android.sensor.referenceIlluminant2} and its corresponding matrices.</p> + * + * @see CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM1 + * @see CameraCharacteristics#SENSOR_COLOR_TRANSFORM1 + * @see CameraCharacteristics#SENSOR_FORWARD_MATRIX1 + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2 + * @see #SENSOR_REFERENCE_ILLUMINANT1_DAYLIGHT + * @see #SENSOR_REFERENCE_ILLUMINANT1_FLUORESCENT + * @see #SENSOR_REFERENCE_ILLUMINANT1_TUNGSTEN + * @see #SENSOR_REFERENCE_ILLUMINANT1_FLASH + * @see #SENSOR_REFERENCE_ILLUMINANT1_FINE_WEATHER + * @see #SENSOR_REFERENCE_ILLUMINANT1_CLOUDY_WEATHER + * @see #SENSOR_REFERENCE_ILLUMINANT1_SHADE + * @see #SENSOR_REFERENCE_ILLUMINANT1_DAYLIGHT_FLUORESCENT + * @see #SENSOR_REFERENCE_ILLUMINANT1_DAY_WHITE_FLUORESCENT + * @see #SENSOR_REFERENCE_ILLUMINANT1_COOL_WHITE_FLUORESCENT + * @see #SENSOR_REFERENCE_ILLUMINANT1_WHITE_FLUORESCENT + * @see #SENSOR_REFERENCE_ILLUMINANT1_STANDARD_A + * @see #SENSOR_REFERENCE_ILLUMINANT1_STANDARD_B + * @see #SENSOR_REFERENCE_ILLUMINANT1_STANDARD_C + * @see #SENSOR_REFERENCE_ILLUMINANT1_D55 + * @see #SENSOR_REFERENCE_ILLUMINANT1_D65 + * @see #SENSOR_REFERENCE_ILLUMINANT1_D75 + * @see #SENSOR_REFERENCE_ILLUMINANT1_D50 + * @see #SENSOR_REFERENCE_ILLUMINANT1_ISO_STUDIO_TUNGSTEN + */ + public static final Key<Integer> SENSOR_REFERENCE_ILLUMINANT1 = + new Key<Integer>("android.sensor.referenceIlluminant1", int.class); + + /** + * <p>The standard reference illuminant used as the scene light source when + * calculating the {@link CameraCharacteristics#SENSOR_COLOR_TRANSFORM2 android.sensor.colorTransform2}, + * {@link CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM2 android.sensor.calibrationTransform2}, and + * {@link CameraCharacteristics#SENSOR_FORWARD_MATRIX2 android.sensor.forwardMatrix2} matrices.</p> + * <p>See {@link CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 android.sensor.referenceIlluminant1} for more details. + * Valid values for this are the same as those given for the first + * reference illuminant.</p> + * <p>If this tag is present, then {@link CameraCharacteristics#SENSOR_COLOR_TRANSFORM2 android.sensor.colorTransform2}, + * {@link CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM2 android.sensor.calibrationTransform2}, and + * {@link CameraCharacteristics#SENSOR_FORWARD_MATRIX2 android.sensor.forwardMatrix2} will also be present.</p> + * + * @see CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM2 + * @see CameraCharacteristics#SENSOR_COLOR_TRANSFORM2 + * @see CameraCharacteristics#SENSOR_FORWARD_MATRIX2 + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 + */ + public static final Key<Byte> SENSOR_REFERENCE_ILLUMINANT2 = + new Key<Byte>("android.sensor.referenceIlluminant2", byte.class); + + /** + * <p>A per-device calibration transform matrix that maps from the + * reference sensor colorspace to the actual device sensor colorspace.</p> + * <p>This matrix is used to correct for per-device variations in the + * sensor colorspace, and is used for processing raw buffer data.</p> + * <p>The matrix is expressed as a 3x3 matrix in row-major-order, and + * contains a per-device calibration transform that maps colors + * from reference sensor color space (i.e. the "golden module" + * colorspace) into this camera device's native sensor color + * space under the first reference illuminant + * ({@link CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 android.sensor.referenceIlluminant1}).</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 + */ + public static final Key<Rational[]> SENSOR_CALIBRATION_TRANSFORM1 = + new Key<Rational[]>("android.sensor.calibrationTransform1", Rational[].class); + + /** + * <p>A per-device calibration transform matrix that maps from the + * reference sensor colorspace to the actual device sensor colorspace + * (this is the colorspace of the raw buffer data).</p> + * <p>This matrix is used to correct for per-device variations in the + * sensor colorspace, and is used for processing raw buffer data.</p> + * <p>The matrix is expressed as a 3x3 matrix in row-major-order, and + * contains a per-device calibration transform that maps colors + * from reference sensor color space (i.e. the "golden module" + * colorspace) into this camera device's native sensor color + * space under the second reference illuminant + * ({@link CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2 android.sensor.referenceIlluminant2}).</p> + * <p>This matrix will only be present if the second reference + * illuminant is present.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2 + */ + public static final Key<Rational[]> SENSOR_CALIBRATION_TRANSFORM2 = + new Key<Rational[]>("android.sensor.calibrationTransform2", Rational[].class); + + /** + * <p>A matrix that transforms color values from CIE XYZ color space to + * reference sensor color space.</p> + * <p>This matrix is used to convert from the standard CIE XYZ color + * space to the reference sensor colorspace, and is used when processing + * raw buffer data.</p> + * <p>The matrix is expressed as a 3x3 matrix in row-major-order, and + * contains a color transform matrix that maps colors from the CIE + * XYZ color space to the reference sensor color space (i.e. the + * "golden module" colorspace) under the first reference illuminant + * ({@link CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 android.sensor.referenceIlluminant1}).</p> + * <p>The white points chosen in both the reference sensor color space + * and the CIE XYZ colorspace when calculating this transform will + * match the standard white point for the first reference illuminant + * (i.e. no chromatic adaptation will be applied by this transform).</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 + */ + public static final Key<Rational[]> SENSOR_COLOR_TRANSFORM1 = + new Key<Rational[]>("android.sensor.colorTransform1", Rational[].class); + + /** + * <p>A matrix that transforms color values from CIE XYZ color space to + * reference sensor color space.</p> + * <p>This matrix is used to convert from the standard CIE XYZ color + * space to the reference sensor colorspace, and is used when processing + * raw buffer data.</p> + * <p>The matrix is expressed as a 3x3 matrix in row-major-order, and + * contains a color transform matrix that maps colors from the CIE + * XYZ color space to the reference sensor color space (i.e. the + * "golden module" colorspace) under the second reference illuminant + * ({@link CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2 android.sensor.referenceIlluminant2}).</p> + * <p>The white points chosen in both the reference sensor color space + * and the CIE XYZ colorspace when calculating this transform will + * match the standard white point for the second reference illuminant + * (i.e. no chromatic adaptation will be applied by this transform).</p> + * <p>This matrix will only be present if the second reference + * illuminant is present.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2 + */ + public static final Key<Rational[]> SENSOR_COLOR_TRANSFORM2 = + new Key<Rational[]>("android.sensor.colorTransform2", Rational[].class); + + /** + * <p>A matrix that transforms white balanced camera colors from the reference + * sensor colorspace to the CIE XYZ colorspace with a D50 whitepoint.</p> + * <p>This matrix is used to convert to the standard CIE XYZ colorspace, and + * is used when processing raw buffer data.</p> + * <p>This matrix is expressed as a 3x3 matrix in row-major-order, and contains + * a color transform matrix that maps white balanced colors from the + * reference sensor color space to the CIE XYZ color space with a D50 white + * point.</p> + * <p>Under the first reference illuminant ({@link CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 android.sensor.referenceIlluminant1}) + * this matrix is chosen so that the standard white point for this reference + * illuminant in the reference sensor colorspace is mapped to D50 in the + * CIE XYZ colorspace.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 + */ + public static final Key<Rational[]> SENSOR_FORWARD_MATRIX1 = + new Key<Rational[]>("android.sensor.forwardMatrix1", Rational[].class); + + /** + * <p>A matrix that transforms white balanced camera colors from the reference + * sensor colorspace to the CIE XYZ colorspace with a D50 whitepoint.</p> + * <p>This matrix is used to convert to the standard CIE XYZ colorspace, and + * is used when processing raw buffer data.</p> + * <p>This matrix is expressed as a 3x3 matrix in row-major-order, and contains + * a color transform matrix that maps white balanced colors from the + * reference sensor color space to the CIE XYZ color space with a D50 white + * point.</p> + * <p>Under the second reference illuminant ({@link CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2 android.sensor.referenceIlluminant2}) + * this matrix is chosen so that the standard white point for this reference + * illuminant in the reference sensor colorspace is mapped to D50 in the + * CIE XYZ colorspace.</p> + * <p>This matrix will only be present if the second reference + * illuminant is present.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2 + */ + public static final Key<Rational[]> SENSOR_FORWARD_MATRIX2 = + new Key<Rational[]>("android.sensor.forwardMatrix2", Rational[].class); + + /** * <p>Gain factor from electrons to raw units when * ISO=100</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 2c53f03..bb290af 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -570,6 +570,14 @@ public interface CameraDevice extends AutoCloseable { public static abstract class CaptureListener { /** + * This constant is used to indicate that no images were captured for + * the request. + * + * @hide + */ + public static final int NO_FRAMES_CAPTURED = -1; + + /** * This method is called when the camera device has started capturing * the output image for the request, at the beginning of image exposure. * @@ -693,9 +701,12 @@ public interface CameraDevice extends AutoCloseable { * The CameraDevice sending the callback. * @param sequenceId * A sequence ID returned by the {@link #capture} family of functions. - * @param frameNumber + * @param lastFrameNumber * The last frame number (returned by {@link CaptureResult#getFrameNumber} * or {@link CaptureFailure#getFrameNumber}) in the capture sequence. + * The last frame number may be equal to NO_FRAMES_CAPTURED if no images + * were captured for this sequence. This can happen, for example, when a + * repeating request or burst is cleared right after being set. * * @see CaptureResult#getFrameNumber() * @see CaptureFailure#getFrameNumber() @@ -703,7 +714,7 @@ public interface CameraDevice extends AutoCloseable { * @see CaptureFailure#getSequenceId() */ public void onCaptureSequenceCompleted(CameraDevice camera, - int sequenceId, int frameNumber) { + int sequenceId, int lastFrameNumber) { // default empty implementation } } diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 9b1bc53..ba8db3a 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -457,6 +457,110 @@ public abstract class CameraMetadata { public static final int SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGB = 4; // + // Enumeration values for CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 + // + + /** + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 + */ + public static final int SENSOR_REFERENCE_ILLUMINANT1_DAYLIGHT = 1; + + /** + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 + */ + public static final int SENSOR_REFERENCE_ILLUMINANT1_FLUORESCENT = 2; + + /** + * <p>Incandescent light</p> + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 + */ + public static final int SENSOR_REFERENCE_ILLUMINANT1_TUNGSTEN = 3; + + /** + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 + */ + public static final int SENSOR_REFERENCE_ILLUMINANT1_FLASH = 4; + + /** + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 + */ + public static final int SENSOR_REFERENCE_ILLUMINANT1_FINE_WEATHER = 9; + + /** + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 + */ + public static final int SENSOR_REFERENCE_ILLUMINANT1_CLOUDY_WEATHER = 10; + + /** + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 + */ + public static final int SENSOR_REFERENCE_ILLUMINANT1_SHADE = 11; + + /** + * <p>D 5700 - 7100K</p> + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 + */ + public static final int SENSOR_REFERENCE_ILLUMINANT1_DAYLIGHT_FLUORESCENT = 12; + + /** + * <p>N 4600 - 5400K</p> + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 + */ + public static final int SENSOR_REFERENCE_ILLUMINANT1_DAY_WHITE_FLUORESCENT = 13; + + /** + * <p>W 3900 - 4500K</p> + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 + */ + public static final int SENSOR_REFERENCE_ILLUMINANT1_COOL_WHITE_FLUORESCENT = 14; + + /** + * <p>WW 3200 - 3700K</p> + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 + */ + public static final int SENSOR_REFERENCE_ILLUMINANT1_WHITE_FLUORESCENT = 15; + + /** + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 + */ + public static final int SENSOR_REFERENCE_ILLUMINANT1_STANDARD_A = 17; + + /** + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 + */ + public static final int SENSOR_REFERENCE_ILLUMINANT1_STANDARD_B = 18; + + /** + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 + */ + public static final int SENSOR_REFERENCE_ILLUMINANT1_STANDARD_C = 19; + + /** + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 + */ + public static final int SENSOR_REFERENCE_ILLUMINANT1_D55 = 20; + + /** + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 + */ + public static final int SENSOR_REFERENCE_ILLUMINANT1_D65 = 21; + + /** + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 + */ + public static final int SENSOR_REFERENCE_ILLUMINANT1_D75 = 22; + + /** + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 + */ + public static final int SENSOR_REFERENCE_ILLUMINANT1_D50 = 23; + + /** + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 + */ + public static final int SENSOR_REFERENCE_ILLUMINANT1_ISO_STUDIO_TUNGSTEN = 24; + + // // Enumeration values for CameraCharacteristics#LED_AVAILABLE_LEDS // @@ -1731,110 +1835,6 @@ public abstract class CameraMetadata { public static final int LENS_STATE_MOVING = 1; // - // Enumeration values for CaptureResult#SENSOR_REFERENCE_ILLUMINANT - // - - /** - * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT - */ - public static final int SENSOR_REFERENCE_ILLUMINANT_DAYLIGHT = 1; - - /** - * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT - */ - public static final int SENSOR_REFERENCE_ILLUMINANT_FLUORESCENT = 2; - - /** - * <p>Incandescent light</p> - * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT - */ - public static final int SENSOR_REFERENCE_ILLUMINANT_TUNGSTEN = 3; - - /** - * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT - */ - public static final int SENSOR_REFERENCE_ILLUMINANT_FLASH = 4; - - /** - * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT - */ - public static final int SENSOR_REFERENCE_ILLUMINANT_FINE_WEATHER = 9; - - /** - * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT - */ - public static final int SENSOR_REFERENCE_ILLUMINANT_CLOUDY_WEATHER = 10; - - /** - * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT - */ - public static final int SENSOR_REFERENCE_ILLUMINANT_SHADE = 11; - - /** - * <p>D 5700 - 7100K</p> - * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT - */ - public static final int SENSOR_REFERENCE_ILLUMINANT_DAYLIGHT_FLUORESCENT = 12; - - /** - * <p>N 4600 - 5400K</p> - * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT - */ - public static final int SENSOR_REFERENCE_ILLUMINANT_DAY_WHITE_FLUORESCENT = 13; - - /** - * <p>W 3900 - 4500K</p> - * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT - */ - public static final int SENSOR_REFERENCE_ILLUMINANT_COOL_WHITE_FLUORESCENT = 14; - - /** - * <p>WW 3200 - 3700K</p> - * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT - */ - public static final int SENSOR_REFERENCE_ILLUMINANT_WHITE_FLUORESCENT = 15; - - /** - * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT - */ - public static final int SENSOR_REFERENCE_ILLUMINANT_STANDARD_A = 17; - - /** - * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT - */ - public static final int SENSOR_REFERENCE_ILLUMINANT_STANDARD_B = 18; - - /** - * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT - */ - public static final int SENSOR_REFERENCE_ILLUMINANT_STANDARD_C = 19; - - /** - * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT - */ - public static final int SENSOR_REFERENCE_ILLUMINANT_D55 = 20; - - /** - * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT - */ - public static final int SENSOR_REFERENCE_ILLUMINANT_D65 = 21; - - /** - * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT - */ - public static final int SENSOR_REFERENCE_ILLUMINANT_D75 = 22; - - /** - * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT - */ - public static final int SENSOR_REFERENCE_ILLUMINANT_D50 = 23; - - /** - * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT - */ - public static final int SENSOR_REFERENCE_ILLUMINANT_ISO_STUDIO_TUNGSTEN = 24; - - // // Enumeration values for CaptureResult#STATISTICS_SCENE_FLICKER // diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index b3bce3b..d8981c8 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -1607,77 +1607,14 @@ public final class CaptureResult extends CameraMetadata { new Key<Float>("android.sensor.temperature", float.class); /** - * <p>A reference illumination source roughly matching the current scene - * illumination, which is used to describe the sensor color space - * transformations.</p> - * <p>The values in this tag correspond to the values defined for the - * EXIF LightSource tag. These illuminants are standard light sources - * that are often used for calibrating camera devices.</p> - * @see #SENSOR_REFERENCE_ILLUMINANT_DAYLIGHT - * @see #SENSOR_REFERENCE_ILLUMINANT_FLUORESCENT - * @see #SENSOR_REFERENCE_ILLUMINANT_TUNGSTEN - * @see #SENSOR_REFERENCE_ILLUMINANT_FLASH - * @see #SENSOR_REFERENCE_ILLUMINANT_FINE_WEATHER - * @see #SENSOR_REFERENCE_ILLUMINANT_CLOUDY_WEATHER - * @see #SENSOR_REFERENCE_ILLUMINANT_SHADE - * @see #SENSOR_REFERENCE_ILLUMINANT_DAYLIGHT_FLUORESCENT - * @see #SENSOR_REFERENCE_ILLUMINANT_DAY_WHITE_FLUORESCENT - * @see #SENSOR_REFERENCE_ILLUMINANT_COOL_WHITE_FLUORESCENT - * @see #SENSOR_REFERENCE_ILLUMINANT_WHITE_FLUORESCENT - * @see #SENSOR_REFERENCE_ILLUMINANT_STANDARD_A - * @see #SENSOR_REFERENCE_ILLUMINANT_STANDARD_B - * @see #SENSOR_REFERENCE_ILLUMINANT_STANDARD_C - * @see #SENSOR_REFERENCE_ILLUMINANT_D55 - * @see #SENSOR_REFERENCE_ILLUMINANT_D65 - * @see #SENSOR_REFERENCE_ILLUMINANT_D75 - * @see #SENSOR_REFERENCE_ILLUMINANT_D50 - * @see #SENSOR_REFERENCE_ILLUMINANT_ISO_STUDIO_TUNGSTEN - */ - public static final Key<Integer> SENSOR_REFERENCE_ILLUMINANT = - new Key<Integer>("android.sensor.referenceIlluminant", int.class); - - /** - * <p>A per-device calibration transform matrix to be applied after the - * color space transform when rendering the raw image buffer.</p> - * <p>This matrix is expressed as a 3x3 matrix in row-major-order, and - * contains a per-device calibration transform that maps colors - * from reference camera color space (i.e. the "golden module" - * colorspace) into this camera device's linear native sensor color - * space for the current scene illumination and white balance choice.</p> - * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> - */ - public static final Key<Rational[]> SENSOR_CALIBRATION_TRANSFORM = - new Key<Rational[]>("android.sensor.calibrationTransform", Rational[].class); - - /** - * <p>A matrix that transforms color values from CIE XYZ color space to - * reference camera color space when rendering the raw image buffer.</p> - * <p>This matrix is expressed as a 3x3 matrix in row-major-order, and - * contains a color transform matrix that maps colors from the CIE - * XYZ color space to the reference camera raw color space (i.e. the - * "golden module" colorspace) for the current scene illumination and - * white balance choice.</p> - * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> - */ - public static final Key<Rational[]> SENSOR_COLOR_TRANSFORM = - new Key<Rational[]>("android.sensor.colorTransform", Rational[].class); - - /** - * <p>A matrix that transforms white balanced camera colors to the CIE XYZ - * colorspace with a D50 whitepoint.</p> - * <p>This matrix is expressed as a 3x3 matrix in row-major-order, and contains - * a color transform matrix that maps a unit vector in the linear native - * sensor color space to the D50 whitepoint in CIE XYZ color space.</p> - * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> - */ - public static final Key<Rational[]> SENSOR_FORWARD_MATRIX = - new Key<Rational[]>("android.sensor.forwardMatrix", Rational[].class); - - /** - * <p>The estimated white balance at the time of capture.</p> - * <p>The estimated white balance encoded as the RGB values of the - * perfectly neutral color point in the linear native sensor color space. - * The order of the values is R, G, B; where R is in the lowest index.</p> + * <p>The estimated camera neutral color in the native sensor colorspace at + * the time of capture.</p> + * <p>This value gives the neutral color point encoded as an RGB value in the + * native sensor color space. The neutral color point indicates the + * currently estimated white point of the scene illumination. It can be + * used to interpolate between the provided color transforms when + * processing raw sensor data.</p> + * <p>The order of the values is R, G, B; where R is in the lowest index.</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> */ public static final Key<Rational[]> SENSOR_NEUTRAL_COLOR_POINT = @@ -2197,8 +2134,8 @@ public final class CaptureResult extends CameraMetadata { * @see #SYNC_FRAME_NUMBER_UNKNOWN * @hide */ - public static final Key<Integer> SYNC_FRAME_NUMBER = - new Key<Integer>("android.sync.frameNumber", int.class); + public static final Key<Long> SYNC_FRAME_NUMBER = + new Key<Long>("android.sync.frameNumber", long.class); /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * End generated code diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java index cd44b51..ee2adac 100644 --- a/core/java/android/hardware/camera2/impl/CameraDevice.java +++ b/core/java/android/hardware/camera2/impl/CameraDevice.java @@ -292,6 +292,76 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { return submitCaptureRequest(requests, listener, handler, /*streaming*/false); } + /** + * This method checks lastFrameNumber returned from ICameraDeviceUser methods for + * starting and stopping repeating request and flushing. + * + * <p>If lastFrameNumber is NO_FRAMES_CAPTURED, it means that the request was never + * sent to HAL. Then onCaptureSequenceCompleted is immediately triggered. + * If lastFrameNumber is non-negative, then the requestId and lastFrameNumber pair + * is added to the list mFrameNumberRequestPairs.</p> + * + * @param requestId the request ID of the current repeating request. + * + * @param lastFrameNumber last frame number returned from binder. + */ + private void checkEarlyTriggerSequenceComplete( + final int requestId, final long lastFrameNumber) { + // lastFrameNumber being equal to NO_FRAMES_CAPTURED means that the request + // was never sent to HAL. Should trigger onCaptureSequenceCompleted immediately. + if (lastFrameNumber == CaptureListener.NO_FRAMES_CAPTURED) { + final CaptureListenerHolder holder; + int index = mCaptureListenerMap.indexOfKey(requestId); + holder = (index >= 0) ? mCaptureListenerMap.valueAt(index) : null; + if (holder != null) { + mCaptureListenerMap.removeAt(index); + if (DEBUG) { + Log.v(TAG, String.format( + "remove holder for requestId %d, " + + "because lastFrame is %d.", + requestId, lastFrameNumber)); + } + } + + if (holder != null) { + if (DEBUG) { + Log.v(TAG, "immediately trigger onCaptureSequenceCompleted because" + + " request did not reach HAL"); + } + + Runnable resultDispatch = new Runnable() { + @Override + public void run() { + if (!CameraDevice.this.isClosed()) { + if (DEBUG) { + Log.d(TAG, String.format( + "early trigger sequence complete for request %d", + requestId)); + } + if (lastFrameNumber < Integer.MIN_VALUE + || lastFrameNumber > Integer.MAX_VALUE) { + throw new AssertionError(lastFrameNumber + " cannot be cast to int"); + } + holder.getListener().onCaptureSequenceCompleted( + CameraDevice.this, + requestId, + (int)lastFrameNumber); + } + } + }; + holder.getHandler().post(resultDispatch); + } else { + Log.w(TAG, String.format( + "did not register listener to request %d", + requestId)); + } + } else { + mFrameNumberRequestPairs.add( + new SimpleEntry<Long, Integer>(lastFrameNumber, + requestId)); + } + } + private int submitCaptureRequest(List<CaptureRequest> requestList, CaptureListener listener, Handler handler, boolean repeating) throws CameraAccessException { @@ -313,7 +383,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { try { requestId = mRemoteDevice.submitRequestList(requestList, repeating, /*out*/lastFrameNumberRef); - if (!repeating) { + if (DEBUG) { Log.v(TAG, "last frame number " + lastFrameNumberRef.getNumber()); } } catch (CameraRuntimeException e) { @@ -322,25 +392,21 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { // impossible return -1; } + if (listener != null) { mCaptureListenerMap.put(requestId, new CaptureListenerHolder(listener, requestList, handler, repeating)); + } else { + if (DEBUG) { + Log.d(TAG, "Listen for request " + requestId + " is null"); + } } long lastFrameNumber = lastFrameNumberRef.getNumber(); - /** - * If it's the first repeating request, then returned lastFrameNumber can be - * negative. Otherwise, it should always be non-negative. - */ - if (((lastFrameNumber < 0) && (requestId > 0)) - || ((lastFrameNumber < 0) && (!repeating))) { - throw new AssertionError(String.format("returned bad frame number %d", - lastFrameNumber)); - } + if (repeating) { if (mRepeatingRequestId != REQUEST_ID_NONE) { - mFrameNumberRequestPairs.add( - new SimpleEntry<Long, Integer>(lastFrameNumber, mRepeatingRequestId)); + checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber); } mRepeatingRequestId = requestId; } else { @@ -395,12 +461,9 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { LongParcelable lastFrameNumberRef = new LongParcelable(); mRemoteDevice.cancelRequest(requestId, /*out*/lastFrameNumberRef); long lastFrameNumber = lastFrameNumberRef.getNumber(); - if ((lastFrameNumber < 0) && (requestId > 0)) { - throw new AssertionError(String.format("returned bad frame number %d", - lastFrameNumber)); - } - mFrameNumberRequestPairs.add( - new SimpleEntry<Long, Integer>(lastFrameNumber, requestId)); + + checkEarlyTriggerSequenceComplete(requestId, lastFrameNumber); + } catch (CameraRuntimeException e) { throw e.asChecked(); } catch (RemoteException e) { @@ -443,11 +506,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { mRemoteDevice.flush(/*out*/lastFrameNumberRef); if (mRepeatingRequestId != REQUEST_ID_NONE) { long lastFrameNumber = lastFrameNumberRef.getNumber(); - if (lastFrameNumber < 0) { - Log.e(TAG, String.format("returned bad frame number %d", lastFrameNumber)); - } - mFrameNumberRequestPairs.add( - new SimpleEntry<Long, Integer>(lastFrameNumber, mRepeatingRequestId)); + checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber); mRepeatingRequestId = REQUEST_ID_NONE; } } catch (CameraRuntimeException e) { @@ -582,8 +641,8 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { */ if (frameNumber != mCompletedFrameNumber + 1) { throw new AssertionError(String.format( - "result frame number %d comes out of order", - frameNumber)); + "result frame number %d comes out of order, should be %d + 1", + frameNumber, mCompletedFrameNumber)); } mCompletedFrameNumber++; } @@ -607,11 +666,18 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { final int requestId = frameNumberRequestPair.getValue(); final CaptureListenerHolder holder; synchronized (mLock) { - int index = CameraDevice.this.mCaptureListenerMap.indexOfKey(requestId); - holder = (index >= 0) ? CameraDevice.this.mCaptureListenerMap.valueAt(index) + int index = mCaptureListenerMap.indexOfKey(requestId); + holder = (index >= 0) ? mCaptureListenerMap.valueAt(index) : null; if (holder != null) { - CameraDevice.this.mCaptureListenerMap.removeAt(index); + mCaptureListenerMap.removeAt(index); + if (DEBUG) { + Log.v(TAG, String.format( + "remove holder for requestId %d, " + + "because lastFrame %d is <= %d", + requestId, frameNumberRequestPair.getKey(), + completedFrameNumber)); + } } } iter.remove(); @@ -628,11 +694,16 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { requestId)); } + long lastFrameNumber = frameNumberRequestPair.getKey(); + if (lastFrameNumber < Integer.MIN_VALUE + || lastFrameNumber > Integer.MAX_VALUE) { + throw new AssertionError(lastFrameNumber + + " cannot be cast to int"); + } holder.getListener().onCaptureSequenceCompleted( CameraDevice.this, requestId, - // TODO: this is problematic, crop long to int - frameNumberRequestPair.getKey().intValue()); + (int)lastFrameNumber); } } }; @@ -705,6 +776,9 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { } // Fire onCaptureSequenceCompleted + if (DEBUG) { + Log.v(TAG, String.format("got error frame %d", resultExtras.getFrameNumber())); + } mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/true); checkAndFireSequenceComplete(); @@ -764,20 +838,40 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { CaptureResultExtras resultExtras) throws RemoteException { int requestId = resultExtras.getRequestId(); if (DEBUG) { - Log.d(TAG, "Received result for id " + requestId); + Log.v(TAG, "Received result frame " + resultExtras.getFrameNumber() + " for id " + + requestId); + } + final CaptureListenerHolder holder; + synchronized (mLock) { + holder = CameraDevice.this.mCaptureListenerMap.get(requestId); } - final CaptureListenerHolder holder = - CameraDevice.this.mCaptureListenerMap.get(requestId); Boolean quirkPartial = result.get(CaptureResult.QUIRKS_PARTIAL_RESULT); boolean quirkIsPartialResult = (quirkPartial != null && quirkPartial); + // Update tracker (increment counter) when it's not a partial result. + if (!quirkIsPartialResult) { + mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/false); + } + // Check if we have a listener for this if (holder == null) { + if (DEBUG) { + Log.d(TAG, + "holder is null, early return at frame " + + resultExtras.getFrameNumber()); + } return; } - if (isClosed()) return; + if (isClosed()) { + if (DEBUG) { + Log.d(TAG, + "camera is closed, early return at frame " + + resultExtras.getFrameNumber()); + } + return; + } final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId()); final CaptureResult resultAsCapture = new CaptureResult(result, request, requestId); @@ -817,7 +911,6 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { // Fire onCaptureSequenceCompleted if (!quirkIsPartialResult) { - mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/false); checkAndFireSequenceComplete(); } } diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index a517bc5..79673b3 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -437,6 +437,14 @@ public final class DisplayManager { * The behavior of the virtual display depends on the flags that are provided * to this method. By default, virtual displays are created to be private, * non-presentation and unsecure. Permissions may be required to use certain flags. + * </p><p> + * As of {@link android.os.Build.VERSION_CODES#KITKAT_WATCH}, the surface may + * be attached or detached dynamically using {@link VirtualDisplay#setSurface}. + * Previously, the surface had to be non-null when {@link #createVirtualDisplay} + * was called and could not be changed for the lifetime of the display. + * </p><p> + * Detaching the surface that backs a virtual display has a similar effect to + * turning off the screen. * </p> * * @param name The name of the virtual display, must be non-empty. @@ -444,7 +452,7 @@ public final class DisplayManager { * @param height The height of the virtual display in pixels, must be greater than 0. * @param densityDpi The density of the virtual display in dpi, must be greater than 0. * @param surface The surface to which the content of the virtual display should - * be rendered, must be non-null. + * be rendered, or null if there is none initially. * @param flags A combination of virtual display flags: * {@link #VIRTUAL_DISPLAY_FLAG_PUBLIC}, {@link #VIRTUAL_DISPLAY_FLAG_PRESENTATION}, * {@link #VIRTUAL_DISPLAY_FLAG_SECURE}, or {@link #VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}. diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 3417430..a8d55e8 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -377,9 +377,6 @@ public final class DisplayManagerGlobal { throw new IllegalArgumentException("width, height, and densityDpi must be " + "greater than 0"); } - if (surface == null) { - throw new IllegalArgumentException("surface must not be null"); - } Binder token = new Binder(); int displayId; @@ -404,7 +401,15 @@ public final class DisplayManagerGlobal { } return null; } - return new VirtualDisplay(this, display, token); + return new VirtualDisplay(this, display, token, surface); + } + + public void setVirtualDisplaySurface(IBinder token, Surface surface) { + try { + mDm.setVirtualDisplaySurface(token, surface); + } catch (RemoteException ex) { + Log.w(TAG, "Failed to set virtual display surface.", ex); + } } public void releaseVirtualDisplay(IBinder token) { diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index 68eb13f..23c58c8 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -63,5 +63,8 @@ interface IDisplayManager { String name, int width, int height, int densityDpi, in Surface surface, int flags); // No permissions required but must be same Uid as the creator. + void setVirtualDisplaySurface(in IBinder token, in Surface surface); + + // No permissions required but must be same Uid as the creator. void releaseVirtualDisplay(in IBinder token); } diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java index 01e5bac..691d6a0 100644 --- a/core/java/android/hardware/display/VirtualDisplay.java +++ b/core/java/android/hardware/display/VirtualDisplay.java @@ -17,15 +17,18 @@ package android.hardware.display; import android.os.IBinder; import android.view.Display; +import android.view.Surface; /** * Represents a virtual display. The content of a virtual display is rendered to a * {@link android.view.Surface} that you must provide to {@link DisplayManager#createVirtualDisplay * createVirtualDisplay()}. - * <p>Because a virtual display renders to a surface provided by the application, it will be + * <p> + * Because a virtual display renders to a surface provided by the application, it will be * released automatically when the process terminates and all remaining windows on it will - * be forcibly removed. However, you should also explicitly call {@link #release} when you're - * done with it. + * be forcibly removed. However, you should also explicitly call {@link #release} when + * you're done with it. + * </p> * * @see DisplayManager#createVirtualDisplay */ @@ -33,11 +36,14 @@ public final class VirtualDisplay { private final DisplayManagerGlobal mGlobal; private final Display mDisplay; private IBinder mToken; + private Surface mSurface; - VirtualDisplay(DisplayManagerGlobal global, Display display, IBinder token) { + VirtualDisplay(DisplayManagerGlobal global, Display display, IBinder token, + Surface surface) { mGlobal = global; mDisplay = display; mToken = token; + mSurface = surface; } /** @@ -48,6 +54,32 @@ public final class VirtualDisplay { } /** + * Gets the surface that backs the virtual display. + */ + public Surface getSurface() { + return mSurface; + } + + /** + * Sets the surface that backs the virtual display. + * <p> + * Detaching the surface that backs a virtual display has a similar effect to + * turning off the screen. + * </p><p> + * It is still the caller's responsibility to destroy the surface after it has + * been detached. + * </p> + * + * @param surface The surface to set, or null to detach the surface from the virtual display. + */ + public void setSurface(Surface surface) { + if (mSurface != surface) { + mGlobal.setVirtualDisplaySurface(mToken, surface); + mSurface = surface; + } + } + + /** * Releases the virtual display and destroys its underlying surface. * <p> * All remaining windows on the virtual display will be forcibly removed @@ -63,6 +95,7 @@ public final class VirtualDisplay { @Override public String toString() { - return "VirtualDisplay{display=" + mDisplay + ", token=" + mToken + "}"; + return "VirtualDisplay{display=" + mDisplay + ", token=" + mToken + + ", surface=" + mSurface + "}"; } } diff --git a/core/java/android/net/INetworkScoreService.aidl b/core/java/android/net/INetworkScoreService.aidl new file mode 100644 index 0000000..a72d9a0 --- /dev/null +++ b/core/java/android/net/INetworkScoreService.aidl @@ -0,0 +1,49 @@ +/** + * 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.net; + +import android.net.ScoredNetwork; + +/** + * A service for updating network scores from a network scorer application. + * @hide + */ +interface INetworkScoreService +{ + /** + * Update scores. + * @return whether the update was successful. + * @throws SecurityException if the caller is not the current active scorer. + */ + boolean updateScores(in ScoredNetwork[] networks); + + /** + * Clear all scores. + * @return whether the clear was successful. + * @throws SecurityException if the caller is neither the current active scorer nor the scorer + * manager. + */ + boolean clearScores(); + + /** + * Set the active scorer and clear existing scores. + * @param packageName the package name of the new scorer to use. + * @return true if the operation succeeded, or false if the new package is not a valid scorer. + * @throws SecurityException if the caller is not the scorer manager. + */ + boolean setActiveScorer(in String packageName); +} diff --git a/core/java/android/net/NetworkKey.aidl b/core/java/android/net/NetworkKey.aidl new file mode 100644 index 0000000..637075f --- /dev/null +++ b/core/java/android/net/NetworkKey.aidl @@ -0,0 +1,19 @@ +/** + * 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.net; + +parcelable NetworkKey; diff --git a/core/java/android/net/NetworkKey.java b/core/java/android/net/NetworkKey.java new file mode 100644 index 0000000..bc19658 --- /dev/null +++ b/core/java/android/net/NetworkKey.java @@ -0,0 +1,128 @@ +/* + * 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.net; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Information which identifies a specific network. + * + * @hide + */ +// NOTE: Ideally, we would abstract away the details of what identifies a network of a specific +// type, so that all networks appear the same and can be scored without concern to the network type +// itself. However, because no such cross-type identifier currently exists in the Android framework, +// and because systems might obtain information about networks from sources other than Android +// devices, we need to provide identifying details about each specific network type (wifi, cell, +// etc.) so that clients can pull out these details depending on the type of network. +public class NetworkKey implements Parcelable { + + /** A wifi network, for which {@link #wifiKey} will be populated. */ + public static final int TYPE_WIFI = 1; + + /** + * The type of this network. + * @see #TYPE_WIFI + */ + public final int type; + + /** + * Information identifying a Wi-Fi network. Only set when {@link #type} equals + * {@link #TYPE_WIFI}. + */ + public final WifiKey wifiKey; + + /** + * Construct a new {@link NetworkKey} for a Wi-Fi network. + * @param wifiKey the {@link WifiKey} identifying this Wi-Fi network. + */ + public NetworkKey(WifiKey wifiKey) { + this.type = TYPE_WIFI; + this.wifiKey = wifiKey; + } + + private NetworkKey(Parcel in) { + type = in.readInt(); + switch (type) { + case TYPE_WIFI: + wifiKey = WifiKey.CREATOR.createFromParcel(in); + break; + default: + throw new IllegalArgumentException("Parcel has unknown type: " + type); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(type); + switch (type) { + case TYPE_WIFI: + wifiKey.writeToParcel(out, flags); + break; + default: + throw new IllegalStateException("NetworkKey has unknown type " + type); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + NetworkKey that = (NetworkKey) o; + + return type == that.type && Objects.equals(wifiKey, that.wifiKey); + } + + @Override + public int hashCode() { + return Objects.hash(type, wifiKey); + } + + @Override + public String toString() { + switch (type) { + case TYPE_WIFI: + return wifiKey.toString(); + default: + // Don't throw an exception here in case someone is logging this object in a catch + // block for debugging purposes. + return "InvalidKey"; + } + } + + public static final Parcelable.Creator<NetworkKey> CREATOR = + new Parcelable.Creator<NetworkKey>() { + @Override + public NetworkKey createFromParcel(Parcel in) { + return new NetworkKey(in); + } + + @Override + public NetworkKey[] newArray(int size) { + return new NetworkKey[size]; + } + }; +} diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java new file mode 100644 index 0000000..6dd56d9 --- /dev/null +++ b/core/java/android/net/NetworkScoreManager.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.net; + +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.content.Context; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; + +/** + * Class that manages communication between network subsystems and a network scorer. + * + * <p>You can get an instance of this class by calling + * {@link android.content.Context#getSystemService(String)}: + * + * <pre>NetworkScoreManager manager = + * (NetworkScoreManager) getSystemService(Context.NETWORK_SCORE_SERVICE)</pre> + * + * <p>A network scorer is any application which: + * <ul> + * <li>Declares the {@link android.Manifest.permission#SCORE_NETWORKS} permission. + * <li>Includes a receiver for {@link #ACTION_SCORE_NETWORKS} guarded by the + * {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission which scores networks + * and (eventually) calls {@link #updateScores} with the results. + * </ul> + * + * <p>The system keeps track of an active scorer application; at any time, only this application + * will receive {@link #ACTION_SCORE_NETWORKS} broadcasts and will be permitted to call + * {@link #updateScores}. Applications may determine the current active scorer with + * {@link #getActiveScorerPackage()} and request to change the active scorer by sending an + * {@link #ACTION_CHANGE_ACTIVE} broadcast with another scorer. + * + * @hide + */ +public class NetworkScoreManager { + /** + * Activity action: ask the user to change the active network scorer. This will show a dialog + * that asks the user whether they want to replace the current active scorer with the one + * specified in {@link #EXTRA_PACKAGE_NAME}. The activity will finish with RESULT_OK if the + * active scorer was changed or RESULT_CANCELED if it failed for any reason. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CHANGE_ACTIVE = "android.net.scoring.CHANGE_ACTIVE"; + + /** + * Extra used with {@link #ACTION_CHANGE_ACTIVE} to specify the new scorer package. Set with + * {@link android.content.Intent#putExtra(String, String)}. + */ + public static final String EXTRA_PACKAGE_NAME = "packageName"; + + /** + * Broadcast action: new network scores are being requested. This intent will only be delivered + * to the current active scorer app. That app is responsible for scoring the networks and + * calling {@link #updateScores} when complete. The networks to score are specified in + * {@link #EXTRA_NETWORKS_TO_SCORE}, and will generally consist of all networks which have been + * configured by the user as well as any open networks. + * + * <p class="note">This is a protected intent that can only be sent by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_SCORE_NETWORKS = "android.net.scoring.SCORE_NETWORKS"; + + /** + * Extra used with {@link #ACTION_SCORE_NETWORKS} to specify the networks to be scored, as an + * array of {@link NetworkKey}s. Can be obtained with + * {@link android.content.Intent#getParcelableArrayExtra(String)}}. + */ + public static final String EXTRA_NETWORKS_TO_SCORE = "networksToScore"; + + private final Context mContext; + private final INetworkScoreService mService; + + /** @hide */ + public NetworkScoreManager(Context context) { + mContext = context; + IBinder iBinder = ServiceManager.getService(Context.NETWORK_SCORE_SERVICE); + mService = INetworkScoreService.Stub.asInterface(iBinder); + } + + /** + * Obtain the package name of the current active network scorer. + * + * <p>At any time, only one scorer application will receive {@link #ACTION_SCORE_NETWORKS} + * broadcasts and be allowed to call {@link #updateScores}. Applications may use this method to + * determine the current scorer and offer the user the ability to select a different scorer via + * the {@link #ACTION_CHANGE_ACTIVE} intent. + * @return the full package name of the current active scorer, or null if there is no active + * scorer. + */ + public String getActiveScorerPackage() { + return NetworkScorerAppManager.getActiveScorer(mContext); + } + + /** + * Update network scores. + * + * <p>This may be called at any time to re-score active networks. Scores will generally be + * updated quickly, but if this method is called too frequently, the scores may be held and + * applied at a later time. + * + * @param networks the networks which have been scored by the scorer. + * @return whether the update was successful. + * @throws SecurityException if the caller is not the active scorer. + */ + public boolean updateScores(ScoredNetwork[] networks) throws SecurityException { + try { + return mService.updateScores(networks); + } catch (RemoteException e) { + return false; + } + } + + /** + * Clear network scores. + * + * <p>Should be called when all scores need to be invalidated, i.e. because the scoring + * algorithm has changed and old scores can no longer be compared to future scores. + * + * <p>Note that scores will be cleared automatically when the active scorer changes, as scores + * from one scorer cannot be compared to those from another scorer. + * + * @return whether the clear was successful. + * @throws SecurityException if the caller is not the active scorer or privileged. + */ + public boolean clearScores() throws SecurityException { + try { + return mService.clearScores(); + } catch (RemoteException e) { + return false; + } + } + + /** + * Set the active scorer to a new package and clear existing scores. + * + * @return true if the operation succeeded, or false if the new package is not a valid scorer. + * @throws SecurityException if the caller does not hold the + * {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission indicating that + * it can manage scorer applications. + * @hide + */ + public boolean setActiveScorer(String packageName) throws SecurityException { + try { + return mService.setActiveScorer(packageName); + } catch (RemoteException e) { + return false; + } + } +} diff --git a/core/java/android/net/NetworkScorerAppManager.java b/core/java/android/net/NetworkScorerAppManager.java new file mode 100644 index 0000000..3660e7a --- /dev/null +++ b/core/java/android/net/NetworkScorerAppManager.java @@ -0,0 +1,164 @@ +/* + * 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.net; + +import android.Manifest.permission; +import android.app.AppOpsManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.provider.Settings; +import android.provider.Settings.Global; +import android.text.TextUtils; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Internal class for managing the primary network scorer application. + * + * @hide + */ +public final class NetworkScorerAppManager { + private static final String TAG = "NetworkScorerAppManager"; + + private static final Intent SCORE_INTENT = + new Intent(NetworkScoreManager.ACTION_SCORE_NETWORKS); + + /** This class cannot be instantiated. */ + private NetworkScorerAppManager() {} + + /** + * Returns the list of available scorer app package names. + * + * <p>A network scorer is any application which: + * <ul> + * <li>Declares the {@link android.Manifest.permission#SCORE_NETWORKS} permission. + * <li>Includes a receiver for {@link NetworkScoreManager#ACTION_SCORE_NETWORKS} guarded by the + * {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission. + * </ul> + * + * @return the list of scorers, or the empty list if there are no valid scorers. + */ + public static Collection<String> getAllValidScorers(Context context) { + List<String> scorers = new ArrayList<>(); + + PackageManager pm = context.getPackageManager(); + List<ResolveInfo> receivers = pm.queryBroadcastReceivers(SCORE_INTENT, 0 /* flags */); + for (ResolveInfo receiver : receivers) { + // This field is a misnomer, see android.content.pm.ResolveInfo#activityInfo + final ActivityInfo receiverInfo = receiver.activityInfo; + if (receiverInfo == null) { + // Should never happen with queryBroadcastReceivers, but invalid nonetheless. + continue; + } + if (!permission.BROADCAST_SCORE_NETWORKS.equals(receiverInfo.permission)) { + // Receiver doesn't require the BROADCAST_SCORE_NETWORKS permission, which means + // anyone could trigger network scoring and flood the framework with score requests. + continue; + } + if (pm.checkPermission(permission.SCORE_NETWORKS, receiverInfo.packageName) != + PackageManager.PERMISSION_GRANTED) { + // Application doesn't hold the SCORE_NETWORKS permission, so the user never + // approved it as a network scorer. + continue; + } + scorers.add(receiverInfo.packageName); + } + + return scorers; + } + + /** + * Get the application package name to use for scoring networks. + * + * @return the scorer package or null if scoring is disabled (including if no scorer was ever + * selected) or if the previously-set scorer is no longer a valid scorer app (e.g. because + * it was disabled or uninstalled). + */ + public static String getActiveScorer(Context context) { + String scorerPackage = Settings.Global.getString(context.getContentResolver(), + Global.NETWORK_SCORER_APP); + if (isPackageValidScorer(context, scorerPackage)) { + return scorerPackage; + } else { + return null; + } + } + + /** + * Set the specified package as the default scorer application. + * + * <p>The caller must have permission to write to {@link Settings.Global}. + * + * @param context the context of the calling application + * @param packageName the packageName of the new scorer to use. If null, scoring will be + * disabled. Otherwise, the scorer will only be set if it is a valid scorer application. + * @return true if the scorer was changed, or false if the package is not a valid scorer. + */ + public static boolean setActiveScorer(Context context, String packageName) { + String oldPackageName = Settings.Global.getString(context.getContentResolver(), + Settings.Global.NETWORK_SCORER_APP); + if (TextUtils.equals(oldPackageName, packageName)) { + // No change. + return true; + } + + Log.i(TAG, "Changing network scorer from " + oldPackageName + " to " + packageName); + + if (packageName == null) { + Settings.Global.putString(context.getContentResolver(), Global.NETWORK_SCORER_APP, + null); + return true; + } else { + // We only make the change if the new package is valid. + if (isPackageValidScorer(context, packageName)) { + Settings.Global.putString(context.getContentResolver(), + Settings.Global.NETWORK_SCORER_APP, packageName); + return true; + } else { + Log.w(TAG, "Requested network scorer is not valid: " + packageName); + return false; + } + } + } + + /** Determine whether the application with the given UID is the enabled scorer. */ + public static boolean isCallerActiveScorer(Context context, int callingUid) { + String defaultApp = getActiveScorer(context); + if (defaultApp == null) { + return false; + } + AppOpsManager appOpsMgr = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + try { + appOpsMgr.checkPackage(callingUid, defaultApp); + return true; + } catch (SecurityException e) { + return false; + } + } + + /** Returns true if the given package is a valid scorer. */ + public static boolean isPackageValidScorer(Context context, String packageName) { + Collection<String> applications = getAllValidScorers(context); + return packageName != null && applications.contains(packageName); + } +} diff --git a/core/java/android/net/RssiCurve.java b/core/java/android/net/RssiCurve.java new file mode 100644 index 0000000..33e81c2 --- /dev/null +++ b/core/java/android/net/RssiCurve.java @@ -0,0 +1,156 @@ +/* + * 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.net; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Arrays; +import java.util.Objects; + +/** + * A curve defining the network score over a range of RSSI values. + * + * <p>For each RSSI bucket, the score may be any byte. Scores have no absolute meaning and are only + * considered relative to other scores assigned by the same scorer. Networks with no score are all + * considered equivalent and ranked below any network with a score. + * + * <p>For example, consider a curve starting at -110 dBm with a bucket width of 10 and the + * following buckets: {@code [-20, -10, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120]}. + * This represents a linear curve between -110 dBm and 30 dBm. It scores progressively higher at + * stronger signal strengths. + * + * <p>A network can be assigned a fixed score independent of RSSI by setting + * {@link #rssiBuckets} to a one-byte array whose element is the fixed score. {@link #start} + * should be set to the lowest RSSI value at which this fixed score should apply, and + * {@link #bucketWidth} should be set such that {@code start + bucketWidth} is equal to the + * highest RSSI value at which this fixed score should apply. + * + * <p>Note that RSSI values below -110 dBm or above 30 dBm are unlikely to cause any difference + * in connectivity behavior from those endpoints. That is, the connectivity framework will treat + * a network with a -120 dBm signal exactly as it would treat one with a -110 dBm signal. + * Therefore, graphs which specify scores outside this range may be truncated to this range by + * the system. + * + * @see ScoredNetwork + * @hide + */ +public class RssiCurve implements Parcelable { + + /** The starting dBm of the curve. */ + public final int start; + + /** The width of each RSSI bucket, in dBm. */ + public final int bucketWidth; + + /** The score for each RSSI bucket. */ + public final byte[] rssiBuckets; + + /** + * Construct a new {@link RssiCurve}. + * + * @param start the starting dBm of the curve. + * @param bucketWidth the width of each RSSI bucket, in dBm. + * @param rssiBuckets the score for each RSSI bucket. + */ + public RssiCurve(int start, int bucketWidth, byte[] rssiBuckets) { + this.start = start; + this.bucketWidth = bucketWidth; + if (rssiBuckets == null || rssiBuckets.length == 0) { + throw new IllegalArgumentException("rssiBuckets must be at least one element large."); + } + this.rssiBuckets = rssiBuckets; + } + + private RssiCurve(Parcel in) { + start = in.readInt(); + bucketWidth = in.readInt(); + int bucketCount = in.readInt(); + rssiBuckets = new byte[bucketCount]; + in.readByteArray(rssiBuckets); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(start); + out.writeInt(bucketWidth); + out.writeInt(rssiBuckets.length); + out.writeByteArray(rssiBuckets); + } + + /** + * Determine if two RSSI curves are defined in the same way. + * + * <p>Note that two curves can be equivalent but defined differently, e.g. if one bucket in one + * curve is split into two buckets in another. For the purpose of this method, these curves are + * not considered equal to each other. + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + RssiCurve rssiCurve = (RssiCurve) o; + + return start == rssiCurve.start && + bucketWidth == rssiCurve.bucketWidth && + Arrays.equals(rssiBuckets, rssiCurve.rssiBuckets); + } + + @Override + public int hashCode() { + return Objects.hash(start, bucketWidth, rssiBuckets); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("RssiCurve[start=") + .append(start) + .append(",bucketWidth=") + .append(bucketWidth); + + sb.append(",buckets="); + for (int i = 0; i < rssiBuckets.length; i++) { + sb.append(rssiBuckets[i]); + if (i < rssiBuckets.length - 1) { + sb.append(","); + } + } + sb.append("]"); + + return sb.toString(); + } + + public static final Creator<RssiCurve> CREATOR = + new Creator<RssiCurve>() { + @Override + public RssiCurve createFromParcel(Parcel in) { + return new RssiCurve(in); + } + + @Override + public RssiCurve[] newArray(int size) { + return new RssiCurve[size]; + } + }; +} diff --git a/core/java/android/net/ScoredNetwork.aidl b/core/java/android/net/ScoredNetwork.aidl new file mode 100644 index 0000000..f83db11 --- /dev/null +++ b/core/java/android/net/ScoredNetwork.aidl @@ -0,0 +1,19 @@ +/** + * 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.net; + +parcelable ScoredNetwork; diff --git a/core/java/android/net/ScoredNetwork.java b/core/java/android/net/ScoredNetwork.java new file mode 100644 index 0000000..7902313 --- /dev/null +++ b/core/java/android/net/ScoredNetwork.java @@ -0,0 +1,117 @@ +/* + * 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.net; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * A network identifier along with a score for the quality of that network. + * + * @hide + */ +public class ScoredNetwork implements Parcelable { + + /** A {@link NetworkKey} uniquely identifying this network. */ + public final NetworkKey networkKey; + + /** + * The {@link RssiCurve} representing the scores for this network based on the RSSI. + * + * <p>This field is optional and may be set to null to indicate that no score is available for + * this network at this time. Such networks, along with networks for which the scorer has not + * responded, are always prioritized below scored networks, regardless of the score. + */ + public final RssiCurve rssiCurve; + + /** + * Construct a new {@link ScoredNetwork}. + * + * @param networkKey the {@link NetworkKey} uniquely identifying this network. + * @param rssiCurve the {@link RssiCurve} representing the scores for this network based on the + * RSSI. This field is optional, and may be skipped to represent a network which the scorer + * has opted not to score at this time. Passing a null value here is strongly preferred to + * not returning any {@link ScoredNetwork} for a given {@link NetworkKey} because it + * indicates to the system not to request scores for this network in the future, although + * the scorer may choose to issue an out-of-band update at any time. + */ + public ScoredNetwork(NetworkKey networkKey, RssiCurve rssiCurve) { + this.networkKey = networkKey; + this.rssiCurve = rssiCurve; + } + + private ScoredNetwork(Parcel in) { + networkKey = NetworkKey.CREATOR.createFromParcel(in); + if (in.readByte() == 1) { + rssiCurve = RssiCurve.CREATOR.createFromParcel(in); + } else { + rssiCurve = null; + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + networkKey.writeToParcel(out, flags); + if (rssiCurve != null) { + out.writeByte((byte) 1); + rssiCurve.writeToParcel(out, flags); + } else { + out.writeByte((byte) 0); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ScoredNetwork that = (ScoredNetwork) o; + + return Objects.equals(networkKey, that.networkKey) && + Objects.equals(rssiCurve, that.rssiCurve); + } + + @Override + public int hashCode() { + return Objects.hash(networkKey, rssiCurve); + } + + @Override + public String toString() { + return "ScoredNetwork[key=" + networkKey + ",score=" + rssiCurve + "]"; + } + + public static final Parcelable.Creator<ScoredNetwork> CREATOR = + new Parcelable.Creator<ScoredNetwork>() { + @Override + public ScoredNetwork createFromParcel(Parcel in) { + return new ScoredNetwork(in); + } + + @Override + public ScoredNetwork[] newArray(int size) { + return new ScoredNetwork[size]; + } + }; +} diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index a7a8a0a..ce70455 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -21,6 +21,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.StrictMode; import android.util.Log; + import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; @@ -32,8 +33,10 @@ import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.RandomAccess; import java.util.Set; + import libcore.net.UriCodec; /** @@ -2338,4 +2341,29 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { StrictMode.onFileUriExposed(location); } } + + /** + * Test if this is a path prefix match against the given Uri. Verifies that + * scheme, authority, and atomic path segments match. + * + * @hide + */ + public boolean isPathPrefixMatch(Uri prefix) { + if (!Objects.equals(getScheme(), prefix.getScheme())) return false; + if (!Objects.equals(getAuthority(), prefix.getAuthority())) return false; + + List<String> seg = getPathSegments(); + List<String> prefixSeg = prefix.getPathSegments(); + + final int prefixSize = prefixSeg.size(); + if (seg.size() < prefixSize) return false; + + for (int i = 0; i < prefixSize; i++) { + if (!Objects.equals(seg.get(i), prefixSeg.get(i))) { + return false; + } + } + + return true; + } } diff --git a/core/java/android/net/WifiKey.java b/core/java/android/net/WifiKey.java new file mode 100644 index 0000000..9e92e89 --- /dev/null +++ b/core/java/android/net/WifiKey.java @@ -0,0 +1,122 @@ +/* + * 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.net; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; +import java.util.regex.Pattern; + +/** + * Information identifying a Wi-Fi network. + * @see NetworkKey + * + * @hide + */ +public class WifiKey implements Parcelable { + + // Patterns used for validation. + private static final Pattern SSID_PATTERN = Pattern.compile("(\".*\")|(0x[\\p{XDigit}]+)"); + private static final Pattern BSSID_PATTERN = + Pattern.compile("([\\p{XDigit}]{2}:){5}[\\p{XDigit}]{2}"); + + /** + * The service set identifier (SSID) of an 802.11 network. If the SSID can be decoded as + * UTF-8, it will be surrounded by double quotation marks. Otherwise, it will be a string of + * hex digits starting with 0x. + */ + public final String ssid; + + /** + * The basic service set identifier (BSSID) of an access point for this network. This will + * be in the form of a six-byte MAC address: {@code XX:XX:XX:XX:XX:XX}, where each X is a + * hexadecimal digit. + */ + public final String bssid; + + /** + * Construct a new {@link WifiKey} for the given Wi-Fi SSID/BSSID pair. + * + * @param ssid the service set identifier (SSID) of an 802.11 network. If the SSID can be + * decoded as UTF-8, it should be surrounded by double quotation marks. Otherwise, + * it should be a string of hex digits starting with 0x. + * @param bssid the basic service set identifier (BSSID) of this network's access point. + * This should be in the form of a six-byte MAC address: {@code XX:XX:XX:XX:XX:XX}, + * where each X is a hexadecimal digit. + * @throws IllegalArgumentException if either the SSID or BSSID is invalid. + */ + public WifiKey(String ssid, String bssid) { + if (!SSID_PATTERN.matcher(ssid).matches()) { + throw new IllegalArgumentException("Invalid ssid: " + ssid); + } + if (!BSSID_PATTERN.matcher(bssid).matches()) { + throw new IllegalArgumentException("Invalid bssid: " + bssid); + } + this.ssid = ssid; + this.bssid = bssid; + } + + private WifiKey(Parcel in) { + ssid = in.readString(); + bssid = in.readString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeString(ssid); + out.writeString(bssid); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + WifiKey wifiKey = (WifiKey) o; + + return Objects.equals(ssid, wifiKey.ssid) && Objects.equals(bssid, wifiKey.bssid); + } + + @Override + public int hashCode() { + return Objects.hash(ssid, bssid); + } + + @Override + public String toString() { + return "WifiKey[SSID=" + ssid + ",BSSID=" + bssid + "]"; + } + + public static final Creator<WifiKey> CREATOR = + new Creator<WifiKey>() { + @Override + public WifiKey createFromParcel(Parcel in) { + return new WifiKey(in); + } + + @Override + public WifiKey[] newArray(int size) { + return new WifiKey[size]; + } + }; +} diff --git a/core/java/android/nfc/INfcCardEmulation.aidl b/core/java/android/nfc/INfcCardEmulation.aidl index b8a5ba7..ae9796b 100644 --- a/core/java/android/nfc/INfcCardEmulation.aidl +++ b/core/java/android/nfc/INfcCardEmulation.aidl @@ -17,6 +17,7 @@ package android.nfc; import android.content.ComponentName; +import android.nfc.cardemulation.AidGroup; import android.nfc.cardemulation.ApduServiceInfo; import android.os.RemoteCallback; @@ -29,5 +30,8 @@ interface INfcCardEmulation boolean isDefaultServiceForAid(int userHandle, in ComponentName service, String aid); boolean setDefaultServiceForCategory(int userHandle, in ComponentName service, String category); boolean setDefaultForNextTap(int userHandle, in ComponentName service); + boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup); + AidGroup getAidGroupForService(int userHandle, in ComponentName service, String category); + boolean removeAidGroupForService(int userHandle, in ComponentName service, String category); List<ApduServiceInfo> getServices(int userHandle, in String category); } diff --git a/core/java/android/nfc/cardemulation/AidGroup.aidl b/core/java/android/nfc/cardemulation/AidGroup.aidl new file mode 100644 index 0000000..56d6fa5 --- /dev/null +++ b/core/java/android/nfc/cardemulation/AidGroup.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2013 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.nfc.cardemulation; + +parcelable AidGroup; diff --git a/core/java/android/nfc/cardemulation/AidGroup.java b/core/java/android/nfc/cardemulation/AidGroup.java new file mode 100644 index 0000000..2820f40 --- /dev/null +++ b/core/java/android/nfc/cardemulation/AidGroup.java @@ -0,0 +1,165 @@ +package android.nfc.cardemulation; + +import java.io.IOException; +import java.util.ArrayList; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +/** + * The AidGroup class represents a group of ISO/IEC 7816-4 + * Application Identifiers (AIDs) for a specific application + * category, along with a description resource describing + * the group. + */ +public final class AidGroup implements Parcelable { + /** + * The maximum number of AIDs that can be present in any one group. + */ + public static final int MAX_NUM_AIDS = 256; + + static final String TAG = "AidGroup"; + + final ArrayList<String> aids; + final String category; + final String description; + + /** + * Creates a new AidGroup object. + * + * @param aids The list of AIDs present in the group + * @param category The category of this group + */ + public AidGroup(ArrayList<String> aids, String category) { + if (aids == null || aids.size() == 0) { + throw new IllegalArgumentException("No AIDS in AID group."); + } + if (aids.size() > MAX_NUM_AIDS) { + throw new IllegalArgumentException("Too many AIDs in AID group."); + } + if (!isValidCategory(category)) { + throw new IllegalArgumentException("Category specified is not valid."); + } + this.aids = aids; + this.category = category; + this.description = null; + } + + AidGroup(String category, String description) { + this.aids = new ArrayList<String>(); + this.category = category; + this.description = description; + } + + /** + * @return the category of this AID group + */ + public String getCategory() { + return category; + } + + /** + * @return the list of AIDs in this group + */ + public ArrayList<String> getAids() { + return aids; + } + + @Override + public String toString() { + StringBuilder out = new StringBuilder("Category: " + category + + ", AIDs:"); + for (String aid : aids) { + out.append(aid); + out.append(", "); + } + return out.toString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(category); + dest.writeInt(aids.size()); + if (aids.size() > 0) { + dest.writeStringList(aids); + } + } + + public static final Parcelable.Creator<AidGroup> CREATOR = + new Parcelable.Creator<AidGroup>() { + + @Override + public AidGroup createFromParcel(Parcel source) { + String category = source.readString(); + int listSize = source.readInt(); + ArrayList<String> aidList = new ArrayList<String>(); + if (listSize > 0) { + source.readStringList(aidList); + } + return new AidGroup(aidList, category); + } + + @Override + public AidGroup[] newArray(int size) { + return new AidGroup[size]; + } + }; + + /** + * @hide + * Note: description is not serialized, since it's not localized + * and resource identifiers don't make sense to persist. + */ + static public AidGroup createFromXml(XmlPullParser parser) throws XmlPullParserException, IOException { + String category = parser.getAttributeValue(null, "category"); + ArrayList<String> aids = new ArrayList<String>(); + int eventType = parser.getEventType(); + int minDepth = parser.getDepth(); + while (eventType != XmlPullParser.END_DOCUMENT && parser.getDepth() >= minDepth) { + if (eventType == XmlPullParser.START_TAG) { + String tagName = parser.getName(); + if (tagName.equals("aid")) { + String aid = parser.getAttributeValue(null, "value"); + if (aid != null) { + aids.add(aid); + } + } else { + Log.d(TAG, "Ignorning unexpected tag: " + tagName); + } + } + eventType = parser.next(); + } + if (category != null && aids.size() > 0) { + return new AidGroup(aids, category); + } else { + return null; + } + } + + /** + * @hide + */ + public void writeAsXml(XmlSerializer out) throws IOException { + out.attribute(null, "category", category); + for (String aid : aids) { + out.startTag(null, "aid"); + out.attribute(null, "value", aid); + out.endTag(null, "aid"); + } + } + + boolean isValidCategory(String category) { + return CardEmulation.CATEGORY_PAYMENT.equals(category) || + CardEmulation.CATEGORY_OTHER.equals(category); + } +} diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java index d7ef4bc..94f35ed 100644 --- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java +++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java @@ -35,9 +35,12 @@ import android.util.Xml; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import java.io.FileDescriptor; import java.io.IOException; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; +import java.util.Map; /** * @hide @@ -56,24 +59,19 @@ public final class ApduServiceInfo implements Parcelable { final String mDescription; /** - * Convenience AID list - */ - final ArrayList<String> mAids; - - /** * Whether this service represents AIDs running on the host CPU */ final boolean mOnHost; /** - * All AID groups this service handles + * Mapping from category to static AID group */ - final ArrayList<AidGroup> mAidGroups; + final HashMap<String, AidGroup> mStaticAidGroups; /** - * Convenience hashmap + * Mapping from category to dynamic AID group */ - final HashMap<String, AidGroup> mCategoryToGroup; + final HashMap<String, AidGroup> mDynamicAidGroups; /** * Whether this service should only be started when the device is unlocked. @@ -86,26 +84,33 @@ public final class ApduServiceInfo implements Parcelable { final int mBannerResourceId; /** + * The uid of the package the service belongs to + */ + final int mUid; + /** * @hide */ public ApduServiceInfo(ResolveInfo info, boolean onHost, String description, - ArrayList<AidGroup> aidGroups, boolean requiresUnlock, int bannerResource) { + ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups, + boolean requiresUnlock, int bannerResource, int uid) { this.mService = info; this.mDescription = description; - this.mAidGroups = aidGroups; - this.mAids = new ArrayList<String>(); - this.mCategoryToGroup = new HashMap<String, AidGroup>(); + this.mStaticAidGroups = new HashMap<String, AidGroup>(); + this.mDynamicAidGroups = new HashMap<String, AidGroup>(); this.mOnHost = onHost; this.mRequiresDeviceUnlock = requiresUnlock; - for (AidGroup aidGroup : aidGroups) { - this.mCategoryToGroup.put(aidGroup.category, aidGroup); - this.mAids.addAll(aidGroup.aids); + for (AidGroup aidGroup : staticAidGroups) { + this.mStaticAidGroups.put(aidGroup.category, aidGroup); + } + for (AidGroup aidGroup : dynamicAidGroups) { + this.mDynamicAidGroups.put(aidGroup.category, aidGroup); } this.mBannerResourceId = bannerResource; + this.mUid = uid; } - public ApduServiceInfo(PackageManager pm, ResolveInfo info, boolean onHost) - throws XmlPullParserException, IOException { + public ApduServiceInfo(PackageManager pm, ResolveInfo info, boolean onHost) throws + XmlPullParserException, IOException { ServiceInfo si = info.serviceInfo; XmlResourceParser parser = null; try { @@ -163,10 +168,10 @@ public final class ApduServiceInfo implements Parcelable { sa.recycle(); } - mAidGroups = new ArrayList<AidGroup>(); - mCategoryToGroup = new HashMap<String, AidGroup>(); - mAids = new ArrayList<String>(); + mStaticAidGroups = new HashMap<String, AidGroup>(); + mDynamicAidGroups = new HashMap<String, AidGroup>(); mOnHost = onHost; + final int depth = parser.getDepth(); AidGroup currentGroup = null; @@ -179,14 +184,14 @@ public final class ApduServiceInfo implements Parcelable { final TypedArray groupAttrs = res.obtainAttributes(attrs, com.android.internal.R.styleable.AidGroup); // Get category of AID group - String groupDescription = groupAttrs.getString( - com.android.internal.R.styleable.AidGroup_description); String groupCategory = groupAttrs.getString( com.android.internal.R.styleable.AidGroup_category); + String groupDescription = groupAttrs.getString( + com.android.internal.R.styleable.AidGroup_description); if (!CardEmulation.CATEGORY_PAYMENT.equals(groupCategory)) { groupCategory = CardEmulation.CATEGORY_OTHER; } - currentGroup = mCategoryToGroup.get(groupCategory); + currentGroup = mStaticAidGroups.get(groupCategory); if (currentGroup != null) { if (!CardEmulation.CATEGORY_OTHER.equals(groupCategory)) { Log.e(TAG, "Not allowing multiple aid-groups in the " + @@ -200,9 +205,8 @@ public final class ApduServiceInfo implements Parcelable { } else if (eventType == XmlPullParser.END_TAG && "aid-group".equals(tagName) && currentGroup != null) { if (currentGroup.aids.size() > 0) { - if (!mCategoryToGroup.containsKey(currentGroup.category)) { - mAidGroups.add(currentGroup); - mCategoryToGroup.put(currentGroup.category, currentGroup); + if (!mStaticAidGroups.containsKey(currentGroup.category)) { + mStaticAidGroups.put(currentGroup.category, currentGroup); } } else { Log.e(TAG, "Not adding <aid-group> with empty or invalid AIDs"); @@ -216,7 +220,6 @@ public final class ApduServiceInfo implements Parcelable { toUpperCase(); if (isValidAid(aid) && !currentGroup.aids.contains(aid)) { currentGroup.aids.add(aid); - mAids.add(aid); } else { Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid); } @@ -228,6 +231,8 @@ public final class ApduServiceInfo implements Parcelable { } finally { if (parser != null) parser.close(); } + // Set uid + mUid = si.applicationInfo.uid; } public ComponentName getComponent() { @@ -235,16 +240,58 @@ public final class ApduServiceInfo implements Parcelable { mService.serviceInfo.name); } + /** + * Returns a consolidated list of AIDs from the AID groups + * registered by this service. Note that if a service has both + * a static (manifest-based) AID group for a category and a dynamic + * AID group, only the dynamically registered AIDs will be returned + * for that category. + * @return List of AIDs registered by the service + */ public ArrayList<String> getAids() { - return mAids; + final ArrayList<String> aids = new ArrayList<String>(); + for (AidGroup group : getAidGroups()) { + aids.addAll(group.aids); + } + return aids; } + /** + * Returns the registered AID group for this category. + */ + public AidGroup getDynamicAidGroupForCategory(String category) { + return mDynamicAidGroups.get(category); + } + + public boolean removeDynamicAidGroupForCategory(String category) { + return (mDynamicAidGroups.remove(category) != null); + } + + /** + * Returns a consolidated list of AID groups + * registered by this service. Note that if a service has both + * a static (manifest-based) AID group for a category and a dynamic + * AID group, only the dynamically registered AID group will be returned + * for that category. + * @return List of AIDs registered by the service + */ public ArrayList<AidGroup> getAidGroups() { - return mAidGroups; + final ArrayList<AidGroup> groups = new ArrayList<AidGroup>(); + for (Map.Entry<String, AidGroup> entry : mDynamicAidGroups.entrySet()) { + groups.add(entry.getValue()); + } + for (Map.Entry<String, AidGroup> entry : mStaticAidGroups.entrySet()) { + if (!mDynamicAidGroups.containsKey(entry.getKey())) { + // Consolidate AID groups - don't return static ones + // if a dynamic group exists for the category. + groups.add(entry.getValue()); + } + } + return groups; } public boolean hasCategory(String category) { - return mCategoryToGroup.containsKey(category); + return (mStaticAidGroups.containsKey(category) || mDynamicAidGroups.containsKey(category)); } public boolean isOnHost() { @@ -259,6 +306,14 @@ public final class ApduServiceInfo implements Parcelable { return mDescription; } + public int getUid() { + return mUid; + } + + public void setOrReplaceDynamicAidGroup(AidGroup aidGroup) { + mDynamicAidGroups.put(aidGroup.getCategory(), aidGroup); + } + public CharSequence loadLabel(PackageManager pm) { return mService.loadLabel(pm); } @@ -304,8 +359,12 @@ public final class ApduServiceInfo implements Parcelable { StringBuilder out = new StringBuilder("ApduService: "); out.append(getComponent()); out.append(", description: " + mDescription); - out.append(", AID Groups: "); - for (AidGroup aidGroup : mAidGroups) { + out.append(", Static AID Groups: "); + for (AidGroup aidGroup : mStaticAidGroups.values()) { + out.append(aidGroup.toString()); + } + out.append(", Dynamic AID Groups: "); + for (AidGroup aidGroup : mDynamicAidGroups.values()) { out.append(aidGroup.toString()); } return out.toString(); @@ -336,12 +395,17 @@ public final class ApduServiceInfo implements Parcelable { mService.writeToParcel(dest, flags); dest.writeString(mDescription); dest.writeInt(mOnHost ? 1 : 0); - dest.writeInt(mAidGroups.size()); - if (mAidGroups.size() > 0) { - dest.writeTypedList(mAidGroups); + dest.writeInt(mStaticAidGroups.size()); + if (mStaticAidGroups.size() > 0) { + dest.writeTypedList(new ArrayList<AidGroup>(mStaticAidGroups.values())); + } + dest.writeInt(mDynamicAidGroups.size()); + if (mDynamicAidGroups.size() > 0) { + dest.writeTypedList(new ArrayList<AidGroup>(mDynamicAidGroups.values())); } dest.writeInt(mRequiresDeviceUnlock ? 1 : 0); dest.writeInt(mBannerResourceId); + dest.writeInt(mUid); }; public static final Parcelable.Creator<ApduServiceInfo> CREATOR = @@ -351,14 +415,21 @@ public final class ApduServiceInfo implements Parcelable { ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source); String description = source.readString(); boolean onHost = (source.readInt() != 0) ? true : false; - ArrayList<AidGroup> aidGroups = new ArrayList<AidGroup>(); - int numGroups = source.readInt(); - if (numGroups > 0) { - source.readTypedList(aidGroups, AidGroup.CREATOR); + ArrayList<AidGroup> staticAidGroups = new ArrayList<AidGroup>(); + int numStaticGroups = source.readInt(); + if (numStaticGroups > 0) { + source.readTypedList(staticAidGroups, AidGroup.CREATOR); + } + ArrayList<AidGroup> dynamicAidGroups = new ArrayList<AidGroup>(); + int numDynamicGroups = source.readInt(); + if (numDynamicGroups > 0) { + source.readTypedList(dynamicAidGroups, AidGroup.CREATOR); } boolean requiresUnlock = (source.readInt() != 0) ? true : false; int bannerResource = source.readInt(); - return new ApduServiceInfo(info, onHost, description, aidGroups, requiresUnlock, bannerResource); + int uid = source.readInt(); + return new ApduServiceInfo(info, onHost, description, staticAidGroups, + dynamicAidGroups, requiresUnlock, bannerResource, uid); } @Override @@ -367,76 +438,22 @@ public final class ApduServiceInfo implements Parcelable { } }; - public static class AidGroup implements Parcelable { - final ArrayList<String> aids; - final String category; - final String description; - - AidGroup(ArrayList<String> aids, String category, String description) { - this.aids = aids; - this.category = category; - this.description = description; - } - - AidGroup(String category, String description) { - this.aids = new ArrayList<String>(); - this.category = category; - this.description = description; - } - - public String getCategory() { - return category; - } - - public ArrayList<String> getAids() { - return aids; - } - - @Override - public String toString() { - StringBuilder out = new StringBuilder("Category: " + category + - ", description: " + description + ", AIDs:"); - for (String aid : aids) { - out.append(aid); - out.append(", "); + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println(" " + getComponent() + + " (Description: " + getDescription() + ")"); + pw.println(" Static AID groups:"); + for (AidGroup group : mStaticAidGroups.values()) { + pw.println(" Category: " + group.category); + for (String aid : group.aids) { + pw.println(" AID: " + aid); } - return out.toString(); } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(category); - dest.writeString(description); - dest.writeInt(aids.size()); - if (aids.size() > 0) { - dest.writeStringList(aids); + pw.println(" Dynamic AID groups:"); + for (AidGroup group : mDynamicAidGroups.values()) { + pw.println(" Category: " + group.category); + for (String aid : group.aids) { + pw.println(" AID: " + aid); } } - - public static final Parcelable.Creator<ApduServiceInfo.AidGroup> CREATOR = - new Parcelable.Creator<ApduServiceInfo.AidGroup>() { - - @Override - public AidGroup createFromParcel(Parcel source) { - String category = source.readString(); - String description = source.readString(); - int listSize = source.readInt(); - ArrayList<String> aidList = new ArrayList<String>(); - if (listSize > 0) { - source.readStringList(aidList); - } - return new AidGroup(aidList, category, description); - } - - @Override - public AidGroup[] newArray(int size) { - return new AidGroup[size]; - } - }; } } diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java index 58d9616..41f039c 100644 --- a/core/java/android/nfc/cardemulation/CardEmulation.java +++ b/core/java/android/nfc/cardemulation/CardEmulation.java @@ -168,6 +168,10 @@ public final class CardEmulation { if (manager == null) { // Get card emu service INfcCardEmulation service = adapter.getCardEmulationService(); + if (service == null) { + Log.e(TAG, "This device does not implement the INfcCardEmulation interface."); + throw new UnsupportedOperationException(); + } manager = new CardEmulation(context, service); sCardEmus.put(context, manager); } @@ -271,6 +275,109 @@ public final class CardEmulation { } /** + * Registers a group of AIDs for the specified service. + * + * <p>If an AID group for that category was previously + * registered for this service (either statically + * through the manifest, or dynamically by using this API), + * that AID group will be replaced with this one. + * + * <p>Note that you can only register AIDs for a service that + * is running under the same UID as you are. Typically + * this means you need to call this from the same + * package as the service itself, though UIDs can also + * be shared between packages using shared UIDs. + * + * @param service The component name of the service + * @param aidGroup The group of AIDs to be registered + * @return whether the registration was successful. + */ + public boolean registerAidGroupForService(ComponentName service, AidGroup aidGroup) { + try { + return sService.registerAidGroupForService(UserHandle.myUserId(), service, aidGroup); + } catch (RemoteException e) { + // Try one more time + recoverService(); + if (sService == null) { + Log.e(TAG, "Failed to recover CardEmulationService."); + return false; + } + try { + return sService.registerAidGroupForService(UserHandle.myUserId(), service, + aidGroup); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to reach CardEmulationService."); + return false; + } + } + } + + /** + * Retrieves the currently registered AID group for the specified + * category for a service. + * + * <p>Note that this will only return AID groups that were dynamically + * registered using {@link #registerAidGroupForService(ComponentName, AidGroup)} + * method. It will *not* return AID groups that were statically registered + * in the manifest. + * + * @param service The component name of the service + * @param category The category of the AID group to be returned, e.g. {@link #CATEGORY_PAYMENT} + * @return The AID group, or null if it couldn't be found + */ + public AidGroup getAidGroupForService(ComponentName service, String category) { + try { + return sService.getAidGroupForService(UserHandle.myUserId(), service, category); + } catch (RemoteException e) { + recoverService(); + if (sService == null) { + Log.e(TAG, "Failed to recover CardEmulationService."); + return null; + } + try { + return sService.getAidGroupForService(UserHandle.myUserId(), service, category); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to recover CardEmulationService."); + return null; + } + } + } + + /** + * Removes a registered AID group for the specified category for the + * service provided. + * + * <p>Note that this will only remove AID groups that were dynamically + * registered using the {@link #registerAidGroupForService(ComponentName, AidGroup)} + * method. It will *not* remove AID groups that were statically registered in + * the manifest. If a dynamically registered AID group is removed using + * this method, and a statically registered AID group for the same category + * exists in the manifest, that AID group will become active again. + * + * @param service The component name of the service + * @param category The category of the AID group to be removed, e.g. {@link #CATEGORY_PAYMENT} + * @return whether the group was successfully removed. + */ + public boolean removeAidGroupForService(ComponentName service, String category) { + try { + return sService.removeAidGroupForService(UserHandle.myUserId(), service, category); + } catch (RemoteException e) { + // Try one more time + recoverService(); + if (sService == null) { + Log.e(TAG, "Failed to recover CardEmulationService."); + return false; + } + try { + return sService.removeAidGroupForService(UserHandle.myUserId(), service, category); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to reach CardEmulationService."); + return false; + } + } + } + + /** * @hide */ public boolean setDefaultServiceForCategory(ComponentName service, String category) { diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 426f21e..f05ddde 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -130,8 +130,8 @@ public abstract class BatteryStats implements Parcelable { // NOTE: Update this list if you add/change any stats above. // These characters are supposed to represent "total", "last", "current", // and "unplugged". They were shortened for efficiency sake. - private static final String[] STAT_NAMES = { "t", "l", "c", "u" }; - + private static final String[] STAT_NAMES = { "l", "c", "u" }; + /** * Bump the version on this if the checkin format changes. */ @@ -186,7 +186,7 @@ public abstract class BatteryStats implements Parcelable { * Returns the count associated with this Counter for the * selected type of statistics. * - * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT + * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT */ public abstract int getCountLocked(int which); @@ -205,7 +205,7 @@ public abstract class BatteryStats implements Parcelable { * Returns the count associated with this Counter for the * selected type of statistics. * - * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT + * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT */ public abstract long getCountLocked(int which); @@ -224,7 +224,7 @@ public abstract class BatteryStats implements Parcelable { * Returns the count associated with this Timer for the * selected type of statistics. * - * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT + * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT */ public abstract int getCountLocked(int which); @@ -233,7 +233,7 @@ public abstract class BatteryStats implements Parcelable { * selected type of statistics. * * @param elapsedRealtimeUs current elapsed realtime of system in microseconds - * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT + * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT * @return a time in microseconds */ public abstract long getTotalTimeLocked(long elapsedRealtimeUs, int which); @@ -385,27 +385,27 @@ public abstract class BatteryStats implements Parcelable { /** * Returns the total time (in 1/100 sec) spent executing in user code. * - * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT. */ public abstract long getUserTime(int which); /** * Returns the total time (in 1/100 sec) spent executing in system code. * - * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT. */ public abstract long getSystemTime(int which); /** * Returns the number of times the process has been started. * - * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT. */ public abstract int getStarts(int which); /** * Returns the cpu time spent in microseconds while the process was in the foreground. - * @param which one of STATS_TOTAL, STATS_LAST, STATS_CURRENT or STATS_UNPLUGGED + * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT. * @return foreground cpu time in microseconds */ public abstract long getForegroundTime(int which); @@ -414,7 +414,7 @@ public abstract class BatteryStats implements Parcelable { * Returns the approximate cpu time spent in microseconds, at a certain CPU speed. * @param speedStep the index of the CPU speed. This is not the actual speed of the * CPU. - * @param which one of STATS_TOTAL, STATS_LAST, STATS_CURRENT or STATS_UNPLUGGED + * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT. * @see BatteryStats#getCpuSpeedSteps() */ public abstract long getTimeAtCpuSpeedStep(int speedStep, int which); @@ -433,7 +433,7 @@ public abstract class BatteryStats implements Parcelable { * Returns the number of times this package has done something that could wake up the * device from sleep. * - * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT. */ public abstract int getWakeups(int which); @@ -451,7 +451,7 @@ public abstract class BatteryStats implements Parcelable { * Returns the amount of time spent started. * * @param batteryUptime elapsed uptime on battery in microseconds. - * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT. * @return */ public abstract long getStartTime(long batteryUptime, int which); @@ -459,14 +459,14 @@ public abstract class BatteryStats implements Parcelable { /** * Returns the total number of times startService() has been called. * - * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT. */ public abstract int getStarts(int which); /** * Returns the total number times the service has been launched. * - * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT. */ public abstract int getLaunches(int which); } @@ -1285,7 +1285,7 @@ public abstract class BatteryStats implements Parcelable { * Returns the total, last, or current battery uptime in microseconds. * * @param curTime the elapsed realtime in microseconds. - * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT. */ public abstract long computeBatteryUptime(long curTime, int which); @@ -1293,7 +1293,7 @@ public abstract class BatteryStats implements Parcelable { * Returns the total, last, or current battery realtime in microseconds. * * @param curTime the current elapsed realtime in microseconds. - * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT. */ public abstract long computeBatteryRealtime(long curTime, int which); @@ -1301,7 +1301,7 @@ public abstract class BatteryStats implements Parcelable { * Returns the total, last, or current battery screen off uptime in microseconds. * * @param curTime the elapsed realtime in microseconds. - * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT. */ public abstract long computeBatteryScreenOffUptime(long curTime, int which); @@ -1309,7 +1309,7 @@ public abstract class BatteryStats implements Parcelable { * Returns the total, last, or current battery screen off realtime in microseconds. * * @param curTime the current elapsed realtime in microseconds. - * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT. */ public abstract long computeBatteryScreenOffRealtime(long curTime, int which); @@ -1317,18 +1317,38 @@ public abstract class BatteryStats implements Parcelable { * Returns the total, last, or current uptime in microseconds. * * @param curTime the current elapsed realtime in microseconds. - * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT. */ public abstract long computeUptime(long curTime, int which); /** * Returns the total, last, or current realtime in microseconds. - * * + * * @param curTime the current elapsed realtime in microseconds. - * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT. */ public abstract long computeRealtime(long curTime, int which); - + + /** + * Compute an approximation for how much run time (in microseconds) is remaining on + * the battery. Returns -1 if no time can be computed: either there is not + * enough current data to make a decision, or the battery is currently + * charging. + * + * @param curTime The current elepsed realtime in microseconds. + */ + public abstract long computeBatteryTimeRemaining(long curTime); + + /** + * Compute an approximation for how much time (in microseconds) remains until the battery + * is fully charged. Returns -1 if no time can be computed: either there is not + * enough current data to make a decision, or the battery is currently + * discharging. + * + * @param curTime The current elepsed realtime in microseconds. + */ + public abstract long computeChargeTimeRemaining(long curTime); + public abstract Map<String, ? extends LongCounter> getWakeupReasonStats(); public abstract Map<String, ? extends Timer> getKernelWakelockStats(); @@ -1430,7 +1450,7 @@ public abstract class BatteryStats implements Parcelable { * @param timer a Timer object contining the wakelock times. * @param elapsedRealtimeUs the current on-battery time in microseconds. * @param name the name of the wakelock. - * @param which which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + * @param which which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT. * @param linePrefix a String to be prepended to each line of output. * @return the line prefix */ @@ -1464,7 +1484,7 @@ public abstract class BatteryStats implements Parcelable { * @param timer a Timer object contining the wakelock times. * @param elapsedRealtimeUs the current time in microseconds. * @param name the name of the wakelock. - * @param which which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + * @param which which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT. * @param linePrefix a String to be prepended to each line of output. * @return the line prefix */ @@ -1682,7 +1702,7 @@ public abstract class BatteryStats implements Parcelable { } } - BatteryStatsHelper helper = new BatteryStatsHelper(context); + BatteryStatsHelper helper = new BatteryStatsHelper(context, false); helper.create(this); helper.refreshStats(which, UserHandle.USER_ALL); List<BatterySipper> sippers = helper.getUsageList(); @@ -1929,6 +1949,8 @@ public abstract class BatteryStats implements Parcelable { final long whichBatteryScreenOffUptime = computeBatteryScreenOffUptime(rawUptime, which); final long whichBatteryScreenOffRealtime = computeBatteryScreenOffRealtime(rawRealtime, which); + final long batteryTimeRemaining = computeBatteryTimeRemaining(rawRealtime); + final long chargeTimeRemaining = computeChargeTimeRemaining(rawRealtime); StringBuilder sb = new StringBuilder(128); @@ -1963,7 +1985,20 @@ public abstract class BatteryStats implements Parcelable { sb.append("realtime, "); formatTimeMs(sb, totalUptime / 1000); sb.append("uptime"); - pw.println(sb.toString()); + if (batteryTimeRemaining >= 0) { + sb.setLength(0); + sb.append(prefix); + sb.append(" Battery time remaining: "); + formatTimeMs(sb, batteryTimeRemaining / 1000); + pw.println(sb.toString()); + } + if (chargeTimeRemaining >= 0) { + sb.setLength(0); + sb.append(prefix); + sb.append(" Charge time remaining: "); + formatTimeMs(sb, chargeTimeRemaining / 1000); + pw.println(sb.toString()); + } pw.print(" Start clock time: "); pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss", getStartClockTime()).toString()); @@ -2268,7 +2303,7 @@ public abstract class BatteryStats implements Parcelable { pw.println(); } - BatteryStatsHelper helper = new BatteryStatsHelper(context); + BatteryStatsHelper helper = new BatteryStatsHelper(context, false); helper.create(this); helper.refreshStats(which, UserHandle.USER_ALL); List<BatterySipper> sippers = helper.getUsageList(); @@ -2861,29 +2896,21 @@ public abstract class BatteryStats implements Parcelable { int oldTemp = -1; int oldVolt = -1; long lastTime = -1; + long firstTime = -1; - public void printNextItem(PrintWriter pw, HistoryItem rec, long now, boolean checkin, + public void printNextItem(PrintWriter pw, HistoryItem rec, long baseTime, boolean checkin, boolean verbose) { if (!checkin) { pw.print(" "); - if (now >= 0) { - TimeUtils.formatDuration(rec.time-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN); - } else { - TimeUtils.formatDuration(rec.time, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN); - } + TimeUtils.formatDuration(rec.time - baseTime, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN); pw.print(" ("); pw.print(rec.numReadInts); pw.print(") "); } else { if (lastTime < 0) { - if (now >= 0) { - pw.print("@"); - pw.print(rec.time-now); - } else { - pw.print(rec.time); - } + pw.print(rec.time - baseTime); } else { - pw.print(rec.time-lastTime); + pw.print(rec.time - lastTime); } lastTime = rec.time; } @@ -3132,10 +3159,27 @@ public abstract class BatteryStats implements Parcelable { pw.println("):"); HistoryPrinter hprinter = new HistoryPrinter(); long lastTime = -1; + long baseTime = -1; + boolean printed = false; while (getNextHistoryLocked(rec)) { lastTime = rec.time; + if (baseTime < 0) { + baseTime = lastTime; + } if (rec.time >= histStart) { - hprinter.printNextItem(pw, rec, histStart >= 0 ? -1 : now, false, + if (histStart >= 0 && !printed) { + if (rec.cmd == HistoryItem.CMD_CURRENT_TIME) { + printed = true; + } else if (rec.currentTime != 0) { + printed = true; + byte cmd = rec.cmd; + rec.cmd = HistoryItem.CMD_CURRENT_TIME; + hprinter.printNextItem(pw, rec, baseTime, false, + (flags&DUMP_VERBOSE) != 0); + rec.cmd = cmd; + } + } + hprinter.printNextItem(pw, rec, baseTime, false, (flags&DUMP_VERBOSE) != 0); } } @@ -3152,8 +3196,12 @@ public abstract class BatteryStats implements Parcelable { try { pw.println("Old battery History:"); HistoryPrinter hprinter = new HistoryPrinter(); + long baseTime = -1; while (getNextOldHistoryLocked(rec)) { - hprinter.printNextItem(pw, rec, now, false, (flags&DUMP_VERBOSE) != 0); + if (baseTime < 0) { + baseTime = rec.time; + } + hprinter.printNextItem(pw, rec, baseTime, false, (flags&DUMP_VERBOSE) != 0); } pw.println(); } finally { @@ -3226,20 +3274,42 @@ public abstract class BatteryStats implements Parcelable { pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(','); pw.print(HISTORY_STRING_POOL); pw.print(','); pw.print(i); - pw.print(','); - pw.print(getHistoryTagPoolString(i)); - pw.print(','); + pw.print(","); pw.print(getHistoryTagPoolUid(i)); + pw.print(",\""); + String str = getHistoryTagPoolString(i); + str = str.replace("\\", "\\\\"); + str = str.replace("\"", "\\\""); + pw.print(str); + pw.print("\""); pw.println(); } HistoryPrinter hprinter = new HistoryPrinter(); long lastTime = -1; + long baseTime = -1; + boolean printed = false; while (getNextHistoryLocked(rec)) { lastTime = rec.time; + if (baseTime < 0) { + baseTime = lastTime; + } if (rec.time >= histStart) { + if (histStart >= 0 && !printed) { + if (rec.cmd == HistoryItem.CMD_CURRENT_TIME) { + printed = true; + } else if (rec.currentTime != 0) { + printed = true; + byte cmd = rec.cmd; + rec.cmd = HistoryItem.CMD_CURRENT_TIME; + pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(','); + pw.print(HISTORY_DATA); pw.print(','); + hprinter.printNextItem(pw, rec, baseTime, true, false); + rec.cmd = cmd; + } + } pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(','); pw.print(HISTORY_DATA); pw.print(','); - hprinter.printNextItem(pw, rec, histStart >= 0 ? -1 : now, true, false); + hprinter.printNextItem(pw, rec, baseTime, true, false); } } if (histStart >= 0) { diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 7f1a2e4..19be2c8 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -476,13 +476,18 @@ public class Build { public static final int KITKAT = 19; /** + * Android 4.5: KitKat for watches, snacks on the run. + */ + public static final int KITKAT_WATCH = CUR_DEVELOPMENT; // STOPSHIP: update API level + + /** * L! * * <p>Applications targeting this or a later release will get these * new changes in behavior:</p> * <ul> * <li> {@link android.content.Context#bindService Context.bindService} now - * requires an explicit Intent, and will throw an exception if given an explicit + * requires an explicit Intent, and will throw an exception if given an implicit * Intent.</li> * </ul> */ diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java index af57507..c85e418 100644 --- a/core/java/android/os/Bundle.java +++ b/core/java/android/os/Bundle.java @@ -17,7 +17,6 @@ package android.os; import android.util.ArrayMap; -import android.util.Log; import android.util.SparseArray; import java.io.Serializable; @@ -29,47 +28,25 @@ import java.util.Set; * A mapping from String values to various Parcelable types. * */ -public final class Bundle implements Parcelable, Cloneable { - private static final String TAG = "Bundle"; - static final boolean DEBUG = false; +public final class Bundle extends CommonBundle { public static final Bundle EMPTY; - - static final int BUNDLE_MAGIC = 0x4C444E42; // 'B' 'N' 'D' 'L' static final Parcel EMPTY_PARCEL; static { EMPTY = new Bundle(); EMPTY.mMap = ArrayMap.EMPTY; - EMPTY_PARCEL = Parcel.obtain(); + EMPTY_PARCEL = CommonBundle.EMPTY_PARCEL; } - // Invariant - exactly one of mMap / mParcelledData will be null - // (except inside a call to unparcel) - - /* package */ ArrayMap<String, Object> mMap = null; - - /* - * If mParcelledData is non-null, then mMap will be null and the - * data are stored as a Parcel containing a Bundle. When the data - * are unparcelled, mParcelledData willbe set to null. - */ - /* package */ Parcel mParcelledData = null; - private boolean mHasFds = false; private boolean mFdsKnown = true; private boolean mAllowFds = true; /** - * The ClassLoader used when unparcelling data from mParcelledData. - */ - private ClassLoader mClassLoader; - - /** * Constructs a new, empty Bundle. */ public Bundle() { - mMap = new ArrayMap<String, Object>(); - mClassLoader = getClass().getClassLoader(); + super(); } /** @@ -79,11 +56,17 @@ public final class Bundle implements Parcelable, Cloneable { * @param parcelledData a Parcel containing a Bundle */ Bundle(Parcel parcelledData) { - readFromParcel(parcelledData); + super(parcelledData); + + mHasFds = mParcelledData.hasFileDescriptors(); + mFdsKnown = true; } /* package */ Bundle(Parcel parcelledData, int length) { - readFromParcelInner(parcelledData, length); + super(parcelledData, length); + + mHasFds = mParcelledData.hasFileDescriptors(); + mFdsKnown = true; } /** @@ -94,8 +77,7 @@ public final class Bundle implements Parcelable, Cloneable { * inside of the Bundle. */ public Bundle(ClassLoader loader) { - mMap = new ArrayMap<String, Object>(); - mClassLoader = loader; + super(loader); } /** @@ -105,8 +87,7 @@ public final class Bundle implements Parcelable, Cloneable { * @param capacity the initial capacity of the Bundle */ public Bundle(int capacity) { - mMap = new ArrayMap<String, Object>(capacity); - mClassLoader = getClass().getClassLoader(); + super(capacity); } /** @@ -116,27 +97,20 @@ public final class Bundle implements Parcelable, Cloneable { * @param b a Bundle to be copied. */ public Bundle(Bundle b) { - if (b.mParcelledData != null) { - if (b.mParcelledData == EMPTY_PARCEL) { - mParcelledData = EMPTY_PARCEL; - } else { - mParcelledData = Parcel.obtain(); - mParcelledData.appendFrom(b.mParcelledData, 0, b.mParcelledData.dataSize()); - mParcelledData.setDataPosition(0); - } - } else { - mParcelledData = null; - } - - if (b.mMap != null) { - mMap = new ArrayMap<String, Object>(b.mMap); - } else { - mMap = null; - } + super(b); mHasFds = b.mHasFds; mFdsKnown = b.mFdsKnown; - mClassLoader = b.mClassLoader; + } + + /** + * Constructs a Bundle containing a copy of the mappings from the given + * PersistableBundle. + * + * @param b a Bundle to be copied. + */ + public Bundle(PersistableBundle b) { + super(b); } /** @@ -145,37 +119,17 @@ public final class Bundle implements Parcelable, Cloneable { * @hide */ public static Bundle forPair(String key, String value) { - // TODO: optimize this case. Bundle b = new Bundle(1); b.putString(key, value); return b; } /** - * TODO: optimize this later (getting just the value part of a Bundle - * with a single pair) once Bundle.forPair() above is implemented - * with a special single-value Map implementation/serialization. - * - * Note: value in single-pair Bundle may be null. - * * @hide */ + @Override public String getPairValue() { - unparcel(); - int size = mMap.size(); - if (size > 1) { - Log.w(TAG, "getPairValue() used on Bundle with multiple pairs."); - } - if (size == 0) { - return null; - } - Object o = mMap.valueAt(0); - try { - return (String) o; - } catch (ClassCastException e) { - typeWarning("getPairValue()", o, "String", e); - return null; - } + return super.getPairValue(); } /** @@ -184,15 +138,17 @@ public final class Bundle implements Parcelable, Cloneable { * @param loader An explicit ClassLoader to use when instantiating objects * inside of the Bundle. */ + @Override public void setClassLoader(ClassLoader loader) { - mClassLoader = loader; + super.setClassLoader(loader); } /** * Return the ClassLoader currently associated with this Bundle. */ + @Override public ClassLoader getClassLoader() { - return mClassLoader; + return super.getClassLoader(); } /** @hide */ @@ -212,52 +168,11 @@ public final class Bundle implements Parcelable, Cloneable { } /** - * If the underlying data are stored as a Parcel, unparcel them - * using the currently assigned class loader. - */ - /* package */ synchronized void unparcel() { - if (mParcelledData == null) { - if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this)) - + ": no parcelled data"); - return; - } - - if (mParcelledData == EMPTY_PARCEL) { - if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this)) - + ": empty"); - if (mMap == null) { - mMap = new ArrayMap<String, Object>(1); - } else { - mMap.erase(); - } - mParcelledData = null; - return; - } - - int N = mParcelledData.readInt(); - if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this)) - + ": reading " + N + " maps"); - if (N < 0) { - return; - } - if (mMap == null) { - mMap = new ArrayMap<String, Object>(N); - } else { - mMap.erase(); - mMap.ensureCapacity(N); - } - mParcelledData.readArrayMapInternal(mMap, N, mClassLoader); - mParcelledData.recycle(); - mParcelledData = null; - if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this)) - + " final map: " + mMap); - } - - /** * @hide */ + @Override public boolean isParcelled() { - return mParcelledData != null; + return super.isParcelled(); } /** @@ -265,25 +180,26 @@ public final class Bundle implements Parcelable, Cloneable { * * @return the number of mappings as an int. */ + @Override public int size() { - unparcel(); - return mMap.size(); + return super.size(); } /** * Returns true if the mapping of this Bundle is empty, false otherwise. */ + @Override public boolean isEmpty() { - unparcel(); - return mMap.isEmpty(); + return super.isEmpty(); } /** * Removes all elements from the mapping of this Bundle. */ + @Override public void clear() { - unparcel(); - mMap.clear(); + super.clear(); + mHasFds = false; mFdsKnown = true; } @@ -295,9 +211,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String key * @return true if the key is part of the mapping, false otherwise */ + @Override public boolean containsKey(String key) { - unparcel(); - return mMap.containsKey(key); + return super.containsKey(key); } /** @@ -306,9 +222,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String key * @return an Object, or null */ + @Override public Object get(String key) { - unparcel(); - return mMap.get(key); + return super.get(key); } /** @@ -316,24 +232,33 @@ public final class Bundle implements Parcelable, Cloneable { * * @param key a String key */ + @Override public void remove(String key) { - unparcel(); - mMap.remove(key); + super.remove(key); } /** * Inserts all mappings from the given Bundle into this Bundle. * - * @param map a Bundle + * @param bundle a Bundle */ - public void putAll(Bundle map) { + public void putAll(Bundle bundle) { unparcel(); - map.unparcel(); - mMap.putAll(map.mMap); + bundle.unparcel(); + mMap.putAll(bundle.mMap); // fd state is now known if and only if both bundles already knew - mHasFds |= map.mHasFds; - mFdsKnown = mFdsKnown && map.mFdsKnown; + mHasFds |= bundle.mHasFds; + mFdsKnown = mFdsKnown && bundle.mFdsKnown; + } + + /** + * Inserts all mappings from the given PersistableBundle into this Bundle. + * + * @param bundle a PersistableBundle + */ + public void putAll(PersistableBundle bundle) { + super.putAll(bundle); } /** @@ -341,9 +266,9 @@ public final class Bundle implements Parcelable, Cloneable { * * @return a Set of String keys */ + @Override public Set<String> keySet() { - unparcel(); - return mMap.keySet(); + return super.keySet(); } /** @@ -352,7 +277,7 @@ public final class Bundle implements Parcelable, Cloneable { public boolean hasFileDescriptors() { if (!mFdsKnown) { boolean fdFound = false; // keep going until we find one or run out of data - + if (mParcelledData != null) { if (mParcelledData.hasFileDescriptors()) { fdFound = true; @@ -390,8 +315,7 @@ public final class Bundle implements Parcelable, Cloneable { ArrayList array = (ArrayList) obj; // an ArrayList here might contain either Strings or // Parcelables; only look inside for Parcelables - if ((array.size() > 0) - && (array.get(0) instanceof Parcelable)) { + if (!array.isEmpty() && (array.get(0) instanceof Parcelable)) { for (int n = array.size() - 1; n >= 0; n--) { Parcelable p = (Parcelable) array.get(n); if (p != null && ((p.describeContents() @@ -410,7 +334,7 @@ public final class Bundle implements Parcelable, Cloneable { } return mHasFds; } - + /** * Inserts a Boolean value into the mapping of this Bundle, replacing * any existing value for the given key. Either key or value may be null. @@ -418,9 +342,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a Boolean, or null */ + @Override public void putBoolean(String key, boolean value) { - unparcel(); - mMap.put(key, value); + super.putBoolean(key, value); } /** @@ -430,9 +354,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a byte */ + @Override public void putByte(String key, byte value) { - unparcel(); - mMap.put(key, value); + super.putByte(key, value); } /** @@ -442,9 +366,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a char, or null */ + @Override public void putChar(String key, char value) { - unparcel(); - mMap.put(key, value); + super.putChar(key, value); } /** @@ -454,9 +378,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a short */ + @Override public void putShort(String key, short value) { - unparcel(); - mMap.put(key, value); + super.putShort(key, value); } /** @@ -466,9 +390,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @param value an int, or null */ + @Override public void putInt(String key, int value) { - unparcel(); - mMap.put(key, value); + super.putInt(key, value); } /** @@ -478,9 +402,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a long */ + @Override public void putLong(String key, long value) { - unparcel(); - mMap.put(key, value); + super.putLong(key, value); } /** @@ -490,9 +414,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a float */ + @Override public void putFloat(String key, float value) { - unparcel(); - mMap.put(key, value); + super.putFloat(key, value); } /** @@ -502,9 +426,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a double */ + @Override public void putDouble(String key, double value) { - unparcel(); - mMap.put(key, value); + super.putDouble(key, value); } /** @@ -514,9 +438,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a String, or null */ + @Override public void putString(String key, String value) { - unparcel(); - mMap.put(key, value); + super.putString(key, value); } /** @@ -526,9 +450,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a CharSequence, or null */ + @Override public void putCharSequence(String key, CharSequence value) { - unparcel(); - mMap.put(key, value); + super.putCharSequence(key, value); } /** @@ -567,7 +491,7 @@ public final class Bundle implements Parcelable, Cloneable { * @param value an ArrayList of Parcelable objects, or null */ public void putParcelableArrayList(String key, - ArrayList<? extends Parcelable> value) { + ArrayList<? extends Parcelable> value) { unparcel(); mMap.put(key, value); mFdsKnown = false; @@ -602,9 +526,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @param value an ArrayList<Integer> object, or null */ + @Override public void putIntegerArrayList(String key, ArrayList<Integer> value) { - unparcel(); - mMap.put(key, value); + super.putIntegerArrayList(key, value); } /** @@ -614,9 +538,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @param value an ArrayList<String> object, or null */ + @Override public void putStringArrayList(String key, ArrayList<String> value) { - unparcel(); - mMap.put(key, value); + super.putStringArrayList(key, value); } /** @@ -626,9 +550,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @param value an ArrayList<CharSequence> object, or null */ + @Override public void putCharSequenceArrayList(String key, ArrayList<CharSequence> value) { - unparcel(); - mMap.put(key, value); + super.putCharSequenceArrayList(key, value); } /** @@ -638,9 +562,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a Serializable object, or null */ + @Override public void putSerializable(String key, Serializable value) { - unparcel(); - mMap.put(key, value); + super.putSerializable(key, value); } /** @@ -650,9 +574,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a boolean array object, or null */ + @Override public void putBooleanArray(String key, boolean[] value) { - unparcel(); - mMap.put(key, value); + super.putBooleanArray(key, value); } /** @@ -662,9 +586,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a byte array object, or null */ + @Override public void putByteArray(String key, byte[] value) { - unparcel(); - mMap.put(key, value); + super.putByteArray(key, value); } /** @@ -674,9 +598,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a short array object, or null */ + @Override public void putShortArray(String key, short[] value) { - unparcel(); - mMap.put(key, value); + super.putShortArray(key, value); } /** @@ -686,9 +610,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a char array object, or null */ + @Override public void putCharArray(String key, char[] value) { - unparcel(); - mMap.put(key, value); + super.putCharArray(key, value); } /** @@ -698,9 +622,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @param value an int array object, or null */ + @Override public void putIntArray(String key, int[] value) { - unparcel(); - mMap.put(key, value); + super.putIntArray(key, value); } /** @@ -710,9 +634,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a long array object, or null */ + @Override public void putLongArray(String key, long[] value) { - unparcel(); - mMap.put(key, value); + super.putLongArray(key, value); } /** @@ -722,9 +646,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a float array object, or null */ + @Override public void putFloatArray(String key, float[] value) { - unparcel(); - mMap.put(key, value); + super.putFloatArray(key, value); } /** @@ -734,9 +658,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a double array object, or null */ + @Override public void putDoubleArray(String key, double[] value) { - unparcel(); - mMap.put(key, value); + super.putDoubleArray(key, value); } /** @@ -746,9 +670,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a String array object, or null */ + @Override public void putStringArray(String key, String[] value) { - unparcel(); - mMap.put(key, value); + super.putStringArray(key, value); } /** @@ -758,9 +682,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a CharSequence array object, or null */ + @Override public void putCharSequenceArray(String key, CharSequence[] value) { - unparcel(); - mMap.put(key, value); + super.putCharSequenceArray(key, value); } /** @@ -776,6 +700,17 @@ public final class Bundle implements Parcelable, Cloneable { } /** + * Inserts a PersistableBundle value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a Bundle object, or null + */ + public void putPersistableBundle(String key, PersistableBundle value) { + super.putPersistableBundle(key, value); + } + + /** * Inserts an {@link IBinder} value into the mapping of this Bundle, replacing * any existing value for the given key. Either key or value may be null. * @@ -817,33 +752,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String * @return a boolean value */ + @Override public boolean getBoolean(String key) { - unparcel(); - if (DEBUG) Log.d(TAG, "Getting boolean in " - + Integer.toHexString(System.identityHashCode(this))); - return getBoolean(key, false); - } - - // Log a message if the value was non-null but not of the expected type - private void typeWarning(String key, Object value, String className, - Object defaultValue, ClassCastException e) { - StringBuilder sb = new StringBuilder(); - sb.append("Key "); - sb.append(key); - sb.append(" expected "); - sb.append(className); - sb.append(" but value was a "); - sb.append(value.getClass().getName()); - sb.append(". The default value "); - sb.append(defaultValue); - sb.append(" was returned."); - Log.w(TAG, sb.toString()); - Log.w(TAG, "Attempt to cast generated internal exception:", e); - } - - private void typeWarning(String key, Object value, String className, - ClassCastException e) { - typeWarning(key, value, className, "<null>", e); + return super.getBoolean(key); } /** @@ -854,18 +765,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param defaultValue Value to return if key does not exist * @return a boolean value */ + @Override public boolean getBoolean(String key, boolean defaultValue) { - unparcel(); - Object o = mMap.get(key); - if (o == null) { - return defaultValue; - } - try { - return (Boolean) o; - } catch (ClassCastException e) { - typeWarning(key, o, "Boolean", defaultValue, e); - return defaultValue; - } + return super.getBoolean(key, defaultValue); } /** @@ -875,9 +777,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String * @return a byte value */ + @Override public byte getByte(String key) { - unparcel(); - return getByte(key, (byte) 0); + return super.getByte(key); } /** @@ -888,18 +790,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param defaultValue Value to return if key does not exist * @return a byte value */ + @Override public Byte getByte(String key, byte defaultValue) { - unparcel(); - Object o = mMap.get(key); - if (o == null) { - return defaultValue; - } - try { - return (Byte) o; - } catch (ClassCastException e) { - typeWarning(key, o, "Byte", defaultValue, e); - return defaultValue; - } + return super.getByte(key, defaultValue); } /** @@ -909,9 +802,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String * @return a char value */ + @Override public char getChar(String key) { - unparcel(); - return getChar(key, (char) 0); + return super.getChar(key); } /** @@ -922,18 +815,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param defaultValue Value to return if key does not exist * @return a char value */ + @Override public char getChar(String key, char defaultValue) { - unparcel(); - Object o = mMap.get(key); - if (o == null) { - return defaultValue; - } - try { - return (Character) o; - } catch (ClassCastException e) { - typeWarning(key, o, "Character", defaultValue, e); - return defaultValue; - } + return super.getChar(key, defaultValue); } /** @@ -943,9 +827,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String * @return a short value */ + @Override public short getShort(String key) { - unparcel(); - return getShort(key, (short) 0); + return super.getShort(key); } /** @@ -956,18 +840,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param defaultValue Value to return if key does not exist * @return a short value */ + @Override public short getShort(String key, short defaultValue) { - unparcel(); - Object o = mMap.get(key); - if (o == null) { - return defaultValue; - } - try { - return (Short) o; - } catch (ClassCastException e) { - typeWarning(key, o, "Short", defaultValue, e); - return defaultValue; - } + return super.getShort(key, defaultValue); } /** @@ -977,9 +852,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String * @return an int value */ + @Override public int getInt(String key) { - unparcel(); - return getInt(key, 0); + return super.getInt(key); } /** @@ -990,18 +865,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param defaultValue Value to return if key does not exist * @return an int value */ + @Override public int getInt(String key, int defaultValue) { - unparcel(); - Object o = mMap.get(key); - if (o == null) { - return defaultValue; - } - try { - return (Integer) o; - } catch (ClassCastException e) { - typeWarning(key, o, "Integer", defaultValue, e); - return defaultValue; - } + return super.getInt(key, defaultValue); } /** @@ -1011,9 +877,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String * @return a long value */ + @Override public long getLong(String key) { - unparcel(); - return getLong(key, 0L); + return super.getLong(key); } /** @@ -1024,18 +890,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param defaultValue Value to return if key does not exist * @return a long value */ + @Override public long getLong(String key, long defaultValue) { - unparcel(); - Object o = mMap.get(key); - if (o == null) { - return defaultValue; - } - try { - return (Long) o; - } catch (ClassCastException e) { - typeWarning(key, o, "Long", defaultValue, e); - return defaultValue; - } + return super.getLong(key, defaultValue); } /** @@ -1045,9 +902,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String * @return a float value */ + @Override public float getFloat(String key) { - unparcel(); - return getFloat(key, 0.0f); + return super.getFloat(key); } /** @@ -1058,18 +915,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param defaultValue Value to return if key does not exist * @return a float value */ + @Override public float getFloat(String key, float defaultValue) { - unparcel(); - Object o = mMap.get(key); - if (o == null) { - return defaultValue; - } - try { - return (Float) o; - } catch (ClassCastException e) { - typeWarning(key, o, "Float", defaultValue, e); - return defaultValue; - } + return super.getFloat(key, defaultValue); } /** @@ -1079,9 +927,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String * @return a double value */ + @Override public double getDouble(String key) { - unparcel(); - return getDouble(key, 0.0); + return super.getDouble(key); } /** @@ -1092,18 +940,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param defaultValue Value to return if key does not exist * @return a double value */ + @Override public double getDouble(String key, double defaultValue) { - unparcel(); - Object o = mMap.get(key); - if (o == null) { - return defaultValue; - } - try { - return (Double) o; - } catch (ClassCastException e) { - typeWarning(key, o, "Double", defaultValue, e); - return defaultValue; - } + return super.getDouble(key, defaultValue); } /** @@ -1114,15 +953,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @return a String value, or null */ + @Override public String getString(String key) { - unparcel(); - final Object o = mMap.get(key); - try { - return (String) o; - } catch (ClassCastException e) { - typeWarning(key, o, "String", e); - return null; - } + return super.getString(key); } /** @@ -1134,9 +967,9 @@ public final class Bundle implements Parcelable, Cloneable { * @return the String value associated with the given key, or defaultValue * if no valid String object is currently mapped to that key. */ + @Override public String getString(String key, String defaultValue) { - final String s = getString(key); - return (s == null) ? defaultValue : s; + return super.getString(key, defaultValue); } /** @@ -1147,15 +980,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @return a CharSequence value, or null */ + @Override public CharSequence getCharSequence(String key) { - unparcel(); - final Object o = mMap.get(key); - try { - return (CharSequence) o; - } catch (ClassCastException e) { - typeWarning(key, o, "CharSequence", e); - return null; - } + return super.getCharSequence(key); } /** @@ -1167,9 +994,9 @@ public final class Bundle implements Parcelable, Cloneable { * @return the CharSequence value associated with the given key, or defaultValue * if no valid CharSequence object is currently mapped to that key. */ + @Override public CharSequence getCharSequence(String key, CharSequence defaultValue) { - final CharSequence cs = getCharSequence(key); - return (cs == null) ? defaultValue : cs; + return super.getCharSequence(key, defaultValue); } /** @@ -1200,6 +1027,18 @@ public final class Bundle implements Parcelable, Cloneable { * value is explicitly associated with the key. * * @param key a String, or null + * @return a PersistableBundle value, or null + */ + public PersistableBundle getPersistableBundle(String key) { + return super.getPersistableBundle(key); + } + + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null * @return a Parcelable value, or null */ public <T extends Parcelable> T getParcelable(String key) { @@ -1291,18 +1130,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @return a Serializable value, or null */ + @Override public Serializable getSerializable(String key) { - unparcel(); - Object o = mMap.get(key); - if (o == null) { - return null; - } - try { - return (Serializable) o; - } catch (ClassCastException e) { - typeWarning(key, o, "Serializable", e); - return null; - } + return super.getSerializable(key); } /** @@ -1313,18 +1143,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @return an ArrayList<String> value, or null */ + @Override public ArrayList<Integer> getIntegerArrayList(String key) { - unparcel(); - Object o = mMap.get(key); - if (o == null) { - return null; - } - try { - return (ArrayList<Integer>) o; - } catch (ClassCastException e) { - typeWarning(key, o, "ArrayList<Integer>", e); - return null; - } + return super.getIntegerArrayList(key); } /** @@ -1335,18 +1156,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @return an ArrayList<String> value, or null */ + @Override public ArrayList<String> getStringArrayList(String key) { - unparcel(); - Object o = mMap.get(key); - if (o == null) { - return null; - } - try { - return (ArrayList<String>) o; - } catch (ClassCastException e) { - typeWarning(key, o, "ArrayList<String>", e); - return null; - } + return super.getStringArrayList(key); } /** @@ -1357,18 +1169,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @return an ArrayList<CharSequence> value, or null */ + @Override public ArrayList<CharSequence> getCharSequenceArrayList(String key) { - unparcel(); - Object o = mMap.get(key); - if (o == null) { - return null; - } - try { - return (ArrayList<CharSequence>) o; - } catch (ClassCastException e) { - typeWarning(key, o, "ArrayList<CharSequence>", e); - return null; - } + return super.getCharSequenceArrayList(key); } /** @@ -1379,18 +1182,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @return a boolean[] value, or null */ + @Override public boolean[] getBooleanArray(String key) { - unparcel(); - Object o = mMap.get(key); - if (o == null) { - return null; - } - try { - return (boolean[]) o; - } catch (ClassCastException e) { - typeWarning(key, o, "byte[]", e); - return null; - } + return super.getBooleanArray(key); } /** @@ -1401,18 +1195,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @return a byte[] value, or null */ + @Override public byte[] getByteArray(String key) { - unparcel(); - Object o = mMap.get(key); - if (o == null) { - return null; - } - try { - return (byte[]) o; - } catch (ClassCastException e) { - typeWarning(key, o, "byte[]", e); - return null; - } + return super.getByteArray(key); } /** @@ -1423,18 +1208,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @return a short[] value, or null */ + @Override public short[] getShortArray(String key) { - unparcel(); - Object o = mMap.get(key); - if (o == null) { - return null; - } - try { - return (short[]) o; - } catch (ClassCastException e) { - typeWarning(key, o, "short[]", e); - return null; - } + return super.getShortArray(key); } /** @@ -1445,18 +1221,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @return a char[] value, or null */ + @Override public char[] getCharArray(String key) { - unparcel(); - Object o = mMap.get(key); - if (o == null) { - return null; - } - try { - return (char[]) o; - } catch (ClassCastException e) { - typeWarning(key, o, "char[]", e); - return null; - } + return super.getCharArray(key); } /** @@ -1467,18 +1234,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @return an int[] value, or null */ + @Override public int[] getIntArray(String key) { - unparcel(); - Object o = mMap.get(key); - if (o == null) { - return null; - } - try { - return (int[]) o; - } catch (ClassCastException e) { - typeWarning(key, o, "int[]", e); - return null; - } + return super.getIntArray(key); } /** @@ -1489,18 +1247,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @return a long[] value, or null */ + @Override public long[] getLongArray(String key) { - unparcel(); - Object o = mMap.get(key); - if (o == null) { - return null; - } - try { - return (long[]) o; - } catch (ClassCastException e) { - typeWarning(key, o, "long[]", e); - return null; - } + return super.getLongArray(key); } /** @@ -1511,18 +1260,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @return a float[] value, or null */ + @Override public float[] getFloatArray(String key) { - unparcel(); - Object o = mMap.get(key); - if (o == null) { - return null; - } - try { - return (float[]) o; - } catch (ClassCastException e) { - typeWarning(key, o, "float[]", e); - return null; - } + return super.getFloatArray(key); } /** @@ -1533,18 +1273,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @return a double[] value, or null */ + @Override public double[] getDoubleArray(String key) { - unparcel(); - Object o = mMap.get(key); - if (o == null) { - return null; - } - try { - return (double[]) o; - } catch (ClassCastException e) { - typeWarning(key, o, "double[]", e); - return null; - } + return super.getDoubleArray(key); } /** @@ -1555,18 +1286,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @return a String[] value, or null */ + @Override public String[] getStringArray(String key) { - unparcel(); - Object o = mMap.get(key); - if (o == null) { - return null; - } - try { - return (String[]) o; - } catch (ClassCastException e) { - typeWarning(key, o, "String[]", e); - return null; - } + return super.getStringArray(key); } /** @@ -1577,18 +1299,9 @@ public final class Bundle implements Parcelable, Cloneable { * @param key a String, or null * @return a CharSequence[] value, or null */ + @Override public CharSequence[] getCharSequenceArray(String key) { - unparcel(); - Object o = mMap.get(key); - if (o == null) { - return null; - } - try { - return (CharSequence[]) o; - } catch (ClassCastException e) { - typeWarning(key, o, "CharSequence[]", e); - return null; - } + return super.getCharSequenceArray(key); } /** @@ -1641,10 +1354,12 @@ public final class Bundle implements Parcelable, Cloneable { public static final Parcelable.Creator<Bundle> CREATOR = new Parcelable.Creator<Bundle>() { + @Override public Bundle createFromParcel(Parcel in) { return in.readBundle(); } + @Override public Bundle[] newArray(int size) { return new Bundle[size]; } @@ -1653,6 +1368,7 @@ public final class Bundle implements Parcelable, Cloneable { /** * Report the nature of this Parcelable's contents */ + @Override public int describeContents() { int mask = 0; if (hasFileDescriptors()) { @@ -1660,44 +1376,17 @@ public final class Bundle implements Parcelable, Cloneable { } return mask; } - + /** * Writes the Bundle contents to a Parcel, typically in order for * it to be passed through an IBinder connection. * @param parcel The parcel to copy this bundle to. */ + @Override public void writeToParcel(Parcel parcel, int flags) { final boolean oldAllowFds = parcel.pushAllowFds(mAllowFds); try { - if (mParcelledData != null) { - if (mParcelledData == EMPTY_PARCEL) { - parcel.writeInt(0); - } else { - int length = mParcelledData.dataSize(); - parcel.writeInt(length); - parcel.writeInt(BUNDLE_MAGIC); - parcel.appendFrom(mParcelledData, 0, length); - } - } else { - // Special case for empty bundles. - if (mMap == null || mMap.size() <= 0) { - parcel.writeInt(0); - return; - } - int lengthPos = parcel.dataPosition(); - parcel.writeInt(-1); // dummy, will hold length - parcel.writeInt(BUNDLE_MAGIC); - - int startPos = parcel.dataPosition(); - parcel.writeArrayMapInternal(mMap); - int endPos = parcel.dataPosition(); - - // Backpatch length - parcel.setDataPosition(lengthPos); - int length = endPos - startPos; - parcel.writeInt(length); - parcel.setDataPosition(endPos); - } + super.writeToParcelInner(parcel, flags); } finally { parcel.restoreAllowFds(oldAllowFds); } @@ -1709,41 +1398,8 @@ public final class Bundle implements Parcelable, Cloneable { * @param parcel The parcel to overwrite this bundle from. */ public void readFromParcel(Parcel parcel) { - int length = parcel.readInt(); - if (length < 0) { - throw new RuntimeException("Bad length in parcel: " + length); - } - readFromParcelInner(parcel, length); - } - - void readFromParcelInner(Parcel parcel, int length) { - if (length == 0) { - // Empty Bundle or end of data. - mParcelledData = EMPTY_PARCEL; - mHasFds = false; - mFdsKnown = true; - return; - } - int magic = parcel.readInt(); - if (magic != BUNDLE_MAGIC) { - //noinspection ThrowableInstanceNeverThrown - throw new IllegalStateException("Bad magic number for Bundle: 0x" - + Integer.toHexString(magic)); - } - - // Advance within this Parcel - int offset = parcel.dataPosition(); - parcel.setDataPosition(offset + length); - - Parcel p = Parcel.obtain(); - p.setDataPosition(0); - p.appendFrom(parcel, offset, length); - if (DEBUG) Log.d(TAG, "Retrieving " + Integer.toHexString(System.identityHashCode(this)) - + ": " + length + " bundle bytes starting at " + offset); - p.setDataPosition(0); - - mParcelledData = p; - mHasFds = p.hasFileDescriptors(); + super.readFromParcelInner(parcel); + mHasFds = mParcelledData.hasFileDescriptors(); mFdsKnown = true; } @@ -1759,4 +1415,5 @@ public final class Bundle implements Parcelable, Cloneable { } return "Bundle[" + mMap.toString() + "]"; } + } diff --git a/core/java/android/os/CommonBundle.java b/core/java/android/os/CommonBundle.java new file mode 100644 index 0000000..e11f170 --- /dev/null +++ b/core/java/android/os/CommonBundle.java @@ -0,0 +1,1384 @@ +/* + * 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.os; + +import android.util.ArrayMap; +import android.util.Log; +import android.util.SparseArray; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * A mapping from String values to various types. + */ +abstract class CommonBundle implements Parcelable, Cloneable { + private static final String TAG = "Bundle"; + static final boolean DEBUG = false; + + static final int BUNDLE_MAGIC = 0x4C444E42; // 'B' 'N' 'D' 'L' + static final Parcel EMPTY_PARCEL; + + static { + EMPTY_PARCEL = Parcel.obtain(); + } + + // Invariant - exactly one of mMap / mParcelledData will be null + // (except inside a call to unparcel) + + ArrayMap<String, Object> mMap = null; + + /* + * If mParcelledData is non-null, then mMap will be null and the + * data are stored as a Parcel containing a Bundle. When the data + * are unparcelled, mParcelledData willbe set to null. + */ + Parcel mParcelledData = null; + + /** + * The ClassLoader used when unparcelling data from mParcelledData. + */ + private ClassLoader mClassLoader; + + /** + * Constructs a new, empty Bundle that uses a specific ClassLoader for + * instantiating Parcelable and Serializable objects. + * + * @param loader An explicit ClassLoader to use when instantiating objects + * inside of the Bundle. + * @param capacity Initial size of the ArrayMap. + */ + CommonBundle(ClassLoader loader, int capacity) { + mMap = capacity > 0 ? + new ArrayMap<String, Object>(capacity) : new ArrayMap<String, Object>(); + mClassLoader = loader == null ? getClass().getClassLoader() : loader; + } + + /** + * Constructs a new, empty Bundle. + */ + CommonBundle() { + this((ClassLoader) null, 0); + } + + /** + * Constructs a Bundle whose data is stored as a Parcel. The data + * will be unparcelled on first contact, using the assigned ClassLoader. + * + * @param parcelledData a Parcel containing a Bundle + */ + CommonBundle(Parcel parcelledData) { + readFromParcelInner(parcelledData); + } + + CommonBundle(Parcel parcelledData, int length) { + readFromParcelInner(parcelledData, length); + } + + /** + * Constructs a new, empty Bundle that uses a specific ClassLoader for + * instantiating Parcelable and Serializable objects. + * + * @param loader An explicit ClassLoader to use when instantiating objects + * inside of the Bundle. + */ + CommonBundle(ClassLoader loader) { + this(loader, 0); + } + + /** + * Constructs a new, empty Bundle sized to hold the given number of + * elements. The Bundle will grow as needed. + * + * @param capacity the initial capacity of the Bundle + */ + CommonBundle(int capacity) { + this((ClassLoader) null, capacity); + } + + /** + * Constructs a Bundle containing a copy of the mappings from the given + * Bundle. + * + * @param b a Bundle to be copied. + */ + CommonBundle(CommonBundle b) { + if (b.mParcelledData != null) { + if (b.mParcelledData == EMPTY_PARCEL) { + mParcelledData = EMPTY_PARCEL; + } else { + mParcelledData = Parcel.obtain(); + mParcelledData.appendFrom(b.mParcelledData, 0, b.mParcelledData.dataSize()); + mParcelledData.setDataPosition(0); + } + } else { + mParcelledData = null; + } + + if (b.mMap != null) { + mMap = new ArrayMap<String, Object>(b.mMap); + } else { + mMap = null; + } + + mClassLoader = b.mClassLoader; + } + + /** + * TODO: optimize this later (getting just the value part of a Bundle + * with a single pair) once Bundle.forPair() above is implemented + * with a special single-value Map implementation/serialization. + * + * Note: value in single-pair Bundle may be null. + * + * @hide + */ + String getPairValue() { + unparcel(); + int size = mMap.size(); + if (size > 1) { + Log.w(TAG, "getPairValue() used on Bundle with multiple pairs."); + } + if (size == 0) { + return null; + } + Object o = mMap.valueAt(0); + try { + return (String) o; + } catch (ClassCastException e) { + typeWarning("getPairValue()", o, "String", e); + return null; + } + } + + /** + * Changes the ClassLoader this Bundle uses when instantiating objects. + * + * @param loader An explicit ClassLoader to use when instantiating objects + * inside of the Bundle. + */ + void setClassLoader(ClassLoader loader) { + mClassLoader = loader; + } + + /** + * Return the ClassLoader currently associated with this Bundle. + */ + ClassLoader getClassLoader() { + return mClassLoader; + } + + /** + * If the underlying data are stored as a Parcel, unparcel them + * using the currently assigned class loader. + */ + /* package */ synchronized void unparcel() { + if (mParcelledData == null) { + if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this)) + + ": no parcelled data"); + return; + } + + if (mParcelledData == EMPTY_PARCEL) { + if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this)) + + ": empty"); + if (mMap == null) { + mMap = new ArrayMap<String, Object>(1); + } else { + mMap.erase(); + } + mParcelledData = null; + return; + } + + int N = mParcelledData.readInt(); + if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this)) + + ": reading " + N + " maps"); + if (N < 0) { + return; + } + if (mMap == null) { + mMap = new ArrayMap<String, Object>(N); + } else { + mMap.erase(); + mMap.ensureCapacity(N); + } + mParcelledData.readArrayMapInternal(mMap, N, mClassLoader); + mParcelledData.recycle(); + mParcelledData = null; + if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this)) + + " final map: " + mMap); + } + + /** + * @hide + */ + boolean isParcelled() { + return mParcelledData != null; + } + + /** + * Returns the number of mappings contained in this Bundle. + * + * @return the number of mappings as an int. + */ + int size() { + unparcel(); + return mMap.size(); + } + + /** + * Returns true if the mapping of this Bundle is empty, false otherwise. + */ + boolean isEmpty() { + unparcel(); + return mMap.isEmpty(); + } + + /** + * Removes all elements from the mapping of this Bundle. + */ + void clear() { + unparcel(); + mMap.clear(); + } + + /** + * Returns true if the given key is contained in the mapping + * of this Bundle. + * + * @param key a String key + * @return true if the key is part of the mapping, false otherwise + */ + boolean containsKey(String key) { + unparcel(); + return mMap.containsKey(key); + } + + /** + * Returns the entry with the given key as an object. + * + * @param key a String key + * @return an Object, or null + */ + Object get(String key) { + unparcel(); + return mMap.get(key); + } + + /** + * Removes any entry with the given key from the mapping of this Bundle. + * + * @param key a String key + */ + void remove(String key) { + unparcel(); + mMap.remove(key); + } + + /** + * Inserts all mappings from the given PersistableBundle into this CommonBundle. + * + * @param bundle a PersistableBundle + */ + void putAll(PersistableBundle bundle) { + unparcel(); + bundle.unparcel(); + mMap.putAll(bundle.mMap); + } + + /** + * Returns a Set containing the Strings used as keys in this Bundle. + * + * @return a Set of String keys + */ + Set<String> keySet() { + unparcel(); + return mMap.keySet(); + } + + /** + * Inserts a Boolean value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a Boolean, or null + */ + void putBoolean(String key, boolean value) { + unparcel(); + mMap.put(key, value); + } + + /** + * Inserts a byte value into the mapping of this Bundle, replacing + * any existing value for the given key. + * + * @param key a String, or null + * @param value a byte + */ + void putByte(String key, byte value) { + unparcel(); + mMap.put(key, value); + } + + /** + * Inserts a char value into the mapping of this Bundle, replacing + * any existing value for the given key. + * + * @param key a String, or null + * @param value a char, or null + */ + void putChar(String key, char value) { + unparcel(); + mMap.put(key, value); + } + + /** + * Inserts a short value into the mapping of this Bundle, replacing + * any existing value for the given key. + * + * @param key a String, or null + * @param value a short + */ + void putShort(String key, short value) { + unparcel(); + mMap.put(key, value); + } + + /** + * Inserts an int value into the mapping of this Bundle, replacing + * any existing value for the given key. + * + * @param key a String, or null + * @param value an int, or null + */ + void putInt(String key, int value) { + unparcel(); + mMap.put(key, value); + } + + /** + * Inserts a long value into the mapping of this Bundle, replacing + * any existing value for the given key. + * + * @param key a String, or null + * @param value a long + */ + void putLong(String key, long value) { + unparcel(); + mMap.put(key, value); + } + + /** + * Inserts a float value into the mapping of this Bundle, replacing + * any existing value for the given key. + * + * @param key a String, or null + * @param value a float + */ + void putFloat(String key, float value) { + unparcel(); + mMap.put(key, value); + } + + /** + * Inserts a double value into the mapping of this Bundle, replacing + * any existing value for the given key. + * + * @param key a String, or null + * @param value a double + */ + void putDouble(String key, double value) { + unparcel(); + mMap.put(key, value); + } + + /** + * Inserts a String value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a String, or null + */ + void putString(String key, String value) { + unparcel(); + mMap.put(key, value); + } + + /** + * Inserts a CharSequence value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a CharSequence, or null + */ + void putCharSequence(String key, CharSequence value) { + unparcel(); + mMap.put(key, value); + } + + /** + * Inserts an ArrayList<Integer> value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value an ArrayList<Integer> object, or null + */ + void putIntegerArrayList(String key, ArrayList<Integer> value) { + unparcel(); + mMap.put(key, value); + } + + /** + * Inserts an ArrayList<String> value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value an ArrayList<String> object, or null + */ + void putStringArrayList(String key, ArrayList<String> value) { + unparcel(); + mMap.put(key, value); + } + + /** + * Inserts an ArrayList<CharSequence> value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value an ArrayList<CharSequence> object, or null + */ + void putCharSequenceArrayList(String key, ArrayList<CharSequence> value) { + unparcel(); + mMap.put(key, value); + } + + /** + * Inserts a Serializable value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a Serializable object, or null + */ + void putSerializable(String key, Serializable value) { + unparcel(); + mMap.put(key, value); + } + + /** + * Inserts a boolean array value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a boolean array object, or null + */ + void putBooleanArray(String key, boolean[] value) { + unparcel(); + mMap.put(key, value); + } + + /** + * Inserts a byte array value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a byte array object, or null + */ + void putByteArray(String key, byte[] value) { + unparcel(); + mMap.put(key, value); + } + + /** + * Inserts a short array value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a short array object, or null + */ + void putShortArray(String key, short[] value) { + unparcel(); + mMap.put(key, value); + } + + /** + * Inserts a char array value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a char array object, or null + */ + void putCharArray(String key, char[] value) { + unparcel(); + mMap.put(key, value); + } + + /** + * Inserts an int array value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value an int array object, or null + */ + void putIntArray(String key, int[] value) { + unparcel(); + mMap.put(key, value); + } + + /** + * Inserts a long array value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a long array object, or null + */ + void putLongArray(String key, long[] value) { + unparcel(); + mMap.put(key, value); + } + + /** + * Inserts a float array value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a float array object, or null + */ + void putFloatArray(String key, float[] value) { + unparcel(); + mMap.put(key, value); + } + + /** + * Inserts a double array value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a double array object, or null + */ + void putDoubleArray(String key, double[] value) { + unparcel(); + mMap.put(key, value); + } + + /** + * Inserts a String array value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a String array object, or null + */ + void putStringArray(String key, String[] value) { + unparcel(); + mMap.put(key, value); + } + + /** + * Inserts a CharSequence array value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a CharSequence array object, or null + */ + void putCharSequenceArray(String key, CharSequence[] value) { + unparcel(); + mMap.put(key, value); + } + + /** + * Inserts a PersistableBundle value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a Bundle object, or null + */ + void putPersistableBundle(String key, PersistableBundle value) { + unparcel(); + mMap.put(key, value); + } + + /** + * Returns the value associated with the given key, or false if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @return a boolean value + */ + boolean getBoolean(String key) { + unparcel(); + if (DEBUG) Log.d(TAG, "Getting boolean in " + + Integer.toHexString(System.identityHashCode(this))); + return getBoolean(key, false); + } + + // Log a message if the value was non-null but not of the expected type + void typeWarning(String key, Object value, String className, + Object defaultValue, ClassCastException e) { + StringBuilder sb = new StringBuilder(); + sb.append("Key "); + sb.append(key); + sb.append(" expected "); + sb.append(className); + sb.append(" but value was a "); + sb.append(value.getClass().getName()); + sb.append(". The default value "); + sb.append(defaultValue); + sb.append(" was returned."); + Log.w(TAG, sb.toString()); + Log.w(TAG, "Attempt to cast generated internal exception:", e); + } + + void typeWarning(String key, Object value, String className, + ClassCastException e) { + typeWarning(key, value, className, "<null>", e); + } + + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return a boolean value + */ + boolean getBoolean(String key, boolean defaultValue) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return defaultValue; + } + try { + return (Boolean) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Boolean", defaultValue, e); + return defaultValue; + } + } + + /** + * Returns the value associated with the given key, or (byte) 0 if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @return a byte value + */ + byte getByte(String key) { + unparcel(); + return getByte(key, (byte) 0); + } + + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return a byte value + */ + Byte getByte(String key, byte defaultValue) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return defaultValue; + } + try { + return (Byte) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Byte", defaultValue, e); + return defaultValue; + } + } + + /** + * Returns the value associated with the given key, or (char) 0 if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @return a char value + */ + char getChar(String key) { + unparcel(); + return getChar(key, (char) 0); + } + + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return a char value + */ + char getChar(String key, char defaultValue) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return defaultValue; + } + try { + return (Character) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Character", defaultValue, e); + return defaultValue; + } + } + + /** + * Returns the value associated with the given key, or (short) 0 if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @return a short value + */ + short getShort(String key) { + unparcel(); + return getShort(key, (short) 0); + } + + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return a short value + */ + short getShort(String key, short defaultValue) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return defaultValue; + } + try { + return (Short) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Short", defaultValue, e); + return defaultValue; + } + } + + /** + * Returns the value associated with the given key, or 0 if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @return an int value + */ + int getInt(String key) { + unparcel(); + return getInt(key, 0); + } + + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return an int value + */ + int getInt(String key, int defaultValue) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return defaultValue; + } + try { + return (Integer) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Integer", defaultValue, e); + return defaultValue; + } + } + + /** + * Returns the value associated with the given key, or 0L if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @return a long value + */ + long getLong(String key) { + unparcel(); + return getLong(key, 0L); + } + + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return a long value + */ + long getLong(String key, long defaultValue) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return defaultValue; + } + try { + return (Long) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Long", defaultValue, e); + return defaultValue; + } + } + + /** + * Returns the value associated with the given key, or 0.0f if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @return a float value + */ + float getFloat(String key) { + unparcel(); + return getFloat(key, 0.0f); + } + + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return a float value + */ + float getFloat(String key, float defaultValue) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return defaultValue; + } + try { + return (Float) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Float", defaultValue, e); + return defaultValue; + } + } + + /** + * Returns the value associated with the given key, or 0.0 if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @return a double value + */ + double getDouble(String key) { + unparcel(); + return getDouble(key, 0.0); + } + + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return a double value + */ + double getDouble(String key, double defaultValue) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return defaultValue; + } + try { + return (Double) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Double", defaultValue, e); + return defaultValue; + } + } + + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a String value, or null + */ + String getString(String key) { + unparcel(); + final Object o = mMap.get(key); + try { + return (String) o; + } catch (ClassCastException e) { + typeWarning(key, o, "String", e); + return null; + } + } + + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String, or null + * @param defaultValue Value to return if key does not exist + * @return the String value associated with the given key, or defaultValue + * if no valid String object is currently mapped to that key. + */ + String getString(String key, String defaultValue) { + final String s = getString(key); + return (s == null) ? defaultValue : s; + } + + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a CharSequence value, or null + */ + CharSequence getCharSequence(String key) { + unparcel(); + final Object o = mMap.get(key); + try { + return (CharSequence) o; + } catch (ClassCastException e) { + typeWarning(key, o, "CharSequence", e); + return null; + } + } + + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String, or null + * @param defaultValue Value to return if key does not exist + * @return the CharSequence value associated with the given key, or defaultValue + * if no valid CharSequence object is currently mapped to that key. + */ + CharSequence getCharSequence(String key, CharSequence defaultValue) { + final CharSequence cs = getCharSequence(key); + return (cs == null) ? defaultValue : cs; + } + + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a Bundle value, or null + */ + PersistableBundle getPersistableBundle(String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (PersistableBundle) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Bundle", e); + return null; + } + } + + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a Serializable value, or null + */ + Serializable getSerializable(String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (Serializable) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Serializable", e); + return null; + } + } + + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return an ArrayList<String> value, or null + */ + ArrayList<Integer> getIntegerArrayList(String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (ArrayList<Integer>) o; + } catch (ClassCastException e) { + typeWarning(key, o, "ArrayList<Integer>", e); + return null; + } + } + + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return an ArrayList<String> value, or null + */ + ArrayList<String> getStringArrayList(String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (ArrayList<String>) o; + } catch (ClassCastException e) { + typeWarning(key, o, "ArrayList<String>", e); + return null; + } + } + + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return an ArrayList<CharSequence> value, or null + */ + ArrayList<CharSequence> getCharSequenceArrayList(String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (ArrayList<CharSequence>) o; + } catch (ClassCastException e) { + typeWarning(key, o, "ArrayList<CharSequence>", e); + return null; + } + } + + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a boolean[] value, or null + */ + boolean[] getBooleanArray(String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (boolean[]) o; + } catch (ClassCastException e) { + typeWarning(key, o, "byte[]", e); + return null; + } + } + + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a byte[] value, or null + */ + byte[] getByteArray(String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (byte[]) o; + } catch (ClassCastException e) { + typeWarning(key, o, "byte[]", e); + return null; + } + } + + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a short[] value, or null + */ + short[] getShortArray(String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (short[]) o; + } catch (ClassCastException e) { + typeWarning(key, o, "short[]", e); + return null; + } + } + + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a char[] value, or null + */ + char[] getCharArray(String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (char[]) o; + } catch (ClassCastException e) { + typeWarning(key, o, "char[]", e); + return null; + } + } + + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return an int[] value, or null + */ + int[] getIntArray(String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (int[]) o; + } catch (ClassCastException e) { + typeWarning(key, o, "int[]", e); + return null; + } + } + + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a long[] value, or null + */ + long[] getLongArray(String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (long[]) o; + } catch (ClassCastException e) { + typeWarning(key, o, "long[]", e); + return null; + } + } + + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a float[] value, or null + */ + float[] getFloatArray(String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (float[]) o; + } catch (ClassCastException e) { + typeWarning(key, o, "float[]", e); + return null; + } + } + + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a double[] value, or null + */ + double[] getDoubleArray(String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (double[]) o; + } catch (ClassCastException e) { + typeWarning(key, o, "double[]", e); + return null; + } + } + + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a String[] value, or null + */ + String[] getStringArray(String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (String[]) o; + } catch (ClassCastException e) { + typeWarning(key, o, "String[]", e); + return null; + } + } + + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a CharSequence[] value, or null + */ + CharSequence[] getCharSequenceArray(String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (CharSequence[]) o; + } catch (ClassCastException e) { + typeWarning(key, o, "CharSequence[]", e); + return null; + } + } + + /** + * Writes the Bundle contents to a Parcel, typically in order for + * it to be passed through an IBinder connection. + * @param parcel The parcel to copy this bundle to. + */ + void writeToParcelInner(Parcel parcel, int flags) { + if (mParcelledData != null) { + if (mParcelledData == EMPTY_PARCEL) { + parcel.writeInt(0); + } else { + int length = mParcelledData.dataSize(); + parcel.writeInt(length); + parcel.writeInt(BUNDLE_MAGIC); + parcel.appendFrom(mParcelledData, 0, length); + } + } else { + // Special case for empty bundles. + if (mMap == null || mMap.size() <= 0) { + parcel.writeInt(0); + return; + } + int lengthPos = parcel.dataPosition(); + parcel.writeInt(-1); // dummy, will hold length + parcel.writeInt(BUNDLE_MAGIC); + + int startPos = parcel.dataPosition(); + parcel.writeArrayMapInternal(mMap); + int endPos = parcel.dataPosition(); + + // Backpatch length + parcel.setDataPosition(lengthPos); + int length = endPos - startPos; + parcel.writeInt(length); + parcel.setDataPosition(endPos); + } + } + + /** + * Reads the Parcel contents into this Bundle, typically in order for + * it to be passed through an IBinder connection. + * @param parcel The parcel to overwrite this bundle from. + */ + void readFromParcelInner(Parcel parcel) { + int length = parcel.readInt(); + if (length < 0) { + throw new RuntimeException("Bad length in parcel: " + length); + } + readFromParcelInner(parcel, length); + } + + private void readFromParcelInner(Parcel parcel, int length) { + if (length == 0) { + // Empty Bundle or end of data. + mParcelledData = EMPTY_PARCEL; + return; + } + int magic = parcel.readInt(); + if (magic != BUNDLE_MAGIC) { + //noinspection ThrowableInstanceNeverThrown + throw new IllegalStateException("Bad magic number for Bundle: 0x" + + Integer.toHexString(magic)); + } + + // Advance within this Parcel + int offset = parcel.dataPosition(); + parcel.setDataPosition(offset + length); + + Parcel p = Parcel.obtain(); + p.setDataPosition(0); + p.appendFrom(parcel, offset, length); + if (DEBUG) Log.d(TAG, "Retrieving " + Integer.toHexString(System.identityHashCode(this)) + + ": " + length + " bundle bytes starting at " + offset); + p.setDataPosition(0); + + mParcelledData = p; + } +} diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index e96398a..e98a26b 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -42,6 +42,7 @@ public class Environment { private static final String ENV_SECONDARY_STORAGE = "SECONDARY_STORAGE"; private static final String ENV_ANDROID_ROOT = "ANDROID_ROOT"; private static final String ENV_OEM_ROOT = "OEM_ROOT"; + private static final String ENV_VENDOR_ROOT = "VENDOR_ROOT"; /** {@hide} */ public static final String DIR_ANDROID = "Android"; @@ -57,6 +58,7 @@ public class Environment { private static final File DIR_ANDROID_ROOT = getDirectory(ENV_ANDROID_ROOT, "/system"); private static final File DIR_OEM_ROOT = getDirectory(ENV_OEM_ROOT, "/oem"); + private static final File DIR_VENDOR_ROOT = getDirectory(ENV_VENDOR_ROOT, "/vendor"); private static final File DIR_MEDIA_STORAGE = getDirectory(ENV_MEDIA_STORAGE, "/data/media"); private static final String CANONCIAL_EMULATED_STORAGE_TARGET = getCanonicalPathOrNull( @@ -225,6 +227,15 @@ public class Environment { } /** + * Return root directory of the "vendor" partition that holds vendor-provided + * software that should persist across simple reflashing of the "system" partition. + * @hide + */ + public static File getVendorDirectory() { + return DIR_VENDOR_ROOT; + } + + /** * Gets the system directory available for secure storage. * If Encrypted File system is enabled, it returns an encrypted directory (/data/secure/system). * Otherwise, it returns the unencrypted /data/system directory. diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index dc18dee..1089f27 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -370,8 +370,8 @@ public class FileUtils { * attacks. */ public static boolean contains(File dir, File file) { - String dirPath = dir.getPath(); - String filePath = file.getPath(); + String dirPath = dir.getAbsolutePath(); + String filePath = file.getAbsolutePath(); if (dirPath.equals(filePath)) { return true; diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 1192a45..c3f7370 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -34,7 +34,7 @@ interface IUserManager { void setUserIcon(int userHandle, in Bitmap icon); Bitmap getUserIcon(int userHandle); List<UserInfo> getUsers(boolean excludeDying); - List<UserInfo> getProfiles(int userHandle); + List<UserInfo> getProfiles(int userHandle, boolean enabledOnly); UserInfo getUserInfo(int userHandle); boolean isRestricted(); void setGuestEnabled(boolean enable); diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 8e0ff08..95cb9f3 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -223,6 +223,7 @@ public final class Parcel { private static final int VAL_SPARSEBOOLEANARRAY = 22; private static final int VAL_BOOLEANARRAY = 23; private static final int VAL_CHARSEQUENCEARRAY = 24; + private static final int VAL_PERSISTABLEBUNDLE = 25; // The initial int32 in a Binder call's reply Parcel header: private static final int EX_SECURITY = -1; @@ -638,6 +639,19 @@ public final class Parcel { } /** + * Flatten a PersistableBundle into the parcel at the current dataPosition(), + * growing dataCapacity() if needed. + */ + public final void writePersistableBundle(PersistableBundle val) { + if (val == null) { + writeInt(-1); + return; + } + + val.writeToParcel(this, 0); + } + + /** * Flatten a List into the parcel at the current dataPosition(), growing * dataCapacity() if needed. The List values are written using * {@link #writeValue} and must follow the specification there. @@ -1256,6 +1270,9 @@ public final class Parcel { } else if (v instanceof Byte) { writeInt(VAL_BYTE); writeInt((Byte) v); + } else if (v instanceof PersistableBundle) { + writeInt(VAL_PERSISTABLEBUNDLE); + writePersistableBundle((PersistableBundle) v); } else { Class<?> clazz = v.getClass(); if (clazz.isArray() && clazz.getComponentType() == Object.class) { @@ -1633,6 +1650,35 @@ public final class Parcel { } /** + * Read and return a new Bundle object from the parcel at the current + * dataPosition(). Returns null if the previously written Bundle object was + * null. + */ + public final PersistableBundle readPersistableBundle() { + return readPersistableBundle(null); + } + + /** + * Read and return a new Bundle object from the parcel at the current + * dataPosition(), using the given class loader to initialize the class + * loader of the Bundle for later retrieval of Parcelable objects. + * Returns null if the previously written Bundle object was null. + */ + public final PersistableBundle readPersistableBundle(ClassLoader loader) { + int length = readInt(); + if (length < 0) { + if (Bundle.DEBUG) Log.d(TAG, "null bundle: length=" + length); + return null; + } + + final PersistableBundle bundle = new PersistableBundle(this, length); + if (loader != null) { + bundle.setClassLoader(loader); + } + return bundle; + } + + /** * Read and return a byte[] object from the parcel. */ public final byte[] createByteArray() { @@ -2082,6 +2128,9 @@ public final class Parcel { case VAL_BUNDLE: return readBundle(loader); // loading will be deferred + case VAL_PERSISTABLEBUNDLE: + return readPersistableBundle(loader); + default: int off = dataPosition() - 4; throw new RuntimeException( diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java index 86dc8b4..24bf05e 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -42,6 +42,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InterruptedIOException; import java.net.DatagramSocket; import java.net.Socket; import java.nio.ByteOrder; @@ -698,6 +699,9 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { } catch (ErrnoException e) { // Reporting status is best-effort Log.w(TAG, "Failed to report status: " + e); + } catch (InterruptedIOException e) { + // Reporting status is best-effort + Log.w(TAG, "Failed to report status: " + e); } } finally { @@ -728,6 +732,9 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { Log.d(TAG, "Failed to read status; assuming dead: " + e); return new Status(Status.DEAD); } + } catch (InterruptedIOException e) { + Log.d(TAG, "Failed to read status; assuming dead: " + e); + return new Status(Status.DEAD); } } diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java new file mode 100644 index 0000000..c2cd3be --- /dev/null +++ b/core/java/android/os/PersistableBundle.java @@ -0,0 +1,555 @@ +/* + * 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.os; + +import android.util.ArrayMap; + +import java.util.Set; + +/** + * A mapping from String values to various types that can be saved to persistent and later + * restored. + * + */ +public final class PersistableBundle extends CommonBundle { + public static final PersistableBundle EMPTY; + static final Parcel EMPTY_PARCEL; + + static { + EMPTY = new PersistableBundle(); + EMPTY.mMap = ArrayMap.EMPTY; + EMPTY_PARCEL = CommonBundle.EMPTY_PARCEL; + } + + /** + * Constructs a new, empty PersistableBundle. + */ + public PersistableBundle() { + super(); + } + + /** + * Constructs a PersistableBundle whose data is stored as a Parcel. The data + * will be unparcelled on first contact, using the assigned ClassLoader. + * + * @param parcelledData a Parcel containing a PersistableBundle + */ + PersistableBundle(Parcel parcelledData) { + super(parcelledData); + } + + /* package */ PersistableBundle(Parcel parcelledData, int length) { + super(parcelledData, length); + } + + /** + * Constructs a new, empty PersistableBundle that uses a specific ClassLoader for + * instantiating Parcelable and Serializable objects. + * + * @param loader An explicit ClassLoader to use when instantiating objects + * inside of the PersistableBundle. + */ + public PersistableBundle(ClassLoader loader) { + super(loader); + } + + /** + * Constructs a new, empty PersistableBundle sized to hold the given number of + * elements. The PersistableBundle will grow as needed. + * + * @param capacity the initial capacity of the PersistableBundle + */ + public PersistableBundle(int capacity) { + super(capacity); + } + + /** + * Constructs a PersistableBundle containing a copy of the mappings from the given + * PersistableBundle. + * + * @param b a PersistableBundle to be copied. + */ + public PersistableBundle(PersistableBundle b) { + super(b); + } + + /** + * Make a PersistableBundle for a single key/value pair. + * + * @hide + */ + public static PersistableBundle forPair(String key, String value) { + PersistableBundle b = new PersistableBundle(1); + b.putString(key, value); + return b; + } + + /** + * @hide + */ + @Override + public String getPairValue() { + return super.getPairValue(); + } + + /** + * Changes the ClassLoader this PersistableBundle uses when instantiating objects. + * + * @param loader An explicit ClassLoader to use when instantiating objects + * inside of the PersistableBundle. + */ + @Override + public void setClassLoader(ClassLoader loader) { + super.setClassLoader(loader); + } + + /** + * Return the ClassLoader currently associated with this PersistableBundle. + */ + @Override + public ClassLoader getClassLoader() { + return super.getClassLoader(); + } + + /** + * Clones the current PersistableBundle. The internal map is cloned, but the keys and + * values to which it refers are copied by reference. + */ + @Override + public Object clone() { + return new PersistableBundle(this); + } + + /** + * @hide + */ + @Override + public boolean isParcelled() { + return super.isParcelled(); + } + + /** + * Returns the number of mappings contained in this PersistableBundle. + * + * @return the number of mappings as an int. + */ + @Override + public int size() { + return super.size(); + } + + /** + * Returns true if the mapping of this PersistableBundle is empty, false otherwise. + */ + @Override + public boolean isEmpty() { + return super.isEmpty(); + } + + /** + * Removes all elements from the mapping of this PersistableBundle. + */ + @Override + public void clear() { + super.clear(); + } + + /** + * Returns true if the given key is contained in the mapping + * of this PersistableBundle. + * + * @param key a String key + * @return true if the key is part of the mapping, false otherwise + */ + @Override + public boolean containsKey(String key) { + return super.containsKey(key); + } + + /** + * Returns the entry with the given key as an object. + * + * @param key a String key + * @return an Object, or null + */ + @Override + public Object get(String key) { + return super.get(key); + } + + /** + * Removes any entry with the given key from the mapping of this PersistableBundle. + * + * @param key a String key + */ + @Override + public void remove(String key) { + super.remove(key); + } + + /** + * Inserts all mappings from the given PersistableBundle into this Bundle. + * + * @param bundle a PersistableBundle + */ + public void putAll(PersistableBundle bundle) { + super.putAll(bundle); + } + + /** + * Returns a Set containing the Strings used as keys in this PersistableBundle. + * + * @return a Set of String keys + */ + @Override + public Set<String> keySet() { + return super.keySet(); + } + + /** + * Inserts an int value into the mapping of this PersistableBundle, replacing + * any existing value for the given key. + * + * @param key a String, or null + * @param value an int, or null + */ + @Override + public void putInt(String key, int value) { + super.putInt(key, value); + } + + /** + * Inserts a long value into the mapping of this PersistableBundle, replacing + * any existing value for the given key. + * + * @param key a String, or null + * @param value a long + */ + @Override + public void putLong(String key, long value) { + super.putLong(key, value); + } + + /** + * Inserts a double value into the mapping of this PersistableBundle, replacing + * any existing value for the given key. + * + * @param key a String, or null + * @param value a double + */ + @Override + public void putDouble(String key, double value) { + super.putDouble(key, value); + } + + /** + * Inserts a String value into the mapping of this PersistableBundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a String, or null + */ + @Override + public void putString(String key, String value) { + super.putString(key, value); + } + + /** + * Inserts an int array value into the mapping of this PersistableBundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value an int array object, or null + */ + @Override + public void putIntArray(String key, int[] value) { + super.putIntArray(key, value); + } + + /** + * Inserts a long array value into the mapping of this PersistableBundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a long array object, or null + */ + @Override + public void putLongArray(String key, long[] value) { + super.putLongArray(key, value); + } + + /** + * Inserts a double array value into the mapping of this PersistableBundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a double array object, or null + */ + @Override + public void putDoubleArray(String key, double[] value) { + super.putDoubleArray(key, value); + } + + /** + * Inserts a String array value into the mapping of this PersistableBundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a String array object, or null + */ + @Override + public void putStringArray(String key, String[] value) { + super.putStringArray(key, value); + } + + /** + * Inserts a PersistableBundle value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a Bundle object, or null + */ + public void putPersistableBundle(String key, PersistableBundle value) { + super.putPersistableBundle(key, value); + } + + /** + * Returns the value associated with the given key, or 0 if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @return an int value + */ + @Override + public int getInt(String key) { + return super.getInt(key); + } + + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return an int value + */ + @Override + public int getInt(String key, int defaultValue) { + return super.getInt(key, defaultValue); + } + + /** + * Returns the value associated with the given key, or 0L if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @return a long value + */ + @Override + public long getLong(String key) { + return super.getLong(key); + } + + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return a long value + */ + @Override + public long getLong(String key, long defaultValue) { + return super.getLong(key, defaultValue); + } + + /** + * Returns the value associated with the given key, or 0.0 if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @return a double value + */ + @Override + public double getDouble(String key) { + return super.getDouble(key); + } + + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return a double value + */ + @Override + public double getDouble(String key, double defaultValue) { + return super.getDouble(key, defaultValue); + } + + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a String value, or null + */ + @Override + public String getString(String key) { + return super.getString(key); + } + + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String, or null + * @param defaultValue Value to return if key does not exist + * @return the String value associated with the given key, or defaultValue + * if no valid String object is currently mapped to that key. + */ + @Override + public String getString(String key, String defaultValue) { + return super.getString(key, defaultValue); + } + + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a Bundle value, or null + */ + @Override + public PersistableBundle getPersistableBundle(String key) { + return super.getPersistableBundle(key); + } + + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return an int[] value, or null + */ + @Override + public int[] getIntArray(String key) { + return super.getIntArray(key); + } + + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a long[] value, or null + */ + @Override + public long[] getLongArray(String key) { + return super.getLongArray(key); + } + + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a double[] value, or null + */ + @Override + public double[] getDoubleArray(String key) { + return super.getDoubleArray(key); + } + + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a String[] value, or null + */ + @Override + public String[] getStringArray(String key) { + return super.getStringArray(key); + } + + public static final Parcelable.Creator<PersistableBundle> CREATOR = + new Parcelable.Creator<PersistableBundle>() { + @Override + public PersistableBundle createFromParcel(Parcel in) { + return in.readPersistableBundle(); + } + + @Override + public PersistableBundle[] newArray(int size) { + return new PersistableBundle[size]; + } + }; + + /** + * Report the nature of this Parcelable's contents + */ + @Override + public int describeContents() { + return 0; + } + + /** + * Writes the PersistableBundle contents to a Parcel, typically in order for + * it to be passed through an IBinder connection. + * @param parcel The parcel to copy this bundle to. + */ + @Override + public void writeToParcel(Parcel parcel, int flags) { + final boolean oldAllowFds = parcel.pushAllowFds(false); + try { + super.writeToParcelInner(parcel, flags); + } finally { + parcel.restoreAllowFds(oldAllowFds); + } + } + + /** + * Reads the Parcel contents into this PersistableBundle, typically in order for + * it to be passed through an IBinder connection. + * @param parcel The parcel to overwrite this bundle from. + */ + public void readFromParcel(Parcel parcel) { + super.readFromParcelInner(parcel); + } + + @Override + synchronized public String toString() { + if (mParcelledData != null) { + if (mParcelledData == EMPTY_PARCEL) { + return "PersistableBundle[EMPTY_PARCEL]"; + } else { + return "PersistableBundle[mParcelledData.dataSize=" + + mParcelledData.dataSize() + "]"; + } + } + return "PersistableBundle[" + mMap.toString() + "]"; + } + +} diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 8aef9bd..63de9a0 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -300,7 +300,8 @@ public class UserManager { /** * Sets all the user-wide restrictions for this user. - * Requires the MANAGE_USERS permission. + * Requires the MANAGE_USERS permission or profile/device owner + * privileges. * @param restrictions the Bundle containing all the restrictions. */ public void setUserRestrictions(Bundle restrictions) { @@ -309,7 +310,8 @@ public class UserManager { /** * Sets all the user-wide restrictions for the specified user. - * Requires the MANAGE_USERS permission. + * Requires the MANAGE_USERS permission or profile/device owner + * privileges. * @param restrictions the Bundle containing all the restrictions. * @param userHandle the UserHandle of the user for whom to set the restrictions. */ @@ -323,7 +325,8 @@ public class UserManager { /** * Sets the value of a specific restriction. - * Requires the MANAGE_USERS permission. + * Requires the MANAGE_USERS permission or profile/device owner + * privileges. * @param key the key of the restriction * @param value the value for the restriction */ @@ -336,7 +339,8 @@ public class UserManager { /** * @hide * Sets the value of a specific restriction on a specific user. - * Requires the {@link android.Manifest.permission#MANAGE_USERS} permission. + * Requires the {@link android.Manifest.permission#MANAGE_USERS} permission or profile/device owner + * privileges. * @param key the key of the restriction * @param value the value for the restriction * @param userHandle the user whose restriction is to be changed. @@ -462,6 +466,8 @@ public class UserManager { /** * Returns list of the profiles of userHandle including * userHandle itself. + * Note that it this returns both enabled and not enabled profiles. See + * {@link #getUserProfiles()} if you need only the enabled ones. * * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. * @param userHandle profiles of this user will be returned. @@ -470,7 +476,7 @@ public class UserManager { */ public List<UserInfo> getProfiles(int userHandle) { try { - return mService.getProfiles(userHandle); + return mService.getProfiles(userHandle, false /* enabledOnly */); } catch (RemoteException re) { Log.w(TAG, "Could not get user list", re); return null; @@ -484,7 +490,13 @@ public class UserManager { */ public List<UserHandle> getUserProfiles() { ArrayList<UserHandle> profiles = new ArrayList<UserHandle>(); - List<UserInfo> users = getProfiles(UserHandle.myUserId()); + List<UserInfo> users = new ArrayList<UserInfo>(); + try { + users = mService.getProfiles(UserHandle.myUserId(), true /* enabledOnly */); + } catch (RemoteException re) { + Log.w(TAG, "Could not get user list", re); + return null; + } for (UserInfo info : users) { UserHandle userHandle = new UserHandle(info.id); profiles.add(userHandle); @@ -548,7 +560,7 @@ public class UserManager { /** * Returns information for all users on this device. Requires * {@link android.Manifest.permission#MANAGE_USERS} permission. - * + * * @param excludeDying specify if the list should exclude users being * removed. * @return the list of users that were created. diff --git a/core/java/android/preference/PreferenceFragment.java b/core/java/android/preference/PreferenceFragment.java index 11d8878..ff16f6c 100644 --- a/core/java/android/preference/PreferenceFragment.java +++ b/core/java/android/preference/PreferenceFragment.java @@ -329,6 +329,11 @@ public abstract class PreferenceFragment extends Fragment implements if (preferenceScreen != null) { preferenceScreen.bind(getListView()); } + onBindPreferences(); + } + + /** @hide */ + protected void onBindPreferences() { } /** @hide */ @@ -337,6 +342,26 @@ public abstract class PreferenceFragment extends Fragment implements return mList; } + /** @hide */ + public boolean hasListView() { + if (mList != null) { + return true; + } + View root = getView(); + if (root == null) { + return false; + } + View rawListView = root.findViewById(android.R.id.list); + if (!(rawListView instanceof ListView)) { + return false; + } + mList = (ListView)rawListView; + if (mList == null) { + return false; + } + return true; + } + private void ensureList() { if (mList != null) { return; diff --git a/core/java/android/preference/PreferenceGroupAdapter.java b/core/java/android/preference/PreferenceGroupAdapter.java index c2e1f51..381a5f0 100644 --- a/core/java/android/preference/PreferenceGroupAdapter.java +++ b/core/java/android/preference/PreferenceGroupAdapter.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import android.graphics.drawable.Drawable; import android.os.Handler; import android.preference.Preference.OnPreferenceChangeInternalListener; import android.view.View; @@ -91,7 +92,8 @@ public class PreferenceGroupAdapter extends BaseAdapter } }; - private int mActivatedPosition = -1; + private int mHighlightedPosition = -1; + private Drawable mHighlightedDrawable; private static class PreferenceLayout implements Comparable<PreferenceLayout> { private int resId; @@ -212,8 +214,18 @@ public class PreferenceGroupAdapter extends BaseAdapter return this.getItem(position).getId(); } - public void setActivated(int position) { - mActivatedPosition = position; + /** + * @hide + */ + public void setHighlighted(int position) { + mHighlightedPosition = position; + } + + /** + * @hide + */ + public void setHighlightedDrawable(Drawable drawable) { + mHighlightedDrawable = drawable; } public View getView(int position, View convertView, ViewGroup parent) { @@ -227,7 +239,10 @@ public class PreferenceGroupAdapter extends BaseAdapter convertView = null; } View result = preference.getView(convertView, parent); - result.setActivated(position == mActivatedPosition); + if (position == mHighlightedPosition && mHighlightedDrawable != null) { + result.setBackgroundDrawable(mHighlightedDrawable); + } + result.setTag(preference.getKey()); return result; } diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index f0520b5..9a768e0 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -57,6 +57,10 @@ import java.util.List; * <p> * To create a document provider, extend {@link DocumentsProvider}, which * provides a foundational implementation of this contract. + * <p> + * All client apps must hold a valid URI permission grant to access documents, + * typically issued when a user makes a selection through + * {@link Intent#ACTION_OPEN_DOCUMENT} or {@link Intent#ACTION_CREATE_DOCUMENT}. * * @see DocumentsProvider */ @@ -69,6 +73,8 @@ public final class DocumentsContract { // content://com.example/root/sdcard/search/?query=pony // content://com.example/document/12/ // content://com.example/document/12/children/ + // content://com.example/via/12/document/24/ + // content://com.example/via/12/document/24/children/ private DocumentsContract() { } @@ -425,6 +431,14 @@ public final class DocumentsContract { public static final int FLAG_SUPPORTS_SEARCH = 1 << 3; /** + * Flag indicating that this root supports directory selection. + * + * @see #COLUMN_FLAGS + * @see DocumentsProvider#isChildDocument(String, String) + */ + public static final int FLAG_SUPPORTS_DIR_SELECTION = 1 << 4; + + /** * Flag indicating that this root is currently empty. This may be used * to hide the root when opening documents, but the root will still be * shown when creating documents and {@link #FLAG_SUPPORTS_CREATE} is @@ -484,12 +498,15 @@ public final class DocumentsContract { /** {@hide} */ public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size"; + /** {@hide} */ + public static final String EXTRA_URI = "uri"; private static final String PATH_ROOT = "root"; private static final String PATH_RECENT = "recent"; private static final String PATH_DOCUMENT = "document"; private static final String PATH_CHILDREN = "children"; private static final String PATH_SEARCH = "search"; + private static final String PATH_VIA = "via"; private static final String PARAM_QUERY = "query"; private static final String PARAM_MANAGE = "manage"; @@ -532,6 +549,17 @@ public final class DocumentsContract { } /** + * Build URI representing access to descendant documents of the given + * {@link Document#COLUMN_DOCUMENT_ID}. + * + * @see #getViaDocumentId(Uri) + */ + public static Uri buildViaUri(String authority, String documentId) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) + .appendPath(PATH_VIA).appendPath(documentId).build(); + } + + /** * Build URI representing the given {@link Document#COLUMN_DOCUMENT_ID} in a * document provider. When queried, a provider will return a single row with * columns defined by {@link Document}. @@ -545,6 +573,41 @@ public final class DocumentsContract { } /** + * Build URI representing the given {@link Document#COLUMN_DOCUMENT_ID} in a + * document provider. Instead of directly accessing the target document, + * gain access via another document. The target document must be a + * descendant (child, grandchild, etc) of the via document. + * <p> + * This is typically used to access documents under a user-selected + * directory, since it doesn't require the user to separately confirm each + * new document access. + * + * @param viaUri a related document (directory) that the caller is + * leveraging to gain access to the target document. The target + * document must be a descendant of this directory. + * @param documentId the target document, which the caller may not have + * direct access to. + * @see Intent#ACTION_PICK_DIRECTORY + * @see DocumentsProvider#isChildDocument(String, String) + * @see #buildDocumentUri(String, String) + */ + public static Uri buildDocumentViaUri(Uri viaUri, String documentId) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(viaUri.getAuthority()).appendPath(PATH_VIA) + .appendPath(getViaDocumentId(viaUri)).appendPath(PATH_DOCUMENT) + .appendPath(documentId).build(); + } + + /** {@hide} */ + public static Uri buildDocumentMaybeViaUri(Uri baseUri, String documentId) { + if (isViaUri(baseUri)) { + return buildDocumentViaUri(baseUri, documentId); + } else { + return buildDocumentUri(baseUri.getAuthority(), documentId); + } + } + + /** * Build URI representing the children of the given directory in a document * provider. When queried, a provider will return zero or more rows with * columns defined by {@link Document}. @@ -562,6 +625,32 @@ public final class DocumentsContract { } /** + * Build URI representing the children of the given directory in a document + * provider. Instead of directly accessing the target document, gain access + * via another document. The target document must be a descendant (child, + * grandchild, etc) of the via document. + * <p> + * This is typically used to access documents under a user-selected + * directory, since it doesn't require the user to separately confirm each + * new document access. + * + * @param viaUri a related document (directory) that the caller is + * leveraging to gain access to the target document. The target + * document must be a descendant of this directory. + * @param parentDocumentId the target document, which the caller may not + * have direct access to. + * @see Intent#ACTION_PICK_DIRECTORY + * @see DocumentsProvider#isChildDocument(String, String) + * @see #buildChildDocumentsUri(String, String) + */ + public static Uri buildChildDocumentsViaUri(Uri viaUri, String parentDocumentId) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(viaUri.getAuthority()).appendPath(PATH_VIA) + .appendPath(getViaDocumentId(viaUri)).appendPath(PATH_DOCUMENT) + .appendPath(parentDocumentId).appendPath(PATH_CHILDREN).build(); + } + + /** * Build URI representing a search for matching documents under a specific * root in a document provider. When queried, a provider will return zero or * more rows with columns defined by {@link Document}. @@ -580,21 +669,31 @@ public final class DocumentsContract { /** * Test if the given URI represents a {@link Document} backed by a * {@link DocumentsProvider}. + * + * @see #buildDocumentUri(String, String) + * @see #buildDocumentViaUri(Uri, String) */ public static boolean isDocumentUri(Context context, Uri uri) { final List<String> paths = uri.getPathSegments(); - if (paths.size() < 2) { - return false; - } - if (!PATH_DOCUMENT.equals(paths.get(0))) { - return false; + if (paths.size() >= 2 + && (PATH_DOCUMENT.equals(paths.get(0)) || PATH_VIA.equals(paths.get(0)))) { + return isDocumentsProvider(context, uri.getAuthority()); } + return false; + } + + /** {@hide} */ + public static boolean isViaUri(Uri uri) { + final List<String> paths = uri.getPathSegments(); + return (paths.size() >= 2 && PATH_VIA.equals(paths.get(0))); + } + private static boolean isDocumentsProvider(Context context, String authority) { final Intent intent = new Intent(PROVIDER_INTERFACE); final List<ResolveInfo> infos = context.getPackageManager() .queryIntentContentProviders(intent, 0); for (ResolveInfo info : infos) { - if (uri.getAuthority().equals(info.providerInfo.authority)) { + if (authority.equals(info.providerInfo.authority)) { return true; } } @@ -606,27 +705,40 @@ public final class DocumentsContract { */ public static String getRootId(Uri rootUri) { final List<String> paths = rootUri.getPathSegments(); - if (paths.size() < 2) { - throw new IllegalArgumentException("Not a root: " + rootUri); - } - if (!PATH_ROOT.equals(paths.get(0))) { - throw new IllegalArgumentException("Not a root: " + rootUri); + if (paths.size() >= 2 && PATH_ROOT.equals(paths.get(0))) { + return paths.get(1); } - return paths.get(1); + throw new IllegalArgumentException("Invalid URI: " + rootUri); } /** * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given URI. + * + * @see #isDocumentUri(Context, Uri) */ public static String getDocumentId(Uri documentUri) { final List<String> paths = documentUri.getPathSegments(); - if (paths.size() < 2) { - throw new IllegalArgumentException("Not a document: " + documentUri); + if (paths.size() >= 2 && PATH_DOCUMENT.equals(paths.get(0))) { + return paths.get(1); } - if (!PATH_DOCUMENT.equals(paths.get(0))) { - throw new IllegalArgumentException("Not a document: " + documentUri); + if (paths.size() >= 4 && PATH_VIA.equals(paths.get(0)) + && PATH_DOCUMENT.equals(paths.get(2))) { + return paths.get(3); } - return paths.get(1); + throw new IllegalArgumentException("Invalid URI: " + documentUri); + } + + /** + * Extract the via {@link Document#COLUMN_DOCUMENT_ID} from the given URI. + * + * @see #isViaUri(Uri) + */ + public static String getViaDocumentId(Uri documentUri) { + final List<String> paths = documentUri.getPathSegments(); + if (paths.size() >= 2 && PATH_VIA.equals(paths.get(0))) { + return paths.get(1); + } + throw new IllegalArgumentException("Invalid URI: " + documentUri); } /** @@ -758,7 +870,6 @@ public final class DocumentsContract { * @param mimeType MIME type of new document * @param displayName name of new document * @return newly created document, or {@code null} if failed - * @hide */ public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri, String mimeType, String displayName) { @@ -778,13 +889,12 @@ public final class DocumentsContract { public static Uri createDocument(ContentProviderClient client, Uri parentDocumentUri, String mimeType, String displayName) throws RemoteException { final Bundle in = new Bundle(); - in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(parentDocumentUri)); + in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri); in.putString(Document.COLUMN_MIME_TYPE, mimeType); in.putString(Document.COLUMN_DISPLAY_NAME, displayName); final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in); - return buildDocumentUri( - parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID)); + return out.getParcelable(DocumentsContract.EXTRA_URI); } /** @@ -811,7 +921,7 @@ public final class DocumentsContract { public static void deleteDocument(ContentProviderClient client, Uri documentUri) throws RemoteException { final Bundle in = new Bundle(); - in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri)); + in.putParcelable(DocumentsContract.EXTRA_URI, documentUri); client.call(METHOD_DELETE_DOCUMENT, null, in); } diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java index 49816f8..1a7a00f2 100644 --- a/core/java/android/provider/DocumentsProvider.java +++ b/core/java/android/provider/DocumentsProvider.java @@ -46,6 +46,7 @@ import android.util.Log; import libcore.io.IoUtils; import java.io.FileNotFoundException; +import java.util.Objects; /** * Base class for a document provider. A document provider offers read and write @@ -125,6 +126,8 @@ public abstract class DocumentsProvider extends ContentProvider { private static final int MATCH_SEARCH = 4; private static final int MATCH_DOCUMENT = 5; private static final int MATCH_CHILDREN = 6; + private static final int MATCH_DOCUMENT_VIA = 7; + private static final int MATCH_CHILDREN_VIA = 8; private String mAuthority; @@ -144,6 +147,8 @@ public abstract class DocumentsProvider extends ContentProvider { mMatcher.addURI(mAuthority, "root/*/search", MATCH_SEARCH); mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT); mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN); + mMatcher.addURI(mAuthority, "via/*/document/*", MATCH_DOCUMENT_VIA); + mMatcher.addURI(mAuthority, "via/*/document/*/children", MATCH_CHILDREN_VIA); // Sanity check our setup if (!info.exported) { @@ -161,6 +166,35 @@ public abstract class DocumentsProvider extends ContentProvider { } /** + * Test if a document is descendant (child, grandchild, etc) from the given + * parent. Providers must override this to support directory selection. You + * should avoid making network requests to keep this request fast. + * + * @param parentDocumentId parent to verify against. + * @param documentId child to verify. + * @return if given document is a descendant of the given parent. + * @see DocumentsContract.Root#FLAG_SUPPORTS_DIR_SELECTION + */ + public boolean isChildDocument(String parentDocumentId, String documentId) { + return false; + } + + /** {@hide} */ + private void enforceVia(Uri documentUri) { + if (DocumentsContract.isViaUri(documentUri)) { + final String parent = DocumentsContract.getViaDocumentId(documentUri); + final String child = DocumentsContract.getDocumentId(documentUri); + if (Objects.equals(parent, child)) { + return; + } + if (!isChildDocument(parent, child)) { + throw new SecurityException( + "Document " + child + " is not a descendant of " + parent); + } + } + } + + /** * Create a new document and return its newly generated * {@link Document#COLUMN_DOCUMENT_ID}. You must allocate a new * {@link Document#COLUMN_DOCUMENT_ID} to represent the document, which must @@ -182,9 +216,10 @@ public abstract class DocumentsProvider extends ContentProvider { /** * Delete the requested document. Upon returning, any URI permission grants - * for the requested document will be revoked. If additional documents were - * deleted as a side effect of this call, such as documents inside a - * directory, the implementor is responsible for revoking those permissions. + * for the given document will be revoked. If additional documents were + * deleted as a side effect of this call (such as documents inside a + * directory) the implementor is responsible for revoking those permissions + * using {@link #revokeDocumentPermission(String)}. * * @param documentId the document to delete. */ @@ -420,8 +455,12 @@ public abstract class DocumentsProvider extends ContentProvider { return querySearchDocuments( getRootId(uri), getSearchDocumentsQuery(uri), projection); case MATCH_DOCUMENT: + case MATCH_DOCUMENT_VIA: + enforceVia(uri); return queryDocument(getDocumentId(uri), projection); case MATCH_CHILDREN: + case MATCH_CHILDREN_VIA: + enforceVia(uri); if (DocumentsContract.isManageMode(uri)) { return queryChildDocumentsForManage( getDocumentId(uri), projection, sortOrder); @@ -449,6 +488,8 @@ public abstract class DocumentsProvider extends ContentProvider { case MATCH_ROOT: return DocumentsContract.Root.MIME_TYPE_ITEM; case MATCH_DOCUMENT: + case MATCH_DOCUMENT_VIA: + enforceVia(uri); return getDocumentType(getDocumentId(uri)); default: return null; @@ -460,6 +501,49 @@ public abstract class DocumentsProvider extends ContentProvider { } /** + * Implementation is provided by the parent class. Can be overridden to + * provide additional functionality, but subclasses <em>must</em> always + * call the superclass. If the superclass returns {@code null}, the subclass + * may implement custom behavior. + * <p> + * This is typically used to resolve a "via" URI into a concrete document + * reference, issuing a narrower single-document URI permission grant along + * the way. + * + * @see DocumentsContract#buildDocumentViaUri(Uri, String) + */ + @Override + public Uri canonicalize(Uri uri) { + final Context context = getContext(); + switch (mMatcher.match(uri)) { + case MATCH_DOCUMENT_VIA: + enforceVia(uri); + + final Uri narrowUri = DocumentsContract.buildDocumentUri(uri.getAuthority(), + DocumentsContract.getDocumentId(uri)); + + // Caller may only have prefix grant, so extend them a grant to + // the narrow Uri. Caller already holds read grant to get here, + // so check for any other modes we should extend. + int modeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION; + if (context.checkCallingOrSelfUriPermission(uri, + Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + == PackageManager.PERMISSION_GRANTED) { + modeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION; + } + if (context.checkCallingOrSelfUriPermission(uri, + Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) + == PackageManager.PERMISSION_GRANTED) { + modeFlags |= Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION; + } + context.grantUriPermission(getCallingPackage(), narrowUri, modeFlags); + return narrowUri; + } + return null; + } + + /** * Implementation is provided by the parent class. Throws by default, and * cannot be overriden. * @@ -496,54 +580,47 @@ public abstract class DocumentsProvider extends ContentProvider { * provide additional functionality, but subclasses <em>must</em> always * call the superclass. If the superclass returns {@code null}, the subclass * may implement custom behavior. - * - * @see #openDocument(String, String, CancellationSignal) - * @see #deleteDocument(String) */ @Override public Bundle call(String method, String arg, Bundle extras) { - final Context context = getContext(); - if (!method.startsWith("android:")) { - // Let non-platform methods pass through + // Ignore non-platform methods return super.call(method, arg, extras); } - final String documentId = extras.getString(Document.COLUMN_DOCUMENT_ID); - final Uri documentUri = DocumentsContract.buildDocumentUri(mAuthority, documentId); + final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI); + final String authority = documentUri.getAuthority(); + final String documentId = DocumentsContract.getDocumentId(documentUri); - // Require that caller can manage requested document - final boolean callerHasManage = - context.checkCallingOrSelfPermission(android.Manifest.permission.MANAGE_DOCUMENTS) - == PackageManager.PERMISSION_GRANTED; - enforceWritePermissionInner(documentUri); + if (!mAuthority.equals(authority)) { + throw new SecurityException( + "Requested authority " + authority + " doesn't match provider " + mAuthority); + } + enforceVia(documentUri); final Bundle out = new Bundle(); try { if (METHOD_CREATE_DOCUMENT.equals(method)) { + enforceWritePermissionInner(documentUri); + final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE); final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME); final String newDocumentId = createDocument(documentId, mimeType, displayName); - out.putString(Document.COLUMN_DOCUMENT_ID, newDocumentId); - - // Extend permission grant towards caller if needed - if (!callerHasManage) { - final Uri newDocumentUri = DocumentsContract.buildDocumentUri( - mAuthority, newDocumentId); - context.grantUriPermission(getCallingPackage(), newDocumentUri, - Intent.FLAG_GRANT_READ_URI_PERMISSION - | Intent.FLAG_GRANT_WRITE_URI_PERMISSION - | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); - } + + // No need to issue new grants here, since caller either has + // manage permission or a prefix grant. We might generate a + // "via" style URI if that's how they called us. + final Uri newDocumentUri = DocumentsContract.buildDocumentMaybeViaUri(documentUri, + newDocumentId); + out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri); } else if (METHOD_DELETE_DOCUMENT.equals(method)) { + enforceWritePermissionInner(documentUri); deleteDocument(documentId); // Document no longer exists, clean up any grants - context.revokeUriPermission(documentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION - | Intent.FLAG_GRANT_WRITE_URI_PERMISSION - | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); + revokeDocumentPermission(documentId); } else { throw new UnsupportedOperationException("Method not supported " + method); @@ -555,12 +632,25 @@ public abstract class DocumentsProvider extends ContentProvider { } /** + * Revoke any active permission grants for the given + * {@link Document#COLUMN_DOCUMENT_ID}, usually called when a document + * becomes invalid. Follows the same semantics as + * {@link Context#revokeUriPermission(Uri, int)}. + */ + public final void revokeDocumentPermission(String documentId) { + final Context context = getContext(); + context.revokeUriPermission(DocumentsContract.buildDocumentUri(mAuthority, documentId), ~0); + context.revokeUriPermission(DocumentsContract.buildViaUri(mAuthority, documentId), ~0); + } + + /** * Implementation is provided by the parent class. Cannot be overriden. * * @see #openDocument(String, String, CancellationSignal) */ @Override public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { + enforceVia(uri); return openDocument(getDocumentId(uri), mode, null); } @@ -572,17 +662,47 @@ public abstract class DocumentsProvider extends ContentProvider { @Override public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal) throws FileNotFoundException { + enforceVia(uri); return openDocument(getDocumentId(uri), mode, signal); } /** * Implementation is provided by the parent class. Cannot be overriden. * + * @see #openDocument(String, String, CancellationSignal) + */ + @Override + @SuppressWarnings("resource") + public final AssetFileDescriptor openAssetFile(Uri uri, String mode) + throws FileNotFoundException { + enforceVia(uri); + final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, null); + return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null; + } + + /** + * Implementation is provided by the parent class. Cannot be overriden. + * + * @see #openDocument(String, String, CancellationSignal) + */ + @Override + @SuppressWarnings("resource") + public final AssetFileDescriptor openAssetFile(Uri uri, String mode, CancellationSignal signal) + throws FileNotFoundException { + enforceVia(uri); + final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, signal); + return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null; + } + + /** + * Implementation is provided by the parent class. Cannot be overriden. + * * @see #openDocumentThumbnail(String, Point, CancellationSignal) */ @Override public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts) throws FileNotFoundException { + enforceVia(uri); if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) { final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE); return openDocumentThumbnail(getDocumentId(uri), sizeHint, null); @@ -600,6 +720,7 @@ public abstract class DocumentsProvider extends ContentProvider { public final AssetFileDescriptor openTypedAssetFile( Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal) throws FileNotFoundException { + enforceVia(uri); if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) { final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE); return openDocumentThumbnail(getDocumentId(uri), sizeHint, signal); diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index ae24968..cfab1b3 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -109,14 +109,18 @@ public final class MediaStore { * An intent to perform a search for music media and automatically play content from the * result when possible. This can be fired, for example, by the result of a voice recognition * command to listen to music. - * <p> - * Contains the {@link android.app.SearchManager#QUERY} extra, which is a string - * that can contain any type of unstructured music search, like the name of an artist, - * an album, a song, a genre, or any combination of these. - * <p> - * Because this intent includes an open-ended unstructured search string, it makes the most - * sense for apps that can support large-scale search of music, such as services connected - * to an online database of music which can be streamed and played on the device. + * <p>This intent always includes the {@link android.provider.MediaStore#EXTRA_MEDIA_FOCUS} + * and {@link android.app.SearchManager#QUERY} extras. The + * {@link android.provider.MediaStore#EXTRA_MEDIA_FOCUS} extra determines the search mode, and + * the value of the {@link android.app.SearchManager#QUERY} extra depends on the search mode. + * For more information about the search modes for this intent, see + * <a href="{@docRoot}guide/components/intents-common.html#PlaySearch">Play music based + * on a search query</a> in <a href="{@docRoot}guide/components/intents-common.html">Common + * Intents</a>.</p> + * + * <p>This intent makes the most sense for apps that can support large-scale search of music, + * such as services connected to an online database of music which can be streamed and played + * on the device.</p> */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH = diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 7062933..7f9f862 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5097,6 +5097,13 @@ public final class Settings { public static final String NETWORK_PREFERENCE = "network_preference"; /** + * Which package name to use for network scoring. If null, or if the package is not a valid + * scorer app, external network scores will neither be requested nor accepted. + * @hide + */ + public static final String NETWORK_SCORER_APP = "network_scorer_app"; + + /** * If the NITZ_UPDATE_DIFF time is exceeded then an automatic adjustment * to SystemClock will be allowed even if NITZ_UPDATE_SPACING has not been * exceeded. @@ -5963,6 +5970,12 @@ public final class Settings { public static final String SHOW_PROCESSES = "show_processes"; /** + * If 1 low power mode is enabled. + * @hide + */ + public static final String LOW_POWER_MODE = "low_power"; + + /** * If 1, the activity manager will aggressively finish activities and * processes as soon as they are no longer needed. If 0, the normal * extended lifetime is used. @@ -6113,6 +6126,13 @@ public final class Settings { } /** + * Opaque value, changes when persisted zen mode configuration changes. + * + * @hide + */ + public static final String ZEN_MODE_CONFIG_ETAG = "zen_mode_config_etag"; + + /** * Defines global heads up toggle. One of HEADS_UP_OFF, HEADS_UP_ON. * * @hide diff --git a/core/java/android/service/notification/ZenModeConfig.aidl b/core/java/android/service/notification/ZenModeConfig.aidl new file mode 100644 index 0000000..c73b75e --- /dev/null +++ b/core/java/android/service/notification/ZenModeConfig.aidl @@ -0,0 +1,20 @@ +/* + * 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.notification; + +parcelable ZenModeConfig; + diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java new file mode 100644 index 0000000..925ddcf --- /dev/null +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -0,0 +1,234 @@ +/** + * 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.notification; + +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.util.Objects; + +/** + * Persisted configuration for zen mode. + * + * @hide + */ +public class ZenModeConfig implements Parcelable { + + public static final String SLEEP_MODE_NIGHTS = "nights"; + public static final String SLEEP_MODE_WEEKNIGHTS = "weeknights"; + + private static final int XML_VERSION = 1; + private static final String ZEN_TAG = "zen"; + private static final String ZEN_ATT_VERSION = "version"; + private static final String ALLOW_TAG = "allow"; + private static final String ALLOW_ATT_CALLS = "calls"; + private static final String ALLOW_ATT_MESSAGES = "messages"; + private static final String SLEEP_TAG = "sleep"; + private static final String SLEEP_ATT_MODE = "mode"; + + private static final String SLEEP_ATT_START_HR = "startHour"; + private static final String SLEEP_ATT_START_MIN = "startMin"; + private static final String SLEEP_ATT_END_HR = "endHour"; + private static final String SLEEP_ATT_END_MIN = "endMin"; + + public boolean allowCalls; + public boolean allowMessages; + + public String sleepMode; + public int sleepStartHour; + public int sleepStartMinute; + public int sleepEndHour; + public int sleepEndMinute; + + public ZenModeConfig() { } + + public ZenModeConfig(Parcel source) { + allowCalls = source.readInt() == 1; + allowMessages = source.readInt() == 1; + if (source.readInt() == 1) { + sleepMode = source.readString(); + } + sleepStartHour = source.readInt(); + sleepStartMinute = source.readInt(); + sleepEndHour = source.readInt(); + sleepEndMinute = source.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(allowCalls ? 1 : 0); + dest.writeInt(allowMessages ? 1 : 0); + if (sleepMode != null) { + dest.writeInt(1); + dest.writeString(sleepMode); + } else { + dest.writeInt(0); + } + dest.writeInt(sleepStartHour); + dest.writeInt(sleepStartMinute); + dest.writeInt(sleepEndHour); + dest.writeInt(sleepEndMinute); + } + + @Override + public String toString() { + return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[') + .append("allowCalls=").append(allowCalls) + .append(",allowMessages=").append(allowMessages) + .append(",sleepMode=").append(sleepMode) + .append(",sleepStart=").append(sleepStartHour).append('.').append(sleepStartMinute) + .append(",sleepEnd=").append(sleepEndHour).append('.').append(sleepEndMinute) + .append(']').toString(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof ZenModeConfig)) return false; + if (o == this) return true; + final ZenModeConfig other = (ZenModeConfig) o; + return other.allowCalls == allowCalls + && other.allowMessages == allowMessages + && Objects.equals(other.sleepMode, sleepMode) + && other.sleepStartHour == sleepStartHour + && other.sleepStartMinute == sleepStartMinute + && other.sleepEndHour == sleepEndHour + && other.sleepEndMinute == sleepEndMinute; + } + + @Override + public int hashCode() { + return Objects.hash(allowCalls, allowMessages, sleepMode, sleepStartHour, + sleepStartMinute, sleepEndHour, sleepEndMinute); + } + + public boolean isValid() { + return isValidHour(sleepStartHour) && isValidMinute(sleepStartMinute) + && isValidHour(sleepEndHour) && isValidMinute(sleepEndMinute) + && (sleepMode == null || sleepMode.equals(SLEEP_MODE_NIGHTS) + || sleepMode.equals(SLEEP_MODE_WEEKNIGHTS)); + } + + public static ZenModeConfig readXml(XmlPullParser parser) + throws XmlPullParserException, IOException { + int type = parser.getEventType(); + if (type != XmlPullParser.START_TAG) return null; + String tag = parser.getName(); + if (!ZEN_TAG.equals(tag)) return null; + final ZenModeConfig rt = new ZenModeConfig(); + final int version = Integer.parseInt(parser.getAttributeValue(null, ZEN_ATT_VERSION)); + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { + tag = parser.getName(); + if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) return rt; + if (type == XmlPullParser.START_TAG) { + if (ALLOW_TAG.equals(tag)) { + rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false); + rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false); + } else if (SLEEP_TAG.equals(tag)) { + final String mode = parser.getAttributeValue(null, SLEEP_ATT_MODE); + rt.sleepMode = (SLEEP_MODE_NIGHTS.equals(mode) + || SLEEP_MODE_WEEKNIGHTS.equals(mode)) ? mode : null; + final int startHour = safeInt(parser, SLEEP_ATT_START_HR, 0); + final int startMinute = safeInt(parser, SLEEP_ATT_START_MIN, 0); + final int endHour = safeInt(parser, SLEEP_ATT_END_HR, 0); + final int endMinute = safeInt(parser, SLEEP_ATT_END_MIN, 0); + rt.sleepStartHour = isValidHour(startHour) ? startHour : 0; + rt.sleepStartMinute = isValidMinute(startMinute) ? startMinute : 0; + rt.sleepEndHour = isValidHour(endHour) ? endHour : 0; + rt.sleepEndMinute = isValidMinute(endMinute) ? endMinute : 0; + } + } + } + return rt; + } + + public void writeXml(XmlSerializer out) throws IOException { + out.startTag(null, ZEN_TAG); + out.attribute(null, ZEN_ATT_VERSION, Integer.toString(XML_VERSION)); + + out.startTag(null, ALLOW_TAG); + out.attribute(null, ALLOW_ATT_CALLS, Boolean.toString(allowCalls)); + out.attribute(null, ALLOW_ATT_MESSAGES, Boolean.toString(allowMessages)); + out.endTag(null, ALLOW_TAG); + + out.startTag(null, SLEEP_TAG); + if (sleepMode != null) { + out.attribute(null, SLEEP_ATT_MODE, sleepMode); + } + out.attribute(null, SLEEP_ATT_START_HR, Integer.toString(sleepStartHour)); + out.attribute(null, SLEEP_ATT_START_MIN, Integer.toString(sleepStartMinute)); + out.attribute(null, SLEEP_ATT_END_HR, Integer.toString(sleepEndHour)); + out.attribute(null, SLEEP_ATT_END_MIN, Integer.toString(sleepEndMinute)); + out.endTag(null, SLEEP_TAG); + + out.endTag(null, ZEN_TAG); + } + + public static boolean isValidHour(int val) { + return val >= 0 && val < 24; + } + + public static boolean isValidMinute(int val) { + return val >= 0 && val < 60; + } + + private static boolean safeBoolean(XmlPullParser parser, String att, boolean defValue) { + final String val = parser.getAttributeValue(null, att); + if (TextUtils.isEmpty(val)) return defValue; + return Boolean.valueOf(val); + } + + private static int safeInt(XmlPullParser parser, String att, int defValue) { + final String val = parser.getAttributeValue(null, att); + if (TextUtils.isEmpty(val)) return defValue; + return Integer.valueOf(val); + } + + @Override + public int describeContents() { + return 0; + } + + public ZenModeConfig copy() { + final Parcel parcel = Parcel.obtain(); + try { + writeToParcel(parcel, 0); + parcel.setDataPosition(0); + return new ZenModeConfig(parcel); + } finally { + parcel.recycle(); + } + } + + public static final Parcelable.Creator<ZenModeConfig> CREATOR + = new Parcelable.Creator<ZenModeConfig>() { + @Override + public ZenModeConfig createFromParcel(Parcel source) { + return new ZenModeConfig(source); + } + + @Override + public ZenModeConfig[] newArray(int size) { + return new ZenModeConfig[size]; + } + }; +} diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 638ef22..f7ac75a 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -653,7 +653,7 @@ public class StaticLayout extends Layout { int extra; - if (needMultiply) { + if (needMultiply && end != bufEnd) { double ex = (below - above) * (spacingmult - 1) + spacingadd; if (ex >= 0) { extra = (int)(ex + EXTRA_ROUNDING); diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java index b7ae31e..c67d6fa 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -1585,7 +1585,7 @@ public abstract class Transition implements Cloneable { * hierarchy underneath it. */ void capturePropagationValues(TransitionValues transitionValues) { - if (mPropagation != null) { + if (mPropagation != null && !transitionValues.values.isEmpty()) { String[] propertyNames = mPropagation.getPropagationProperties(); if (propertyNames == null) { return; diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java index f675c6a..14ecc15 100644 --- a/core/java/android/transition/TransitionInflater.java +++ b/core/java/android/transition/TransitionInflater.java @@ -309,15 +309,11 @@ public class TransitionInflater { if (transitionId >= 0) { Transition transition = inflateTransition(transitionId); if (transition != null) { + if (toScene == null) { + throw new RuntimeException("No toScene for transition ID " + transitionId); + } if (fromScene == null) { - if (toScene == null) { - throw new RuntimeException("No matching fromScene or toScene " + - "for transition ID " + transitionId); - } else { - transitionManager.setTransition(toScene, transition); - } - } else if (toScene == null) { - transitionManager.setExitTransition(fromScene, transition); + transitionManager.setTransition(toScene, transition); } else { transitionManager.setTransition(fromScene, toScene, transition); } diff --git a/core/java/android/transition/TransitionManager.java b/core/java/android/transition/TransitionManager.java index 1614d34..ce3cc2f 100644 --- a/core/java/android/transition/TransitionManager.java +++ b/core/java/android/transition/TransitionManager.java @@ -70,7 +70,6 @@ public class TransitionManager { private static final String[] EMPTY_STRINGS = new String[0]; ArrayMap<Scene, Transition> mSceneTransitions = new ArrayMap<Scene, Transition>(); - ArrayMap<Scene, Transition> mExitSceneTransitions = new ArrayMap<Scene, Transition>(); ArrayMap<Scene, ArrayMap<Scene, Transition>> mScenePairTransitions = new ArrayMap<Scene, ArrayMap<Scene, Transition>>(); private static ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>> @@ -119,21 +118,6 @@ public class TransitionManager { } /** - * Sets a specific transition to occur when the given scene is exited. This - * has the lowest priority -- if a Scene-to-Scene transition or - * Scene enter transition can be applied, it will. - * - * @param scene The scene which, when exited, will cause the given - * transition to run. - * @param transition The transition that will play when the given scene is - * exited. A value of null will result in the default behavior of - * using the default transition instead. - */ - public void setExitTransition(Scene scene, Transition transition) { - mExitSceneTransitions.put(scene, transition); - } - - /** * Sets a specific transition to occur when the given pair of scenes is * exited/entered. * @@ -181,9 +165,6 @@ public class TransitionManager { } } transition = mSceneTransitions.get(scene); - if (transition == null && sceneRoot != null) { - transition = mExitSceneTransitions.get(Scene.getCurrentScene(sceneRoot)); - } return (transition != null) ? transition : sDefaultTransition; } @@ -239,34 +220,6 @@ public class TransitionManager { } /** - * Retrieve the transition to a target defined scene if one has been - * associated with this TransitionManager. - * - * @param toScene Target scene that this transition will move to - * @return Transition corresponding to the given toScene or null - * if no association exists in this TransitionManager - * - * @see #setTransition(Scene, Transition) - * @hide - */ - public Transition getEnterTransition(Scene toScene) { - return mSceneTransitions.get(toScene); - } - - /** - * Retrieve the transition from a defined scene to a target named scene if one has been - * associated with this TransitionManager. - * - * @param fromScene Scene that this transition starts from - * @return Transition corresponding to the given fromScene or null - * if no association exists in this TransitionManager - * @hide - */ - public Transition getExitTransition(Scene fromScene) { - return mExitSceneTransitions.get(fromScene); - } - - /** * This private utility class is used to listen for both OnPreDraw and * OnAttachStateChange events. OnPreDraw events are the main ones we care * about since that's what triggers the transition to take place. diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java index 966b24d..9081234 100644 --- a/core/java/android/transition/TransitionSet.java +++ b/core/java/android/transition/TransitionSet.java @@ -256,11 +256,45 @@ public class TransitionSet extends Transition { @Override protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues, TransitionValuesMaps endValues) { + startValues = removeExcludes(startValues); + endValues = removeExcludes(endValues); for (Transition childTransition : mTransitions) { childTransition.createAnimators(sceneRoot, startValues, endValues); } } + private TransitionValuesMaps removeExcludes(TransitionValuesMaps values) { + if (mTargetIds.isEmpty() && mTargetIdExcludes == null && mTargetTypeExcludes == null + && mTargets.isEmpty()) { + return values; + } + TransitionValuesMaps included = new TransitionValuesMaps(); + int numValues = values.viewValues.size(); + for (int i = 0; i < numValues; i++) { + View view = values.viewValues.keyAt(i); + if (isValidTarget(view, view.getId())) { + included.viewValues.put(view, values.viewValues.valueAt(i)); + } + } + numValues = values.idValues.size(); + for (int i = 0; i < numValues; i++) { + int id = values.idValues.keyAt(i); + TransitionValues transitionValues = values.idValues.valueAt(i); + if (isValidTarget(transitionValues.view, id)) { + included.idValues.put(id, transitionValues); + } + } + numValues = values.itemIdValues.size(); + for (int i = 0; i < numValues; i++) { + long id = values.itemIdValues.keyAt(i); + TransitionValues transitionValues = values.itemIdValues.valueAt(i); + if (isValidTarget(transitionValues.view, id)) { + included.itemIdValues.put(id, transitionValues); + } + } + return included; + } + /** * @hide */ diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java index 7783b6f..526803a 100644 --- a/core/java/android/transition/Visibility.java +++ b/core/java/android/transition/Visibility.java @@ -109,14 +109,14 @@ public abstract class Visibility extends Transition { final VisibilityInfo visInfo = new VisibilityInfo(); visInfo.visibilityChange = false; visInfo.fadeIn = false; - if (startValues != null) { + if (startValues != null && startValues.values.containsKey(PROPNAME_VISIBILITY)) { visInfo.startVisibility = (Integer) startValues.values.get(PROPNAME_VISIBILITY); visInfo.startParent = (ViewGroup) startValues.values.get(PROPNAME_PARENT); } else { visInfo.startVisibility = -1; visInfo.startParent = null; } - if (endValues != null) { + if (endValues != null && endValues.values.containsKey(PROPNAME_VISIBILITY)) { visInfo.endVisibility = (Integer) endValues.values.get(PROPNAME_VISIBILITY); visInfo.endParent = (ViewGroup) endValues.values.get(PROPNAME_PARENT); } else { diff --git a/core/java/android/tv/ITvInputManager.aidl b/core/java/android/tv/ITvInputManager.aidl index a927dc9..a4c99e4 100644 --- a/core/java/android/tv/ITvInputManager.aidl +++ b/core/java/android/tv/ITvInputManager.aidl @@ -17,6 +17,7 @@ package android.tv; import android.content.ComponentName; +import android.graphics.Rect; import android.net.Uri; import android.tv.ITvInputClient; import android.tv.TvInputInfo; @@ -40,4 +41,9 @@ interface ITvInputManager { void setSurface(in IBinder sessionToken, in Surface surface, int userId); void setVolume(in IBinder sessionToken, float volume, int userId); void tune(in IBinder sessionToken, in Uri channelUri, int userId); + + void createOverlayView(in IBinder sessionToken, in IBinder windowToken, in Rect frame, + int userId); + void relayoutOverlayView(in IBinder sessionToken, in Rect frame, int userId); + void removeOverlayView(in IBinder sessionToken, int userId); } diff --git a/core/java/android/tv/ITvInputService.aidl b/core/java/android/tv/ITvInputService.aidl index d80f286..672784f 100644 --- a/core/java/android/tv/ITvInputService.aidl +++ b/core/java/android/tv/ITvInputService.aidl @@ -17,7 +17,6 @@ package android.tv; import android.tv.ITvInputServiceCallback; -import android.tv.ITvInputSession; import android.tv.ITvInputSessionCallback; /** diff --git a/core/java/android/tv/ITvInputSession.aidl b/core/java/android/tv/ITvInputSession.aidl index d379d2d..32fee4b 100644 --- a/core/java/android/tv/ITvInputSession.aidl +++ b/core/java/android/tv/ITvInputSession.aidl @@ -16,6 +16,7 @@ package android.tv; +import android.graphics.Rect; import android.net.Uri; import android.view.Surface; @@ -31,4 +32,8 @@ oneway interface ITvInputSession { // is to introduce some new concepts that will solve a number of problems in audio policy today. void setVolume(float volume); void tune(in Uri channelUri); + + void createOverlayView(in IBinder windowToken, in Rect frame); + void relayoutOverlayView(in Rect frame); + void removeOverlayView(); } diff --git a/core/java/android/tv/ITvInputSessionWrapper.java b/core/java/android/tv/ITvInputSessionWrapper.java index 66fe5e1..a6e0877 100644 --- a/core/java/android/tv/ITvInputSessionWrapper.java +++ b/core/java/android/tv/ITvInputSessionWrapper.java @@ -17,13 +17,16 @@ package android.tv; import android.content.Context; +import android.graphics.Rect; import android.net.Uri; +import android.os.IBinder; import android.os.Message; import android.tv.TvInputService.TvInputSessionImpl; import android.util.Log; import android.view.Surface; import com.android.internal.os.HandlerCaller; +import com.android.internal.os.SomeArgs; /** * Implements the internal ITvInputSession interface to convert incoming calls on to it back to @@ -38,6 +41,9 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand private static final int DO_SET_SURFACE = 2; private static final int DO_SET_VOLUME = 3; private static final int DO_TUNE = 4; + private static final int DO_CREATE_OVERLAY_VIEW = 5; + private static final int DO_RELAYOUT_OVERLAY_VIEW = 6; + private static final int DO_REMOVE_OVERLAY_VIEW = 7; private TvInputSessionImpl mTvInputSession; private final HandlerCaller mCaller; @@ -71,6 +77,20 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand mTvInputSession.tune((Uri) msg.obj); return; } + case DO_CREATE_OVERLAY_VIEW: { + SomeArgs args = (SomeArgs) msg.obj; + mTvInputSession.createOverlayView((IBinder) args.arg1, (Rect) args.arg2); + args.recycle(); + return; + } + case DO_RELAYOUT_OVERLAY_VIEW: { + mTvInputSession.relayoutOverlayView((Rect) msg.obj); + return; + } + case DO_REMOVE_OVERLAY_VIEW: { + mTvInputSession.removeOverlayView(true); + return; + } default: { Log.w(TAG, "Unhandled message code: " + msg.what); return; @@ -97,4 +117,20 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand public void tune(Uri channelUri) { mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_TUNE, channelUri)); } + + @Override + public void createOverlayView(IBinder windowToken, Rect frame) { + mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_OVERLAY_VIEW, windowToken, + frame)); + } + + @Override + public void relayoutOverlayView(Rect frame) { + mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_RELAYOUT_OVERLAY_VIEW, frame)); + } + + @Override + public void removeOverlayView() { + mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_OVERLAY_VIEW)); + } } diff --git a/core/java/android/tv/TvInputManager.java b/core/java/android/tv/TvInputManager.java index 4cf2b35..05f0b9c 100644 --- a/core/java/android/tv/TvInputManager.java +++ b/core/java/android/tv/TvInputManager.java @@ -17,6 +17,7 @@ package android.tv; import android.content.ComponentName; +import android.graphics.Rect; import android.net.Uri; import android.os.Handler; import android.os.IBinder; @@ -24,6 +25,7 @@ import android.os.RemoteException; import android.util.Log; import android.util.SparseArray; import android.view.Surface; +import android.view.View; import java.util.ArrayList; import java.util.HashMap; @@ -320,8 +322,8 @@ public final class TvInputManager { /** The Session provides the per-session functionality of TV inputs. */ public static final class Session { private final ITvInputManager mService; - private final IBinder mToken; private final int mUserId; + private IBinder mToken; /** @hide */ private Session(ComponentName name, IBinder token, ITvInputManager service, int userId) { @@ -332,10 +334,16 @@ public final class TvInputManager { /** * Releases this session. + * + * @throws IllegalStateException if the session has been already released. */ public void release() { + if (mToken == null) { + throw new IllegalStateException("the session has been already released"); + } try { mService.releaseSession(mToken, mUserId); + mToken = null; } catch (RemoteException e) { throw new RuntimeException(e); } @@ -345,8 +353,12 @@ public final class TvInputManager { * Sets the {@link android.view.Surface} for this session. * * @param surface A {@link android.view.Surface} used to render video. + * @throws IllegalStateException if the session has been already released. */ - public void setSurface(Surface surface) { + void setSurface(Surface surface) { + if (mToken == null) { + throw new IllegalStateException("the session has been already released"); + } // surface can be null. try { mService.setSurface(mToken, surface, mUserId); @@ -360,8 +372,12 @@ public final class TvInputManager { * * @param volume A volume value between 0.0f to 1.0f. * @throws IllegalArgumentException if the volume value is out of range. + * @throws IllegalStateException if the session has been already released. */ public void setVolume(float volume) { + if (mToken == null) { + throw new IllegalStateException("the session has been already released"); + } try { if (volume < 0.0f || volume > 1.0f) { throw new IllegalArgumentException("volume should be between 0.0f and 1.0f"); @@ -377,16 +393,90 @@ public final class TvInputManager { * * @param channelUri The URI of a channel. * @throws IllegalArgumentException if the argument is {@code null}. + * @throws IllegalStateException if the session has been already released. */ public void tune(Uri channelUri) { if (channelUri == null) { throw new IllegalArgumentException("channelUri cannot be null"); } + if (mToken == null) { + throw new IllegalStateException("the session has been already released"); + } try { mService.tune(mToken, channelUri, mUserId); } catch (RemoteException e) { throw new RuntimeException(e); } } + + /** + * Creates an overlay view. Once the overlay view is created, {@link #relayoutOverlayView} + * should be called whenever the layout of its containing view is changed. + * {@link #removeOverlayView()} should be called to remove the overlay view. + * Since a session can have only one overlay view, this method should be called only once + * or it can be called again after calling {@link #removeOverlayView()}. + * + * @param view A view playing TV. + * @param frame A position of the overlay view. + * @throws IllegalArgumentException if any of the arguments is {@code null}. + * @throws IllegalStateException if {@code view} is not attached to a window or + * if the session has been already released. + */ + void createOverlayView(View view, Rect frame) { + if (view == null) { + throw new IllegalArgumentException("view cannot be null"); + } + if (frame == null) { + throw new IllegalArgumentException("frame cannot be null"); + } + if (view.getWindowToken() == null) { + throw new IllegalStateException("view must be attached to a window"); + } + if (mToken == null) { + throw new IllegalStateException("the session has been already released"); + } + try { + mService.createOverlayView(mToken, view.getWindowToken(), frame, mUserId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Relayouts the current overlay view. + * + * @param frame A new position of the overlay view. + * @throws IllegalArgumentException if the arguments is {@code null}. + * @throws IllegalStateException if the session has been already released. + */ + void relayoutOverlayView(Rect frame) { + if (frame == null) { + throw new IllegalArgumentException("frame cannot be null"); + } + if (mToken == null) { + throw new IllegalStateException("the session has been already released"); + } + try { + mService.relayoutOverlayView(mToken, frame, mUserId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Removes the current overlay view. + * + * @throws IllegalStateException if the session has been already released. + */ + void removeOverlayView() { + if (mToken == null) { + throw new IllegalStateException("the session has been already released"); + } + try { + mService.removeOverlayView(mToken, mUserId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } } } diff --git a/core/java/android/tv/TvInputService.java b/core/java/android/tv/TvInputService.java index d7f6c32..80eb407 100644 --- a/core/java/android/tv/TvInputService.java +++ b/core/java/android/tv/TvInputService.java @@ -18,7 +18,10 @@ package android.tv; import android.app.Service; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; +import android.graphics.PixelFormat; +import android.graphics.Rect; import android.net.Uri; import android.os.Handler; import android.os.IBinder; @@ -26,7 +29,10 @@ import android.os.Message; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.Log; +import android.view.Gravity; import android.view.Surface; +import android.view.View; +import android.view.WindowManager; import com.android.internal.annotations.VisibleForTesting; @@ -125,7 +131,37 @@ public abstract class TvInputService extends Service { /** * Base class for derived classes to implement to provide {@link TvInputManager.Session}. */ - public abstract static class TvInputSessionImpl { + public abstract class TvInputSessionImpl { + private final WindowManager mWindowManager; + private WindowManager.LayoutParams mWindowParams; + private View mOverlayView; + private boolean mOverlayViewEnabled; + private IBinder mWindowToken; + private Rect mOverlayFrame; + + public TvInputSessionImpl() { + mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); + } + + public void setOverlayViewEnabled(final boolean enable) { + mHandler.post(new Runnable() { + @Override + public void run() { + if (enable == mOverlayViewEnabled) { + return; + } + mOverlayViewEnabled = enable; + if (enable) { + if (mWindowToken != null) { + createOverlayView(mWindowToken, mOverlayFrame); + } + } else { + removeOverlayView(false); + } + } + }); + } + /** * Called when the session is released. */ @@ -157,11 +193,22 @@ public abstract class TvInputService extends Service { public abstract boolean onTune(Uri channelUri); /** + * Called when an application requests to create an overlay view. Each session + * implementation can override this method and return its own view. + * + * @return a view attached to the overlay window + */ + public View onCreateOverlayView() { + return null; + } + + /** * This method is called when the application would like to stop using the current input * session. */ void release() { onRelease(); + removeOverlayView(true); } /** @@ -186,6 +233,87 @@ public abstract class TvInputService extends Service { onTune(channelUri); // TODO: Handle failure. } + + /** + * Creates an overlay view. This calls {@link onCreateOverlayView} to get + * a view to attach to the overlay window. + * + * @param windowToken A window token of an application. + * @param frame A position of the overlay view. + */ + void createOverlayView(IBinder windowToken, Rect frame) { + if (mOverlayView != null) { + mWindowManager.removeView(mOverlayView); + mOverlayView = null; + } + if (DEBUG) { + Log.d(TAG, "create overlay view(" + frame + ")"); + } + mWindowToken = windowToken; + mOverlayFrame = frame; + if (!mOverlayViewEnabled) { + return; + } + mOverlayView = onCreateOverlayView(); + if (mOverlayView == null) { + return; + } + // TvView's window type is TYPE_APPLICATION_MEDIA and we want to create + // an overlay window above the media window but below the application window. + int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY; + // We make the overlay view non-focusable and non-touchable so that + // the application that owns the window token can decide whether to consume or + // dispatch the input events. + int flag = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + mWindowParams = new WindowManager.LayoutParams( + frame.right - frame.left, frame.bottom - frame.top, + frame.left, frame.top, type, flag, PixelFormat.TRANSPARENT); + mWindowParams.privateFlags |= + WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; + mWindowParams.gravity = Gravity.START | Gravity.TOP; + mWindowParams.token = windowToken; + mWindowManager.addView(mOverlayView, mWindowParams); + } + + /** + * Relayouts the current overlay view. + * + * @param frame A new position of the overlay view. + */ + void relayoutOverlayView(Rect frame) { + if (DEBUG) { + Log.d(TAG, "relayout overlay view(" + frame + ")"); + } + mOverlayFrame = frame; + if (!mOverlayViewEnabled || mOverlayView == null) { + return; + } + mWindowParams.x = frame.left; + mWindowParams.y = frame.top; + mWindowParams.width = frame.right - frame.left; + mWindowParams.height = frame.bottom - frame.top; + mWindowManager.updateViewLayout(mOverlayView, mWindowParams); + } + + /** + * Removes the current overlay view. + */ + void removeOverlayView(boolean clearWindowToken) { + if (DEBUG) { + Log.d(TAG, "remove overlay view(" + mOverlayView + ")"); + } + if (clearWindowToken) { + mWindowToken = null; + mOverlayFrame = null; + } + if (mOverlayView != null) { + mWindowManager.removeView(mOverlayView); + mOverlayView = null; + mWindowParams = null; + } + } } private final class ServiceHandler extends Handler { diff --git a/core/java/android/tv/TvView.java b/core/java/android/tv/TvView.java new file mode 100644 index 0000000..325950d --- /dev/null +++ b/core/java/android/tv/TvView.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.tv; + +import android.content.ComponentName; +import android.content.Context; +import android.graphics.Rect; +import android.os.Handler; +import android.tv.TvInputManager; +import android.tv.TvInputManager.Session; +import android.tv.TvInputManager.SessionCreateCallback; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.ViewTreeObserver; + +/** + * View playing TV + */ +public class TvView extends SurfaceView { + private static final String TAG = "TvView"; + + private final Handler mHandler = new Handler(); + private TvInputManager.Session mSession; + private Surface mSurface; + private boolean mOverlayViewCreated; + private Rect mOverlayViewFrame; + private boolean mGlobalListenersAdded; + private TvInputManager mTvInputManager; + private SessionCreateCallback mSessionCreateCallback; + + private SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() { + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + Log.d(TAG, "surfaceChanged(holder=" + holder + ", format=" + format + ", width=" + width + + ", height=" + height + ")"); + if (holder.getSurface() == mSurface) { + return; + } + mSurface = holder.getSurface(); + setSessionSurface(mSurface); + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + mSurface = holder.getSurface(); + setSessionSurface(mSurface); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + mSurface = null; + setSessionSurface(null); + } + }; + + public TvView(Context context) { + this(context, null, 0); + } + + public TvView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public TvView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + getHolder().addCallback(mSurfaceHolderCallback); + mTvInputManager = (TvInputManager) getContext().getSystemService(Context.TV_INPUT_SERVICE); + } + + /** + * Binds a TV input to this view. {@link SessionCreateCallback#onSessionCreated} will be + * called to send the result of this binding with {@link TvInputManager.Session}. + * If a TV input is already bound, the input will be unbound from this view and its session + * will be released. + * + * @param name TV input name will be bound to this view. + * @param callback called when TV input is bound. The callback sends + * {@link TvInputManager.Session} + * @throws IllegalArgumentException if any of the arguments is {@code null}. + */ + public void bindTvInput(ComponentName name, SessionCreateCallback callback) { + if (name == null) { + throw new IllegalArgumentException("name cannot be null"); + } + if (callback == null) { + throw new IllegalArgumentException("callback cannot be null"); + } + if (mSession != null) { + release(); + } + // When bindTvInput is called multiple times before the callback is called, + // only the callback of the last bindTvInput call will be actually called back. + // The previous callbacks will be ignored. For the logic, mSessionCreateCallback + // is newly assigned for every bindTvInput call and compared with + // MySessionCreateCallback.this. + mSessionCreateCallback = new MySessionCreateCallback(callback); + mTvInputManager.createSession(name, mSessionCreateCallback, mHandler); + } + + /** + * Unbinds a TV input currently bound. Its corresponding {@link TvInputManager.Session} + * is released. + */ + public void unbindTvInput() { + if (mSession != null) { + release(); + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + createSessionOverlayView(); + } + + @Override + protected void onDetachedFromWindow() { + removeSessionOverlayView(); + super.onDetachedFromWindow(); + } + + /** @hide */ + @Override + protected void updateWindow(boolean force, boolean redrawNeeded) { + super.updateWindow(force, redrawNeeded); + relayoutSessionOverlayView(); + } + + private void release() { + setSessionSurface(null); + removeSessionOverlayView(); + mSession.release(); + mSession = null; + } + + private void setSessionSurface(Surface surface) { + if (mSession == null) { + return; + } + mSession.setSurface(surface); + } + + private void createSessionOverlayView() { + if (mSession == null || !isAttachedToWindow() + || mOverlayViewCreated) { + return; + } + mOverlayViewFrame = getViewFrameOnScreen(); + mSession.createOverlayView(this, mOverlayViewFrame); + mOverlayViewCreated = true; + } + + private void removeSessionOverlayView() { + if (mSession == null || !mOverlayViewCreated) { + return; + } + mSession.removeOverlayView(); + mOverlayViewCreated = false; + mOverlayViewFrame = null; + } + + private void relayoutSessionOverlayView() { + if (mSession == null || !isAttachedToWindow() + || !mOverlayViewCreated) { + return; + } + Rect viewFrame = getViewFrameOnScreen(); + if (viewFrame.equals(mOverlayViewFrame)) { + return; + } + mSession.relayoutOverlayView(viewFrame); + mOverlayViewFrame = viewFrame; + } + + private Rect getViewFrameOnScreen() { + int[] location = new int[2]; + getLocationOnScreen(location); + return new Rect(location[0], location[1], + location[0] + getWidth(), location[1] + getHeight()); + } + + private class MySessionCreateCallback implements SessionCreateCallback { + final SessionCreateCallback mExternalCallback; + + MySessionCreateCallback(SessionCreateCallback externalCallback) { + mExternalCallback = externalCallback; + } + + @Override + public void onSessionCreated(Session session) { + if (this != mSessionCreateCallback) { + // This callback is obsolete. + session.release(); + return; + } + mSession = session; + if (session != null) { + // mSurface may not be ready yet as soon as starting an application. + // In the case, we don't send Session.setSurface(null) unnecessarily. + // setSessionSurface will be called in surfaceCreated. + if (mSurface != null) { + setSessionSurface(mSurface); + } + createSessionOverlayView(); + } + if (mExternalCallback != null) { + mExternalCallback.onSessionCreated(session); + } + } + } +} diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java index abd173a..2b81072 100644 --- a/core/java/android/util/Log.java +++ b/core/java/android/util/Log.java @@ -352,6 +352,7 @@ public final class Log { /** @hide */ public static final int LOG_ID_RADIO = 1; /** @hide */ public static final int LOG_ID_EVENTS = 2; /** @hide */ public static final int LOG_ID_SYSTEM = 3; + /** @hide */ public static final int LOG_ID_CRASH = 4; /** @hide */ public static native int println_native(int bufID, int priority, String tag, String msg); diff --git a/core/java/android/util/Patterns.java b/core/java/android/util/Patterns.java index 13cc88b..2cc91b9 100644 --- a/core/java/android/util/Patterns.java +++ b/core/java/android/util/Patterns.java @@ -130,7 +130,10 @@ public class Patterns { private static final String IRI = "[" + GOOD_IRI_CHAR + "]([" + GOOD_IRI_CHAR + "\\-]{0,61}[" + GOOD_IRI_CHAR + "]){0,1}"; - private static final String HOST_NAME = IRI + "(?:\\." + IRI + ")+"; + private static final String GOOD_GTLD_CHAR = + "a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF"; + private static final String GTLD = "[" + GOOD_GTLD_CHAR + "]{2,63}"; + private static final String HOST_NAME = "(" + IRI + "\\.)+" + GTLD; public static final Pattern DOMAIN_NAME = Pattern.compile("(" + HOST_NAME + "|" + IP_ADDRESS + ")"); diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index c274fc4..34b85d9 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -378,17 +378,6 @@ class GLES20Canvas extends HardwareCanvas { private static native void nDrawLayer(long renderer, long layer, float x, float y); - void interrupt() { - nInterrupt(mRenderer); - } - - void resume() { - nResume(mRenderer); - } - - private static native void nInterrupt(long renderer); - private static native void nResume(long renderer); - /////////////////////////////////////////////////////////////////////////// // Support /////////////////////////////////////////////////////////////////////////// @@ -1128,11 +1117,11 @@ class GLES20Canvas extends HardwareCanvas { } @Override - public void drawRoundRect(RectF rect, float rx, float ry, Paint paint) { + public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, + Paint paint) { int modifiers = setupModifiers(paint, MODIFIER_SHADER); try { - nDrawRoundRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom, - rx, ry, paint.mNativePaint); + nDrawRoundRect(mRenderer, left, top, right, bottom, rx, ry, paint.mNativePaint); } finally { if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); } diff --git a/core/java/android/view/GLRenderer.java b/core/java/android/view/GLRenderer.java index eba4f7f..d8d11f7 100644 --- a/core/java/android/view/GLRenderer.java +++ b/core/java/android/view/GLRenderer.java @@ -1177,7 +1177,7 @@ public class GLRenderer extends HardwareRenderer { callbacks.onHardwarePreDraw(canvas); if (displayList != null) { - status |= drawDisplayList(attachInfo, canvas, displayList, status); + status |= drawDisplayList(canvas, displayList, status); } else { // Shouldn't reach here view.draw(canvas); @@ -1308,8 +1308,8 @@ public class GLRenderer extends HardwareRenderer { return status; } - private int drawDisplayList(View.AttachInfo attachInfo, HardwareCanvas canvas, - RenderNode displayList, int status) { + private int drawDisplayList(HardwareCanvas canvas, RenderNode displayList, + int status) { long drawDisplayListStartTime = 0; if (mProfileEnabled) { diff --git a/core/java/android/view/MenuInflater.java b/core/java/android/view/MenuInflater.java index a7ee12b..71296fa 100644 --- a/core/java/android/view/MenuInflater.java +++ b/core/java/android/view/MenuInflater.java @@ -161,6 +161,7 @@ public class MenuInflater { } else if (tagName.equals(XML_MENU)) { // A menu start tag denotes a submenu for an item SubMenu subMenu = menuState.addSubMenuItem(); + registerMenu(subMenu, attrs); // Parse the submenu into returned SubMenu parseMenu(parser, attrs, subMenu); @@ -183,9 +184,9 @@ public class MenuInflater { if (!menuState.hasAddedItem()) { if (menuState.itemActionProvider != null && menuState.itemActionProvider.hasSubMenu()) { - menuState.addSubMenuItem(); + registerMenu(menuState.addSubMenuItem(), attrs); } else { - menuState.addItem(); + registerMenu(menuState.addItem(), attrs); } } } else if (tagName.equals(XML_MENU)) { @@ -200,7 +201,30 @@ public class MenuInflater { eventType = parser.next(); } } - + + /** + * The method is a hook for layoutlib to do its magic. + * Nothing is needed outside of LayoutLib. However, it should not be deleted because it + * appears to do nothing. + */ + private void registerMenu(@SuppressWarnings("unused") MenuItem item, + @SuppressWarnings("unused") AttributeSet set) { + } + + /** + * The method is a hook for layoutlib to do its magic. + * Nothing is needed outside of LayoutLib. However, it should not be deleted because it + * appears to do nothing. + */ + private void registerMenu(@SuppressWarnings("unused") SubMenu subMenu, + @SuppressWarnings("unused") AttributeSet set) { + } + + // Needed by layoutlib. + /*package*/ Context getContext() { + return mContext; + } + private static class InflatedOnMenuItemClickListener implements MenuItem.OnMenuItemClickListener { private static final Class<?>[] PARAM_TYPES = new Class[] { MenuItem.class }; @@ -446,9 +470,11 @@ public class MenuInflater { } } - public void addItem() { + public MenuItem addItem() { itemAdded = true; - setItem(menu.add(groupId, itemId, itemCategoryOrder, itemTitle)); + MenuItem item = menu.add(groupId, itemId, itemCategoryOrder, itemTitle); + setItem(item); + return item; } public SubMenu addSubMenuItem() { diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 23123dd..4a2cc1a 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -422,7 +422,8 @@ public class SurfaceView extends View { mWindowType = type; } - private void updateWindow(boolean force, boolean redrawNeeded) { + /** @hide */ + protected void updateWindow(boolean force, boolean redrawNeeded) { if (!mHaveFrame) { return; } diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 1429837..924c331 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -178,11 +178,14 @@ public class ThreadedRenderer extends HardwareRenderer { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList"); HardwareCanvas canvas = mRootNode.start(mWidth, mHeight); - callbacks.onHardwarePostDraw(canvas); - canvas.drawDisplayList(view.getDisplayList()); - callbacks.onHardwarePostDraw(canvas); - mRootNode.end(canvas); - Trace.traceEnd(Trace.TRACE_TAG_VIEW); + try { + callbacks.onHardwarePostDraw(canvas); + canvas.drawDisplayList(view.getDisplayList()); + callbacks.onHardwarePostDraw(canvas); + } finally { + mRootNode.end(canvas); + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } view.mRecreateDisplayList = false; } @@ -194,6 +197,8 @@ public class ThreadedRenderer extends HardwareRenderer { updateRootDisplayList(view, callbacks); + attachInfo.mIgnoreDirtyState = false; + if (dirty == null) { dirty = NULL_RECT; } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index f44cc87..84d1328 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -27,7 +27,6 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; -import android.graphics.Camera; import android.graphics.Canvas; import android.graphics.Insets; import android.graphics.Interpolator; @@ -720,6 +719,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private static boolean sIgnoreMeasureCache = false; /** + * Ignore the clipBounds of this view for the children. + */ + static boolean sIgnoreClipBoundsForChildren = false; + + /** * This view does not want keystrokes. Use with TAKES_FOCUS_MASK when * calling setFlags. */ @@ -2390,6 +2394,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static final int PFLAG3_FITTING_SYSTEM_WINDOWS = 0x100; + /** + * Flag indicating that nested scrolling is enabled for this view. + * The view will optionally cooperate with views up its parent chain to allow for + * integrated nested scrolling along the same axis. + */ + static final int PFLAG3_NESTED_SCROLLING_ENABLED = 0x200; + /* End of masks for mPrivateFlags3 */ static final int DRAG_MASK = PFLAG2_DRAG_CAN_ACCEPT | PFLAG2_DRAG_HOVERED; @@ -2842,6 +2853,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public static final int SCREEN_STATE_ON = 0x1; /** + * Indicates no axis of view scrolling. + */ + public static final int SCROLL_AXIS_NONE = 0; + + /** + * Indicates scrolling along the horizontal axis. + */ + public static final int SCROLL_AXIS_HORIZONTAL = 1 << 0; + + /** + * Indicates scrolling along the vertical axis. + */ + public static final int SCROLL_AXIS_VERTICAL = 1 << 1; + + /** * Controls the over-scroll mode for this view. * See {@link #overScrollBy(int, int, int, int, int, int, int, int, boolean)}, * {@link #OVER_SCROLL_ALWAYS}, {@link #OVER_SCROLL_IF_CONTENT_SCROLLS}, @@ -2963,7 +2989,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Current clip bounds. to which all drawing of this view are constrained. */ - private Rect mClipBounds = null; + Rect mClipBounds = null; private boolean mLastIsOpaque; @@ -3468,6 +3494,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, ViewOverlay mOverlay; /** + * The currently active parent view for receiving delegated nested scrolling events. + * This is set by {@link #startNestedScroll(int)} during a touch interaction and cleared + * by {@link #stopNestedScroll()} at the same point where we clear + * requestDisallowInterceptTouchEvent. + */ + private ViewParent mNestedScrollingParent; + + /** * Consistency verifier for debugging purposes. * @hide */ @@ -3477,6 +3511,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1); + private int[] mTempNestedScrollConsumed; + /** * Simple constructor to use when creating a view from code. * @@ -3511,6 +3547,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // of whether a layout was requested on that View. sIgnoreMeasureCache = targetSdkVersion < KITKAT; + // Older apps may need this to ignore the clip bounds + sIgnoreClipBoundsForChildren = targetSdkVersion < L; + sCompatibilityDone = true; } } @@ -3955,6 +3994,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, case R.styleable.View_sharedElementName: setSharedElementName(a.getString(attr)); break; + case R.styleable.View_nestedScrollingEnabled: + setNestedScrollingEnabled(a.getBoolean(attr, false)); + break; } } @@ -4729,13 +4771,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private void manageFocusHotspot(boolean focused, View v) { if (mBackground != null && mBackground.supportsHotspots()) { final Rect r = new Rect(); - if (v != null) { + if (!focused && v != null) { v.getBoundsOnScreen(r); final int[] location = new int[2]; getLocationOnScreen(location); r.offset(-location[0], -location[1]); } else { - r.set(mLeft, mTop, mRight, mBottom); + r.set(0, 0, mRight - mLeft, mBottom - mTop); } final float x = r.exactCenterX(); @@ -4850,16 +4892,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if ((mPrivateFlags & PFLAG_FOCUSED) != 0) { mPrivateFlags &= ~PFLAG_FOCUSED; - if (hasFocus()) { - manageFocusHotspot(false, focused); - } - if (propagate && mParent != null) { mParent.clearChildFocus(this); } onFocusChanged(false, 0, null); + manageFocusHotspot(false, focused); refreshDrawableState(); if (propagate && (!refocus || !rootViewRequestFocus())) { @@ -7971,27 +8010,46 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @return True if the event was handled by the view, false otherwise. */ public boolean dispatchTouchEvent(MotionEvent event) { + boolean result = false; + if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } + final int actionMasked = event.getActionMasked(); + if (actionMasked == MotionEvent.ACTION_DOWN) { + // Defensive cleanup for new gesture + stopNestedScroll(); + } + if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; - if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED + if (li != null && li.mOnTouchListener != null + && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { - return true; + result = true; } - if (onTouchEvent(event)) { - return true; + if (!result && onTouchEvent(event)) { + result = true; } } - if (mInputEventConsistencyVerifier != null) { + if (!result && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } - return false; + + // Clean up after nested scrolls if this is the end of a gesture; + // also cancel it if we tried an ACTION_DOWN but we didn't want the rest + // of the gesture. + if (actionMasked == MotionEvent.ACTION_UP || + actionMasked == MotionEvent.ACTION_CANCEL || + (actionMasked == MotionEvent.ACTION_DOWN && !result)) { + stopNestedScroll(); + } + + return result; } /** @@ -12730,6 +12788,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, removeLongPressCallback(); removePerformClickCallback(); removeSendViewScrolledAccessibilityEventCallback(); + stopNestedScroll(); destroyDrawingCache(); destroyLayer(false); @@ -14892,8 +14951,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final int width = bounds.width(); final int height = bounds.height(); final HardwareCanvas canvas = displayList.start(width, height); - drawable.draw(canvas); - displayList.end(canvas); + try { + drawable.draw(canvas); + } finally { + displayList.end(canvas); + } // Set up drawable properties that are view-independent. displayList.setLeftTopRightBottom(bounds.left, bounds.top, bounds.right, bounds.bottom); @@ -17893,6 +17955,245 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Enable or disable nested scrolling for this view. + * + * <p>If this property is set to true the view will be permitted to initiate nested + * scrolling operations with a compatible parent view in the current hierarchy. If this + * view does not implement nested scrolling this will have no effect.</p> + * + * @param enabled true to enable nested scrolling, false to disable + * + * @see #isNestedScrollingEnabled() + */ + public void setNestedScrollingEnabled(boolean enabled) { + if (enabled) { + mPrivateFlags3 |= PFLAG3_NESTED_SCROLLING_ENABLED; + } else { + mPrivateFlags3 &= ~PFLAG3_NESTED_SCROLLING_ENABLED; + } + } + + /** + * Returns true if nested scrolling is enabled for this view. + * + * <p>If nested scrolling is enabled and this View class implementation supports it, + * this view will act as a nested scrolling child view when applicable, forwarding data + * about the scroll operation in progress to a compatible and cooperating nested scrolling + * parent.</p> + * + * @return true if nested scrolling is enabled + * + * @see #setNestedScrollingEnabled(boolean) + */ + public boolean isNestedScrollingEnabled() { + return (mPrivateFlags3 & PFLAG3_NESTED_SCROLLING_ENABLED) == + PFLAG3_NESTED_SCROLLING_ENABLED; + } + + /** + * Begin a nestable scroll operation along the given axes. + * + * <p>A view starting a nested scroll promises to abide by the following contract:</p> + * + * <p>The view will call startNestedScroll upon initiating a scroll operation. In the case + * of a touch scroll this corresponds to the initial {@link MotionEvent#ACTION_DOWN}. + * In the case of touch scrolling the nested scroll will be terminated automatically in + * the same manner as {@link ViewParent#requestDisallowInterceptTouchEvent(boolean)}. + * In the event of programmatic scrolling the caller must explicitly call + * {@link #stopNestedScroll()} to indicate the end of the nested scroll.</p> + * + * <p>If <code>startNestedScroll</code> returns true, a cooperative parent was found. + * If it returns false the caller may ignore the rest of this contract until the next scroll. + * </p> + * + * <p>At each incremental step of the scroll the caller should invoke + * {@link #dispatchNestedPreScroll(int, int, int[], int[]) dispatchNestedPreScroll} + * once it has calculated the requested scrolling delta. If it returns true the nested scrolling + * parent at least partially consumed the scroll and the caller should adjust the amount it + * scrolls by.</p> + * + * <p>After applying the remainder of the scroll delta the caller should invoke + * {@link #dispatchNestedScroll(int, int, int, int, int[]) dispatchNestedScroll}, passing + * both the delta consumed and the delta unconsumed. A nested scrolling parent may treat + * these values differently. See {@link ViewParent#onNestedScroll(View, int, int, int, int)}. + * </p> + * + * @param axes Flags consisting of a combination of {@link #SCROLL_AXIS_HORIZONTAL} and/or + * {@link #SCROLL_AXIS_VERTICAL}. + * @return true if a cooperative parent was found and nested scrolling has been enabled for + * the current gesture. + * + * @see #stopNestedScroll() + * @see #dispatchNestedPreScroll(int, int, int[], int[]) + * @see #dispatchNestedScroll(int, int, int, int, int[]) + */ + public boolean startNestedScroll(int axes) { + if (isNestedScrollingEnabled()) { + ViewParent p = getParent(); + View child = this; + while (p != null) { + try { + if (p.onStartNestedScroll(child, this, axes)) { + mNestedScrollingParent = p; + p.onNestedScrollAccepted(child, this, axes); + return true; + } + } catch (AbstractMethodError e) { + Log.e(VIEW_LOG_TAG, "ViewParent " + p + " does not implement interface " + + "method onStartNestedScroll", e); + // Allow the search upward to continue + } + if (p instanceof View) { + child = (View) p; + } + p = p.getParent(); + } + } + return false; + } + + /** + * Stop a nested scroll in progress. + * + * <p>Calling this method when a nested scroll is not currently in progress is harmless.</p> + * + * @see #startNestedScroll(int) + */ + public void stopNestedScroll() { + if (mNestedScrollingParent != null) { + mNestedScrollingParent.onStopNestedScroll(this); + mNestedScrollingParent = null; + } + } + + /** + * Returns true if this view has a nested scrolling parent. + * + * <p>The presence of a nested scrolling parent indicates that this view has initiated + * a nested scroll and it was accepted by an ancestor view further up the view hierarchy.</p> + * + * @return whether this view has a nested scrolling parent + */ + public boolean hasNestedScrollingParent() { + return mNestedScrollingParent != null; + } + + /** + * Dispatch one step of a nested scroll in progress. + * + * <p>Implementations of views that support nested scrolling should call this to report + * info about a scroll in progress to the current nested scrolling parent. If a nested scroll + * is not currently in progress or nested scrolling is not + * {@link #isNestedScrollingEnabled() enabled} for this view this method does nothing.</p> + * + * <p>Compatible View implementations should also call + * {@link #dispatchNestedPreScroll(int, int, int[], int[]) dispatchNestedPreScroll} before + * consuming a component of the scroll event themselves.</p> + * + * @param dxConsumed Horizontal distance in pixels consumed by this view during this scroll step + * @param dyConsumed Vertical distance in pixels consumed by this view during this scroll step + * @param dxUnconsumed Horizontal scroll distance in pixels not consumed by this view + * @param dyUnconsumed Horizontal scroll distance in pixels not consumed by this view + * @param offsetInWindow Optional. If not null, on return this will contain the offset + * in local view coordinates of this view from before this operation + * to after it completes. View implementations may use this to adjust + * expected input coordinate tracking. + * @return true if the event was dispatched, false if it could not be dispatched. + * @see #dispatchNestedPreScroll(int, int, int[], int[]) + */ + public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, + int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { + if (isNestedScrollingEnabled() && mNestedScrollingParent != null) { + int startX = 0; + int startY = 0; + if (offsetInWindow != null) { + getLocationInWindow(offsetInWindow); + startX = offsetInWindow[0]; + startY = offsetInWindow[1]; + } + + mNestedScrollingParent.onNestedScroll(this, dxConsumed, dyConsumed, + dxUnconsumed, dyUnconsumed); + + if (offsetInWindow != null) { + getLocationInWindow(offsetInWindow); + offsetInWindow[0] -= startX; + offsetInWindow[1] -= startY; + } + return true; + } + return false; + } + + /** + * Dispatch one step of a nested scroll in progress before this view consumes any portion of it. + * + * <p>Nested pre-scroll events are to nested scroll events what touch intercept is to touch. + * <code>dispatchNestedPreScroll</code> offers an opportunity for the parent view in a nested + * scrolling operation to consume some or all of the scroll operation before the child view + * consumes it.</p> + * + * @param dx Horizontal scroll distance in pixels + * @param dy Vertical scroll distance in pixels + * @param consumed Output. If not null, consumed[0] will contain the consumed component of dx + * and consumed[1] the consumed dy. + * @param offsetInWindow Optional. If not null, on return this will contain the offset + * in local view coordinates of this view from before this operation + * to after it completes. View implementations may use this to adjust + * expected input coordinate tracking. + * @return true if the parent consumed some or all of the scroll delta + * @see #dispatchNestedScroll(int, int, int, int, int[]) + */ + public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { + if (isNestedScrollingEnabled() && mNestedScrollingParent != null) { + int startX = 0; + int startY = 0; + if (offsetInWindow != null) { + getLocationInWindow(offsetInWindow); + startX = offsetInWindow[0]; + startY = offsetInWindow[1]; + } + + if (consumed == null) { + if (mTempNestedScrollConsumed == null) { + mTempNestedScrollConsumed = new int[2]; + } + consumed = mTempNestedScrollConsumed; + } + consumed[0] = 0; + consumed[1] = 0; + mNestedScrollingParent.onNestedPreScroll(this, dx, dy, consumed); + + if (offsetInWindow != null) { + getLocationInWindow(offsetInWindow); + offsetInWindow[0] -= startX; + offsetInWindow[1] -= startY; + } + return consumed[0] != 0 || consumed[1] != 0; + } + return false; + } + + /** + * Dispatch a fling to a nested scrolling parent. + * + * <p>If a nested scrolling child view would normally fling but it is at the edge of its + * own content it should use this method to delegate the fling to its nested scrolling parent. + * The view implementation can use a {@link VelocityTracker} to obtain the velocity values + * to pass.</p> + * + * @param velocityX Horizontal fling velocity in pixels per second + * @param velocityY Vertical fling velocity in pixels per second + * @return true if the nested scrolling parent consumed the fling + */ + public boolean dispatchNestedFling(float velocityX, float velocityY) { + if (isNestedScrollingEnabled() && mNestedScrollingParent != null) { + return mNestedScrollingParent.onNestedFling(this, velocityX, velocityY); + } + return false; + } + + /** * Gets a scale factor that determines the distance the view should scroll * vertically in response to {@link MotionEvent#ACTION_SCROLL}. * @return The vertical scroll scale factor. diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index d2c6302..ad76145 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -460,6 +460,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @ViewDebug.ExportedProperty(category = "layout") private int mChildCountWithTransientState = 0; + /** + * Currently registered axes for nested scrolling. Flag set consisting of + * {@link #SCROLL_AXIS_HORIZONTAL} {@link #SCROLL_AXIS_VERTICAL} or {@link #SCROLL_AXIS_NONE} + * for null. + */ + private int mNestedScrollAxes; + public ViewGroup(Context context) { this(context, null); } @@ -2011,6 +2018,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager clearTouchTargets(); resetCancelNextUpFlag(this); mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; + mNestedScrollAxes = SCROLL_AXIS_NONE; } /** @@ -2305,11 +2313,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * Changes whether or not this ViewGroup should be treated as a single entity during - * ActivityTransitions. + * Activity Transitions. * @param isTransitionGroup Whether or not the ViewGroup should be treated as a unit * in Activity transitions. If false, the ViewGroup won't transition, * only its children. If true, the entire ViewGroup will transition * together. + * @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.Window, + * android.app.ActivityOptions.ActivityTransitionListener) */ public void setTransitionGroup(boolean isTransitionGroup) { mGroupFlags |= FLAG_IS_TRANSITION_GROUP_SET; @@ -2960,14 +2970,24 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } - int saveCount = 0; + int clipSaveCount = 0; final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK; + boolean hasClipBounds = mClipBounds != null && !sIgnoreClipBoundsForChildren; + boolean clippingNeeded = clipToPadding || hasClipBounds; + + if (clippingNeeded) { + clipSaveCount = canvas.save(); + } + if (clipToPadding) { - saveCount = canvas.save(); canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop, mScrollX + mRight - mLeft - mPaddingRight, mScrollY + mBottom - mTop - mPaddingBottom); + } + if (hasClipBounds) { + canvas.clipRect(mClipBounds.left, mClipBounds.top, mClipBounds.right, + mClipBounds.bottom); } // We will draw our child's animation, let's reset the flag @@ -3008,8 +3028,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager onDebugDraw(canvas); } - if (clipToPadding) { - canvas.restoreToCount(saveCount); + if (clippingNeeded) { + canvas.restoreToCount(clipSaveCount); } // mGroupFlags might have been updated by drawChild() @@ -5844,6 +5864,74 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return true; } + /** + * @inheritDoc + */ + @Override + public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { + return false; + } + + /** + * @inheritDoc + */ + @Override + public void onNestedScrollAccepted(View child, View target, int axes) { + mNestedScrollAxes = axes; + } + + /** + * @inheritDoc + * + * <p>The default implementation of onStopNestedScroll calls + * {@link #stopNestedScroll()} to halt any recursive nested scrolling in progress.</p> + */ + @Override + public void onStopNestedScroll(View child) { + // Stop any recursive nested scrolling. + stopNestedScroll(); + } + + /** + * @inheritDoc + */ + @Override + public void onNestedScroll(View target, int dxConsumed, int dyConsumed, + int dxUnconsumed, int dyUnconsumed) { + // Do nothing + } + + /** + * @inheritDoc + */ + @Override + public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { + // Do nothing + } + + /** + * @inheritDoc + */ + @Override + public boolean onNestedFling(View target, float velocityX, float velocityY) { + return false; + } + + /** + * Return the current axes of nested scrolling for this ViewGroup. + * + * <p>A ViewGroup returning something other than {@link #SCROLL_AXIS_NONE} is currently + * acting as a nested scrolling parent for one or more descendant views in the hierarchy.</p> + * + * @return Flags indicating the current axes of nested scrolling + * @see #SCROLL_AXIS_HORIZONTAL + * @see #SCROLL_AXIS_VERTICAL + * @see #SCROLL_AXIS_NONE + */ + public int getNestedScrollAxes() { + return mNestedScrollAxes; + } + /** @hide */ protected void onSetLayoutParams(View child, LayoutParams layoutParams) { } diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java index 0137693..3cd6449 100644 --- a/core/java/android/view/ViewParent.java +++ b/core/java/android/view/ViewParent.java @@ -407,4 +407,119 @@ public interface ViewParent { * {@link View#TEXT_ALIGNMENT_VIEW_END} */ public int getTextAlignment(); + + /** + * React to a descendant view initiating a nestable scroll operation, claiming the + * nested scroll operation if appropriate. + * + * <p>This method will be called in response to a descendant view invoking + * {@link View#startNestedScroll(int)}. Each parent up the view hierarchy will be + * given an opportunity to respond and claim the nested scrolling operation by returning + * <code>true</code>.</p> + * + * <p>This method may be overridden by ViewParent implementations to indicate when the view + * is willing to support a nested scrolling operation that is about to begin. If it returns + * true, this ViewParent will become the target view's nested scrolling parent for the duration + * of the scroll operation in progress. When the nested scroll is finished this ViewParent + * will receive a call to {@link #onStopNestedScroll(View)}. + * </p> + * + * @param child Direct child of this ViewParent containing target + * @param target View that initiated the nested scroll + * @param nestedScrollAxes Flags consisting of {@link View#SCROLL_AXIS_HORIZONTAL}, + * {@link View#SCROLL_AXIS_VERTICAL} or both + * @return true if this ViewParent accepts the nested scroll operation + */ + public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes); + + /** + * React to the successful claiming of a nested scroll operation. + * + * <p>This method will be called after + * {@link #onStartNestedScroll(View, View, int) onStartNestedScroll} returns true. It offers + * an opportunity for the view and its superclasses to perform initial configuration + * for the nested scroll. Implementations of this method should always call their superclass's + * implementation of this method if one is present.</p> + * + * @param child Direct child of this ViewParent containing target + * @param target View that initiated the nested scroll + * @param nestedScrollAxes Flags consisting of {@link View#SCROLL_AXIS_HORIZONTAL}, + * {@link View#SCROLL_AXIS_VERTICAL} or both + * @see #onStartNestedScroll(View, View, int) + * @see #onStopNestedScroll(View) + */ + public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes); + + /** + * React to a nested scroll operation ending. + * + * <p>Perform cleanup after a nested scrolling operation. + * This method will be called when a nested scroll stops, for example when a nested touch + * scroll ends with a {@link MotionEvent#ACTION_UP} or {@link MotionEvent#ACTION_CANCEL} event. + * Implementations of this method should always call their superclass's implementation of this + * method if one is present.</p> + * + * @param target View that initiated the nested scroll + */ + public void onStopNestedScroll(View target); + + /** + * React to a nested scroll in progress. + * + * <p>This method will be called when the ViewParent's current nested scrolling child view + * dispatches a nested scroll event. To receive calls to this method the ViewParent must have + * previously returned <code>true</code> for a call to + * {@link #onStartNestedScroll(View, View, int)}.</p> + * + * <p>Both the consumed and unconsumed portions of the scroll distance are reported to the + * ViewParent. An implementation may choose to use the consumed portion to match or chase scroll + * position of multiple child elements, for example. The unconsumed portion may be used to + * allow continuous dragging of multiple scrolling or draggable elements, such as scrolling + * a list within a vertical drawer where the drawer begins dragging once the edge of inner + * scrolling content is reached.</p> + * + * @param target The descendent view controlling the nested scroll + * @param dxConsumed Horizontal scroll distance in pixels already consumed by target + * @param dyConsumed Vertical scroll distance in pixels already consumed by target + * @param dxUnconsumed Horizontal scroll distance in pixels not consumed by target + * @param dyUnconsumed Vertical scroll distance in pixels not consumed by target + */ + public void onNestedScroll(View target, int dxConsumed, int dyConsumed, + int dxUnconsumed, int dyUnconsumed); + + /** + * React to a nested scroll in progress before the target view consumes a portion of the scroll. + * + * <p>When working with nested scrolling often the parent view may want an opportunity + * to consume the scroll before the nested scrolling child does. An example of this is a + * drawer that contains a scrollable list. The user will want to be able to scroll the list + * fully into view before the list itself begins scrolling.</p> + * + * <p><code>onNestedPreScroll</code> is called when a nested scrolling child invokes + * {@link View#dispatchNestedPreScroll(int, int, int[], int[])}. The implementation should + * report how any pixels of the scroll reported by dx, dy were consumed in the + * <code>consumed</code> array. Index 0 corresponds to dx and index 1 corresponds to dy. + * This parameter will never be null. Initial values for consumed[0] and consumed[1] + * will always be 0.</p> + * + * @param target View that initiated the nested scroll + * @param dx Horizontal scroll distance in pixels + * @param dy Vertical scroll distance in pixels + * @param consumed Output. The horizontal and vertical scroll distance consumed by this parent + */ + public void onNestedPreScroll(View target, int dx, int dy, int[] consumed); + + /** + * Request a fling from a nested scroll. + * + * <p>If a nested scrolling child view would normally fling but it is at the edge of + * its own content, it can delegate the fling to its nested scrolling parent instead. + * This method allows the parent to optionally consume the fling.</p> + * + * @param target View that initiated the nested scroll + * @param velocityX Horizontal velocity in pixels per second. + * @param velocityY Vertical velocity in pixels per second + * @return true if this parent consumed the fling + */ + public boolean onNestedFling(View target, float velocityX, float velocityY); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index ef22def..246905d 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1171,6 +1171,19 @@ public final class ViewRootImpl implements ViewParent, m.preTranslate(-attachInfo.mWindowLeft, -attachInfo.mWindowTop); } + void dispatchApplyInsets(View host) { + mFitSystemWindowsInsets.set(mAttachInfo.mContentInsets); + boolean isRound = false; + if ((mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN) != 0 + && mDisplay.getDisplayId() == 0) { + // we're fullscreen and not hosted in an ActivityView + isRound = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_windowIsRound); + } + host.dispatchApplyWindowInsets(new WindowInsets( + mFitSystemWindowsInsets, isRound)); + } + private void performTraversals() { // cache mView since it is used so much below... final View host = mView; @@ -1257,8 +1270,7 @@ public final class ViewRootImpl implements ViewParent, } host.dispatchAttachedToWindow(attachInfo, 0); attachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true); - mFitSystemWindowsInsets.set(mAttachInfo.mContentInsets); - host.fitSystemWindows(mFitSystemWindowsInsets); + dispatchApplyInsets(host); //Log.i(TAG, "Screen on initialized: " + attachInfo.mKeepScreenOn); } else { @@ -1383,9 +1395,8 @@ public final class ViewRootImpl implements ViewParent, if (mFitSystemWindowsRequested) { mFitSystemWindowsRequested = false; - mFitSystemWindowsInsets.set(mAttachInfo.mContentInsets); mLastOverscanRequested = mAttachInfo.mOverscanRequested; - host.fitSystemWindows(mFitSystemWindowsInsets); + dispatchApplyInsets(host); if (mLayoutRequested) { // Short-circuit catching a new layout request here, so // we don't need to go through two layout passes when things @@ -1503,43 +1514,46 @@ public final class ViewRootImpl implements ViewParent, mResizeBuffer.prepare(mWidth, mHeight, false); RenderNode layerRenderNode = mResizeBuffer.startRecording(); HardwareCanvas layerCanvas = layerRenderNode.start(mWidth, mHeight); - final int restoreCount = layerCanvas.save(); - - int yoff; - final boolean scrolling = mScroller != null - && mScroller.computeScrollOffset(); - if (scrolling) { - yoff = mScroller.getCurrY(); - mScroller.abortAnimation(); - } else { - yoff = mScrollY; - } + try { + final int restoreCount = layerCanvas.save(); + + int yoff; + final boolean scrolling = mScroller != null + && mScroller.computeScrollOffset(); + if (scrolling) { + yoff = mScroller.getCurrY(); + mScroller.abortAnimation(); + } else { + yoff = mScrollY; + } - layerCanvas.translate(0, -yoff); - if (mTranslator != null) { - mTranslator.translateCanvas(layerCanvas); - } + layerCanvas.translate(0, -yoff); + if (mTranslator != null) { + mTranslator.translateCanvas(layerCanvas); + } - RenderNode renderNode = mView.mRenderNode; - if (renderNode != null && renderNode.isValid()) { - layerCanvas.drawDisplayList(renderNode, null, - RenderNode.FLAG_CLIP_CHILDREN); - } else { - mView.draw(layerCanvas); - } + RenderNode renderNode = mView.mRenderNode; + if (renderNode != null && renderNode.isValid()) { + layerCanvas.drawDisplayList(renderNode, null, + RenderNode.FLAG_CLIP_CHILDREN); + } else { + mView.draw(layerCanvas); + } - drawAccessibilityFocusedDrawableIfNeeded(layerCanvas); + drawAccessibilityFocusedDrawableIfNeeded(layerCanvas); - mResizeBufferStartTime = SystemClock.uptimeMillis(); - mResizeBufferDuration = mView.getResources().getInteger( - com.android.internal.R.integer.config_mediumAnimTime); + mResizeBufferStartTime = SystemClock.uptimeMillis(); + mResizeBufferDuration = mView.getResources().getInteger( + com.android.internal.R.integer.config_mediumAnimTime); - layerCanvas.restoreToCount(restoreCount); - layerRenderNode.end(layerCanvas); - layerRenderNode.setCaching(true); - layerRenderNode.setLeftTopRightBottom(0, 0, mWidth, mHeight); - mTempRect.set(0, 0, mWidth, mHeight); - mResizeBuffer.endRecording(mTempRect); + layerCanvas.restoreToCount(restoreCount); + layerRenderNode.end(layerCanvas); + layerRenderNode.setCaching(true); + layerRenderNode.setLeftTopRightBottom(0, 0, mWidth, mHeight); + mTempRect.set(0, 0, mWidth, mHeight); + } finally { + mResizeBuffer.endRecording(mTempRect); + } mAttachInfo.mHardwareRenderer.flushLayerUpdates(); } mAttachInfo.mContentInsets.set(mPendingContentInsets); @@ -1559,8 +1573,7 @@ public final class ViewRootImpl implements ViewParent, mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility; mLastOverscanRequested = mAttachInfo.mOverscanRequested; mFitSystemWindowsRequested = false; - mFitSystemWindowsInsets.set(mAttachInfo.mContentInsets); - host.fitSystemWindows(mFitSystemWindowsInsets); + dispatchApplyInsets(host); } if (visibleInsetsChanged) { mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets); @@ -2939,7 +2952,7 @@ public final class ViewRootImpl implements ViewParent, } } } - + /** * Return true if child is an ancestor of parent, (or equal to the parent). */ @@ -3712,7 +3725,8 @@ public final class ViewRootImpl implements ViewParent, if (result == InputMethodManager.DISPATCH_HANDLED) { return FINISH_HANDLED; } else if (result == InputMethodManager.DISPATCH_NOT_HANDLED) { - return FINISH_NOT_HANDLED; + // The IME could not handle it, so skip along to the next InputStage + return FORWARD; } else { return DEFER; // callback will be invoked later } @@ -5810,6 +5824,13 @@ public final class ViewRootImpl implements ViewParent, } } + public void dispatchUnhandledInputEvent(InputEvent event) { + if (event instanceof KeyEvent) { + dispatchUnhandledKey((KeyEvent) event); + return; + } + } + public void dispatchAppVisibility(boolean visible) { Message msg = mHandler.obtainMessage(MSG_DISPATCH_APP_VISIBILITY); msg.arg1 = visible ? 1 : 0; @@ -6069,6 +6090,33 @@ public final class ViewRootImpl implements ViewParent, // Do nothing. } + @Override + public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { + return false; + } + + @Override + public void onStopNestedScroll(View target) { + } + + @Override + public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) { + } + + @Override + public void onNestedScroll(View target, int dxConsumed, int dyConsumed, + int dxUnconsumed, int dyUnconsumed) { + } + + @Override + public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { + } + + @Override + public boolean onNestedFling(View target, float velocityX, float velocityY) { + return false; + } + void changeCanvasOpacity(boolean opaque) { // TODO(romainguy): recreate Canvas (software or hardware) to reflect the opacity change. Log.d(TAG, "changeCanvasOpacity: opaque=" + opaque); diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 7bd1f56..9c44bd1 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -32,8 +32,6 @@ import android.transition.Transition; import android.transition.TransitionManager; import android.view.accessibility.AccessibilityEvent; -import java.util.Map; - /** * Abstract base class for a top-level window look and behavior policy. An * instance of this class should be used as the top-level view added to the @@ -1385,86 +1383,132 @@ public abstract class Window { } /** - * Set options that can affect the transition behavior within this window. - * @param options Options to set or null for none - * @hide + * Sets the Transition that will be used to move Views into the initial scene. The entering + * Views will be those that are regular Views or ViewGroups that have + * {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend + * {@link android.transition.Visibility} as entering is governed by changing visibility from + * {@link View#INVISIBLE} to {@link View#VISIBLE}. If <code>transition</code> is null, + * entering Views will remain unaffected. + * @param transition The Transition to use to move Views into the initial Scene. */ - public void setTransitionOptions(Bundle options, SceneTransitionListener listener) { - } + public void setEnterTransition(Transition transition) {} /** - * A callback for Window transitions to be told when the shared element is ready to be shown - * and start the transition to its target location. - * @hide + * Sets the Transition that will be used to move Views out of the scene when starting a + * new Activity. The exiting Views will be those that are regular Views or ViewGroups that + * have {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend + * {@link android.transition.Visibility} as exiting is governed by changing visibility + * from {@link View#VISIBLE} to {@link View#INVISIBLE}. If transition is null, the views will + * remain unaffected. Requires {@link #FEATURE_CONTENT_TRANSITIONS}. + * @param transition The Transition to use to move Views out of the scene when calling a + * new Activity. */ - public interface SceneTransitionListener { - void nullPendingTransition(); - void convertFromTranslucent(); - void convertToTranslucent(); - void sharedElementStart(Transition transition); - void sharedElementEnd(); - } + public void setExitTransition(Transition transition) {} /** - * Controls how the Activity's start Scene is faded in and when the enter scene - * is triggered to start. - * <p>When allow is true, the enter Scene will begin as soon as possible - * and the background will fade in when all shared elements are ready to begin - * transitioning. If allow is false, the Activity enter Scene and - * background fade will be triggered when the calling Activity's exit transition - * completes.</p> - * @param allow Set to true to have the Activity enter scene transition in - * as early as possible or set to false to wait for the calling - * Activity to exit first. The default value is true. + * Returns the transition used to move Views into the initial scene. The entering + * Views will be those that are regular Views or ViewGroups that have + * {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend + * {@link android.transition.Visibility} as entering is governed by changing visibility from + * {@link View#INVISIBLE} to {@link View#VISIBLE}. If <code>transition</code> is null, + * entering Views will remain unaffected. Requires {@link #FEATURE_CONTENT_TRANSITIONS}. + * + * @return the Transition to use to move Views into the initial Scene. */ - public void setAllowOverlappingEnterTransition(boolean allow) { - } + public Transition getEnterTransition() { return null; } /** - * Controls how the Activity's Scene fades out and when the calling Activity's - * enter scene is triggered when finishing to return to a calling Activity. - * <p>When allow is true, the Scene will fade out quickly - * and inform the calling Activity to transition in when the fade completes. - * When allow is false, the calling Activity will transition in after - * the Activity's Scene has exited. - * </p> - * @param allow Set to true to have the Activity fade out as soon as possible - * and transition in the calling Activity. The default value is - * true. + * Returns the Transition that will be used to move Views out of the scene when starting a + * new Activity. The exiting Views will be those that are regular Views or ViewGroups that + * have {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend + * {@link android.transition.Visibility} as exiting is governed by changing visibility + * from {@link View#VISIBLE} to {@link View#INVISIBLE}. If transition is null, the views will + * remain unaffected. Requires {@link #FEATURE_CONTENT_TRANSITIONS}. + * @return the Transition to use to move Views out of the scene when calling a + * new Activity. */ - public void setAllowOverlappingExitTransition(boolean allow) { - } + public Transition getExitTransition() { return null; } /** - * Start the exit transition. - * @hide + * Sets the Transition that will be used for shared elements transferred into the content + * Scene. Typical Transitions will affect size and location, such as + * {@link android.transition.MoveImage} and {@link android.transition.ChangeBounds}. A null + * value will cause transferred shared elements to blink to the final position. + * Requires {@link #FEATURE_CONTENT_TRANSITIONS}. + * @param transition The Transition to use for shared elements transferred into the content + * Scene. */ - public Bundle startExitTransitionToCallee(Bundle options) { - return null; - } + public void setSharedElementEnterTransition(Transition transition) {} /** - * Starts the transition back to the calling Activity. - * onTransitionEnd will be called on the current thread if there is no exit transition. - * @hide + * Returns the Transition that will be used for shared elements transferred into the content + * Scene. Requires {@link #FEATURE_CONTENT_TRANSITIONS}. + * @return Transition to use for sharend elements transferred into the content Scene. */ - public void startExitTransitionToCaller(Runnable onTransitionEnd) { - onTransitionEnd.run(); - } + public Transition getSharedElementEnterTransition() { return null; } - /** @hide */ - public void restoreViewVisibilityAfterTransitionToCallee() { - } + /** + * Sets the Transition that will be used for shared elements after starting a new Activity + * before the shared elements are transferred to the called Activity. If the shared elements + * must animate during the exit transition, this Transition should be used. Upon completion, + * the shared elements may be transferred to the started Activity. + * Requires {@link #FEATURE_CONTENT_TRANSITIONS}. + * @param transition The Transition to use for shared elements in the launching Window + * prior to transferring to the launched Activity's Window. + */ + public void setSharedElementExitTransition(Transition transition) {} /** - * On entering Activity Scene transitions, shared element names may be mapped from a - * source Activity's specified name to a unique shared element name in the View hierarchy. - * Under most circumstances, mapping is not necessary - a single View will have the - * shared element name given by the calling Activity. However, if there are several similar - * Views (e.g. in a ListView), the correct shared element must be mapped. - * @param sharedElementNames A mapping from the calling Activity's assigned shared element - * name to a unique shared element name in the View hierarchy. + * Returns the Transition to use for shared elements in the launching Window prior + * to transferring to the launched Activity's Window. + * Requires {@link #FEATURE_CONTENT_TRANSITIONS}. + * + * @return the Transition to use for shared elements in the launching Window prior + * to transferring to the launched Activity's Window. */ - public void mapTransitionTargets(Map<String, String> sharedElementNames) { - } + public Transition getSharedElementExitTransition() { return null; } + + /** + * Controls how the transition set in + * {@link #setEnterTransition(android.transition.Transition)} overlaps with the exit + * transition of the calling Activity. When true, the transition will start as soon as possible. + * When false, the transition will wait until the remote exiting transition completes before + * starting. + * @param allow true to start the enter transition when possible or false to + * wait until the exiting transition completes. + */ + public void setAllowEnterTransitionOverlap(boolean allow) {} + + /** + * Returns how the transition set in + * {@link #setEnterTransition(android.transition.Transition)} overlaps with the exit + * transition of the calling Activity. When true, the transition will start as soon as possible. + * When false, the transition will wait until the remote exiting transition completes before + * starting. + * @return true when the enter transition should start as soon as possible or false to + * when it should wait until the exiting transition completes. + */ + public boolean getAllowEnterTransitionOverlap() { return true; } + + /** + * Controls how the transition set in + * {@link #setExitTransition(android.transition.Transition)} overlaps with the exit + * transition of the called Activity when reentering after if finishes. When true, + * the transition will start as soon as possible. When false, the transition will wait + * until the called Activity's exiting transition completes before starting. + * @param allow true to start the transition when possible or false to wait until the + * called Activity's exiting transition completes. + */ + public void setAllowExitTransitionOverlap(boolean allow) {} + + /** + * Returns how the transition set in + * {@link #setExitTransition(android.transition.Transition)} overlaps with the exit + * transition of the called Activity when reentering after if finishes. When true, + * the transition will start as soon as possible. When false, the transition will wait + * until the called Activity's exiting transition completes before starting. + * @return true when the transition should start when possible or false when it should wait + * until the called Activity's exiting transition completes. + */ + public boolean getAllowExitTransitionOverlap() { return true; } } diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index cdfcb43..2160efe 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -33,6 +33,7 @@ public class WindowInsets { private Rect mSystemWindowInsets; private Rect mWindowDecorInsets; private Rect mTempRect; + private boolean mIsRound; private static final Rect EMPTY_RECT = new Rect(0, 0, 0, 0); @@ -46,8 +47,19 @@ public class WindowInsets { /** @hide */ public WindowInsets(Rect systemWindowInsets, Rect windowDecorInsets) { + this(systemWindowInsets, windowDecorInsets, false); + } + + /** @hide */ + public WindowInsets(Rect systemWindowInsets, boolean isRound) { + this(systemWindowInsets, EMPTY_RECT, isRound); + } + + /** @hide */ + public WindowInsets(Rect systemWindowInsets, Rect windowDecorInsets, boolean isRound) { mSystemWindowInsets = systemWindowInsets; mWindowDecorInsets = windowDecorInsets; + mIsRound = isRound; } /** @@ -58,12 +70,12 @@ public class WindowInsets { public WindowInsets(WindowInsets src) { mSystemWindowInsets = src.mSystemWindowInsets; mWindowDecorInsets = src.mWindowDecorInsets; + mIsRound = src.mIsRound; } /** @hide */ public WindowInsets(Rect systemWindowInsets) { - mSystemWindowInsets = systemWindowInsets; - mWindowDecorInsets = EMPTY_RECT; + this(systemWindowInsets, EMPTY_RECT); } /** @@ -220,6 +232,20 @@ public class WindowInsets { return hasSystemWindowInsets() || hasWindowDecorInsets(); } + /** + * Returns true if the associated window has a round shape. + * + * <p>A round window's left, top, right and bottom edges reach all the way to the + * associated edges of the window but the corners may not be visible. Views responding + * to round insets should take care to not lay out critical elements within the corners + * where they may not be accessible.</p> + * + * @return True if the window is round + */ + public boolean isRound() { + return mIsRound; + } + public WindowInsets cloneWithSystemWindowInsetsConsumed() { final WindowInsets result = new WindowInsets(this); result.mSystemWindowInsets = new Rect(0, 0, 0, 0); @@ -273,6 +299,6 @@ public class WindowInsets { @Override public String toString() { return "WindowInsets{systemWindowInsets=" + mSystemWindowInsets + " windowDecorInsets=" + - mWindowDecorInsets + "}"; + mWindowDecorInsets + (isRound() ? "round}" : "}"); } } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 62fbbc4..d2e7324 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -1136,9 +1136,18 @@ public class WebView extends AbsoluteLayout } /** + * @deprecated Use {@link #createPrintDocumentAdapter(String)} which requires user + * to provide a print document name. + */ + @Deprecated + public PrintDocumentAdapter createPrintDocumentAdapter() { + checkThread(); + if (DebugFlags.TRACE_API) Log.d(LOGTAG, "createPrintDocumentAdapter"); + return mProvider.createPrintDocumentAdapter("default"); + } + + /** * Creates a PrintDocumentAdapter that provides the content of this Webview for printing. - * Only supported for API levels - * {@link android.os.Build.VERSION_CODES#KITKAT} and above. * * The adapter works by converting the Webview contents to a PDF stream. The Webview cannot * be drawn during the conversion process - any such draws are undefined. It is recommended @@ -1146,11 +1155,14 @@ public class WebView extends AbsoluteLayout * temporarily hide a visible WebView by using a custom PrintDocumentAdapter instance * wrapped around the object returned and observing the onStart and onFinish methods. See * {@link android.print.PrintDocumentAdapter} for more information. + * + * @param documentName The user-facing name of the printed document. See + * {@link android.print.PrintDocumentInfo} */ - public PrintDocumentAdapter createPrintDocumentAdapter() { + public PrintDocumentAdapter createPrintDocumentAdapter(String documentName) { checkThread(); if (DebugFlags.TRACE_API) Log.d(LOGTAG, "createPrintDocumentAdapter"); - return mProvider.createPrintDocumentAdapter(); + return mProvider.createPrintDocumentAdapter(documentName); } /** diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java index 9488cdd..5081ff5 100644 --- a/core/java/android/webkit/WebViewProvider.java +++ b/core/java/android/webkit/WebViewProvider.java @@ -148,7 +148,7 @@ public interface WebViewProvider { public Picture capturePicture(); - public PrintDocumentAdapter createPrintDocumentAdapter(); + public PrintDocumentAdapter createPrintDocumentAdapter(String documentName); public float getScale(); diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java index 438a9da..225cd6d 100644 --- a/core/java/android/widget/AbsSeekBar.java +++ b/core/java/android/widget/AbsSeekBar.java @@ -29,6 +29,8 @@ import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; +import com.android.internal.R; + public abstract class AbsSeekBar extends ProgressBar { private Drawable mThumb; private int mThumbOffset; @@ -289,28 +291,39 @@ public abstract class AbsSeekBar extends ProgressBar { */ private void setThumbPos(int w, Drawable thumb, float scale, int gap) { int available = w - mPaddingLeft - mPaddingRight; - int thumbWidth = thumb.getIntrinsicWidth(); - int thumbHeight = thumb.getIntrinsicHeight(); + final int thumbWidth = thumb.getIntrinsicWidth(); + final int thumbHeight = thumb.getIntrinsicHeight(); available -= thumbWidth; // The extra space for the thumb to move on the track available += mThumbOffset * 2; - int thumbPos = (int) (scale * available + 0.5f); + final int thumbPos = (int) (scale * available + 0.5f); - int topBound, bottomBound; + final int top, bottom; if (gap == Integer.MIN_VALUE) { - Rect oldBounds = thumb.getBounds(); - topBound = oldBounds.top; - bottomBound = oldBounds.bottom; + final Rect oldBounds = thumb.getBounds(); + top = oldBounds.top; + bottom = oldBounds.bottom; } else { - topBound = gap; - bottomBound = gap + thumbHeight; + top = gap; + bottom = gap + thumbHeight; } - - // Canvas will be translated, so 0,0 is where we start drawing + final int left = (isLayoutRtl() && mMirrorForRtl) ? available - thumbPos : thumbPos; - thumb.setBounds(left, topBound, left + thumbWidth, bottomBound); + final int right = left + thumbWidth; + + final Drawable background = getBackground(); + if (background != null && background.supportsHotspots()) { + final Rect bounds = mThumb.getBounds(); + final int offsetX = mPaddingLeft - mThumbOffset; + final int offsetY = mPaddingTop; + background.setHotspotBounds(left + offsetX, bounds.top + offsetY, + right + offsetX, bounds.bottom + offsetY); + } + + // Canvas will be translated, so 0,0 is where we start drawing + thumb.setBounds(left, top, right, bottom); } /** @@ -328,6 +341,7 @@ public abstract class AbsSeekBar extends ProgressBar { @Override protected synchronized void onDraw(Canvas canvas) { super.onDraw(canvas); + if (mThumb != null) { canvas.save(); // Translate the padding. For the x, we need to allow the thumb to @@ -424,10 +438,24 @@ public abstract class AbsSeekBar extends ProgressBar { return true; } + private void setHotspot(int id, float x, float y) { + final Drawable bg = getBackground(); + if (bg != null && bg.supportsHotspots()) { + bg.setHotspot(id, x, y); + } + } + + private void clearHotspot(int id) { + final Drawable bg = getBackground(); + if (bg != null && bg.supportsHotspots()) { + bg.removeHotspot(id); + } + } + private void trackTouchEvent(MotionEvent event) { final int width = getWidth(); final int available = width - mPaddingLeft - mPaddingRight; - int x = (int)event.getX(); + final int x = (int) event.getX(); float scale; float progress = 0; if (isLayoutRtl() && mMirrorForRtl) { @@ -451,7 +479,8 @@ public abstract class AbsSeekBar extends ProgressBar { } final int max = getMax(); progress += scale * max; - + + setHotspot(R.attr.state_pressed, x, (int) event.getY()); setProgress((int) progress, true); } @@ -477,6 +506,7 @@ public abstract class AbsSeekBar extends ProgressBar { * canceled. */ void onStopTrackingTouch() { + clearHotspot(R.attr.state_pressed); mIsDragging = false; } diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java index 4298545..9e17cca 100644 --- a/core/java/android/widget/CompoundButton.java +++ b/core/java/android/widget/CompoundButton.java @@ -261,15 +261,13 @@ public abstract class CompoundButton extends Button implements Checkable { @Override protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - final Drawable buttonDrawable = mButtonDrawable; if (buttonDrawable != null) { final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK; final int drawableHeight = buttonDrawable.getIntrinsicHeight(); final int drawableWidth = buttonDrawable.getIntrinsicWidth(); - int top = 0; + final int top; switch (verticalGravity) { case Gravity.BOTTOM: top = getHeight() - drawableHeight; @@ -277,12 +275,24 @@ public abstract class CompoundButton extends Button implements Checkable { case Gravity.CENTER_VERTICAL: top = (getHeight() - drawableHeight) / 2; break; + default: + top = 0; } - int bottom = top + drawableHeight; - int left = isLayoutRtl() ? getWidth() - drawableWidth : 0; - int right = isLayoutRtl() ? getWidth() : drawableWidth; + final int bottom = top + drawableHeight; + final int left = isLayoutRtl() ? getWidth() - drawableWidth : 0; + final int right = isLayoutRtl() ? getWidth() : drawableWidth; buttonDrawable.setBounds(left, top, right, bottom); + + final Drawable background = getBackground(); + if (background != null && background.supportsHotspots()) { + background.setHotspotBounds(left, top, right, bottom); + } + } + + super.onDraw(canvas); + + if (buttonDrawable != null) { buttonDrawable.draw(canvas); } } diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 082d728..34a6a40 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -138,6 +138,12 @@ public class ScrollView extends FrameLayout { private int mActivePointerId = INVALID_POINTER; /** + * Used during scrolling to retrieve the new offset within the window. + */ + private final int[] mScrollOffset = new int[2]; + private final int[] mScrollConsumed = new int[2]; + + /** * The StrictMode "critical time span" objects to catch animation * stutters. Non-null when a time-sensitive animation is * in-flight. Must call finish() on them when done animating. @@ -505,7 +511,7 @@ public class ScrollView extends FrameLayout { final int y = (int) ev.getY(pointerIndex); final int yDiff = Math.abs(y - mLastMotionY); - if (yDiff > mTouchSlop) { + if (yDiff > mTouchSlop && (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) { mIsBeingDragged = true; mLastMotionY = y; initVelocityTrackerIfNotExists(); @@ -606,6 +612,7 @@ public class ScrollView extends FrameLayout { // Remember where the motion event started mLastMotionY = (int) ev.getY(); mActivePointerId = ev.getPointerId(0); + startNestedScroll(SCROLL_AXIS_VERTICAL); break; } case MotionEvent.ACTION_MOVE: @@ -617,6 +624,9 @@ public class ScrollView extends FrameLayout { final int y = (int) ev.getY(activePointerIndex); int deltaY = mLastMotionY - y; + if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) { + deltaY -= mScrollConsumed[1] + mScrollOffset[1]; + } if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) { final ViewParent parent = getParent(); if (parent != null) { @@ -633,22 +643,25 @@ public class ScrollView extends FrameLayout { // Scroll to follow the motion event mLastMotionY = y; - final int oldX = mScrollX; final int oldY = mScrollY; final int range = getScrollRange(); final int overscrollMode = getOverScrollMode(); - final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS || + boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS || (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0); // Calling overScrollBy will call onOverScrolled, which // calls onScrollChanged if applicable. - if (overScrollBy(0, deltaY, 0, mScrollY, - 0, range, 0, mOverscrollDistance, true)) { + if (overScrollBy(0, deltaY, 0, mScrollY, 0, range, 0, mOverscrollDistance, true) + && !hasNestedScrollingParent()) { // Break our velocity if we hit a scroll barrier. mVelocityTracker.clear(); } - if (canOverscroll) { + final int scrolledDeltaY = mScrollY - oldY; + final int unconsumedY = deltaY - scrolledDeltaY; + if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset)) { + mLastMotionY -= mScrollOffset[1]; + } else if (canOverscroll) { final int pulledToY = oldY + deltaY; if (pulledToY < 0) { mEdgeGlowTop.onPull((float) deltaY / getHeight()); @@ -674,15 +687,11 @@ public class ScrollView extends FrameLayout { velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); - if (getChildCount() > 0) { - if ((Math.abs(initialVelocity) > mMinimumVelocity)) { - fling(-initialVelocity); - } else { - if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, - getScrollRange())) { - postInvalidateOnAnimation(); - } - } + if ((Math.abs(initialVelocity) > mMinimumVelocity)) { + flingWithNestedDispatch(-initialVelocity); + } else if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, + getScrollRange())) { + postInvalidateOnAnimation(); } mActivePointerId = INVALID_POINTER; @@ -1553,6 +1562,15 @@ public class ScrollView extends FrameLayout { } } + private void flingWithNestedDispatch(int velocityY) { + if (mScrollY == 0 && velocityY < 0 || + mScrollY == getScrollRange() && velocityY > 0) { + dispatchNestedFling(0, velocityY); + } else { + fling(velocityY); + } + } + private void endDrag() { mIsBeingDragged = false; @@ -1603,6 +1621,34 @@ public class ScrollView extends FrameLayout { } @Override + public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { + return (nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0; + } + + /** + * @inheritDoc + */ + @Override + public void onStopNestedScroll(View target) { + super.onStopNestedScroll(target); + } + + @Override + public void onNestedScroll(View target, int dxConsumed, int dyConsumed, + int dxUnconsumed, int dyUnconsumed) { + scrollBy(0, dyUnconsumed); + } + + /** + * @inheritDoc + */ + @Override + public boolean onNestedFling(View target, float velocityX, float velocityY) { + flingWithNestedDispatch((int) velocityY); + return true; + } + + @Override public void draw(Canvas canvas) { super.draw(canvas); if (mEdgeGlowTop != null) { diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java index 3d23e4d..08af4de 100644 --- a/core/java/android/widget/Switch.java +++ b/core/java/android/widget/Switch.java @@ -773,8 +773,6 @@ public class Switch extends CompoundButton { @Override protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - final Rect tempRect = mTempRect; final Drawable trackDrawable = mTrackDrawable; final Drawable thumbDrawable = mThumbDrawable; @@ -785,16 +783,12 @@ public class Switch extends CompoundButton { final int switchRight = mSwitchRight; final int switchBottom = mSwitchBottom; trackDrawable.setBounds(switchLeft, switchTop, switchRight, switchBottom); - trackDrawable.draw(canvas); - - final int saveCount = canvas.save(); - trackDrawable.getPadding(tempRect); + final int switchInnerLeft = switchLeft + tempRect.left; final int switchInnerTop = switchTop + tempRect.top; final int switchInnerRight = switchRight - tempRect.right; final int switchInnerBottom = switchBottom - tempRect.bottom; - canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom); // Relies on mTempRect, MUST be called first! final int thumbPos = getThumbOffset(); @@ -803,6 +797,18 @@ public class Switch extends CompoundButton { int thumbLeft = switchInnerLeft - tempRect.left + thumbPos; int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + tempRect.right; thumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom); + + final Drawable background = getBackground(); + if (background != null && background.supportsHotspots()) { + background.setHotspotBounds(thumbLeft, switchTop, thumbRight, switchBottom); + } + + super.onDraw(canvas); + + trackDrawable.draw(canvas); + + final int saveCount = canvas.save(); + canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom); thumbDrawable.draw(canvas); final int drawableState[] = getDrawableState(); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index a7278da..b91111d 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -652,6 +652,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener boolean allCaps = false; int shadowcolor = 0; float dx = 0, dy = 0, r = 0; + boolean elegant = false; final Resources.Theme theme = context.getTheme(); @@ -728,6 +729,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case com.android.internal.R.styleable.TextAppearance_shadowRadius: r = appearance.getFloat(attr, 0); break; + + case com.android.internal.R.styleable.TextAppearance_elegantTextHeight: + elegant = appearance.getBoolean(attr, false); + break; } } @@ -1065,6 +1070,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case com.android.internal.R.styleable.TextView_textAllCaps: allCaps = a.getBoolean(attr, false); break; + + case com.android.internal.R.styleable.TextView_elegantTextHeight: + elegant = a.getBoolean(attr, false); + break; } } a.recycle(); @@ -1245,6 +1254,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener setHighlightColor(textColorHighlight); } setRawTextSize(textSize); + setElegantTextHeight(elegant); if (allCaps) { setTransformationMethod(new AllCapsTransformationMethod(getContext())); @@ -2468,6 +2478,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener setTransformationMethod(new AllCapsTransformationMethod(getContext())); } + if (appearance.hasValue(com.android.internal.R.styleable.TextAppearance_elegantTextHeight)) { + setElegantTextHeight(appearance.getBoolean( + com.android.internal.R.styleable.TextAppearance_elegantTextHeight, false)); + } + appearance.recycle(); } @@ -2615,6 +2630,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * Set the TextView's elegant height metrics flag. This setting selects font + * variants that have not been compacted to fit Latin-based vertical + * metrics, and also increases top and bottom bounds to provide more space. + * + * @param elegant set the paint's elegant metrics flag. + */ + public void setElegantTextHeight(boolean elegant) { + mTextPaint.setElegantTextHeight(elegant); + } + + /** * Sets the text color for all the states (normal, selected, * focused) to be this color. * diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 32f700d..fc89b31 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -19,6 +19,7 @@ package com.android.internal.app; import com.android.internal.os.BatteryStatsImpl; import android.os.WorkSource; +import android.telephony.DataConnectionRealTimeInfo; import android.telephony.SignalStrength; interface IBatteryStats { @@ -55,7 +56,7 @@ interface IBatteryStats { void noteScreenOff(); void noteInputEvent(); void noteUserActivity(int uid, int event); - void noteDataConnectionActive(int type, boolean active, long timestampNs); + void noteMobileRadioPowerState(int powerState, long timestampNs); void notePhoneOn(); void notePhoneOff(); void notePhoneSignalStrength(in SignalStrength signalStrength); diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java index 34156e5..afb6f7c 100644 --- a/core/java/com/android/internal/app/ToolbarActionBar.java +++ b/core/java/com/android/internal/app/ToolbarActionBar.java @@ -444,9 +444,4 @@ public class ToolbarActionBar extends ActionBar { mMenuVisibilityListeners.get(i).onMenuVisibilityChanged(isVisible); } } - - @Override - public void captureSharedElements(Map<String, View> sharedElements) { - mToolbar.findSharedElements(sharedElements); - } } diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java index fb93ddd..131f828 100644 --- a/core/java/com/android/internal/app/WindowDecorActionBar.java +++ b/core/java/com/android/internal/app/WindowDecorActionBar.java @@ -362,10 +362,6 @@ public class WindowDecorActionBar extends ActionBar { setSubtitle(mContext.getString(resId)); } - public void captureSharedElements(Map<String, View> sharedElements) { - mContainerView.findSharedElements(sharedElements); - } - public void setSelectedNavigationItem(int position) { switch (mActionView.getNavigationMode()) { case NAVIGATION_MODE_TABS: diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java index 1dd1f5e..7ff949e 100644 --- a/core/java/com/android/internal/os/BatteryStatsHelper.java +++ b/core/java/com/android/internal/os/BatteryStatsHelper.java @@ -22,6 +22,8 @@ import static android.os.BatteryStats.NETWORK_WIFI_RX_DATA; import static android.os.BatteryStats.NETWORK_WIFI_TX_DATA; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.hardware.Sensor; import android.hardware.SensorManager; import android.net.ConnectivityManager; @@ -60,11 +62,14 @@ public class BatteryStatsHelper { private static final String TAG = BatteryStatsHelper.class.getSimpleName(); private static BatteryStats sStatsXfer; + private static Intent sBatteryBroadcastXfer; final private Context mContext; + final private boolean mCollectBatteryBroadcast; private IBatteryStats mBatteryInfo; private BatteryStats mStats; + private Intent mBatteryBroadcast; private PowerProfile mPowerProfile; private final List<BatterySipper> mUsageList = new ArrayList<BatterySipper>(); @@ -85,6 +90,8 @@ public class BatteryStatsHelper { long mBatteryUptime; long mTypeBatteryRealtime; long mTypeBatteryUptime; + long mBatteryTimeRemaining; + long mChargeTimeRemaining; private long mStatsPeriod = 0; private double mMaxPower = 1; @@ -102,7 +109,12 @@ public class BatteryStatsHelper { private long mAppWifiRunning; public BatteryStatsHelper(Context context) { + this(context, true); + } + + public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast) { mContext = context; + mCollectBatteryBroadcast = collectBatteryBroadcast; } /** Clears the current stats and forces recreating for future use. */ @@ -117,6 +129,13 @@ public class BatteryStatsHelper { return mStats; } + public Intent getBatteryBroadcast() { + if (mBatteryBroadcast == null && mCollectBatteryBroadcast) { + load(); + } + return mBatteryBroadcast; + } + public PowerProfile getPowerProfile() { return mPowerProfile; } @@ -129,6 +148,7 @@ public class BatteryStatsHelper { public void create(Bundle icicle) { if (icicle != null) { mStats = sStatsXfer; + mBatteryBroadcast = sBatteryBroadcastXfer; } mBatteryInfo = IBatteryStats.Stub.asInterface( ServiceManager.getService(BatteryStats.SERVICE_NAME)); @@ -137,6 +157,7 @@ public class BatteryStatsHelper { public void storeState() { sStatsXfer = mStats; + sBatteryBroadcastXfer = mBatteryBroadcast; } public static String makemAh(double power) { @@ -190,6 +211,8 @@ public class BatteryStatsHelper { mBatteryRealtime = mStats.getBatteryRealtime(rawRealtimeUs); mTypeBatteryUptime = mStats.computeBatteryUptime(rawUptimeUs, mStatsType); mTypeBatteryRealtime = mStats.computeBatteryRealtime(rawRealtimeUs, mStatsType); + mBatteryTimeRemaining = mStats.computeBatteryTimeRemaining(rawRealtimeUs); + mChargeTimeRemaining = mStats.computeChargeTimeRemaining(rawRealtimeUs); if (DEBUG) { Log.d(TAG, "Raw time: realtime=" + (rawRealtimeUs/1000) + " uptime=" @@ -787,6 +810,10 @@ public class BatteryStatsHelper { return mMaxDrainedPower; } + public long getBatteryTimeRemaining() { return mBatteryTimeRemaining; } + + public long getChargeTimeRemaining() { return mChargeTimeRemaining; } + private void load() { if (mBatteryInfo == null) { return; @@ -803,5 +830,9 @@ public class BatteryStatsHelper { } catch (RemoteException e) { Log.e(TAG, "RemoteException:", e); } + if (mCollectBatteryBroadcast) { + mBatteryBroadcast = mContext.registerReceiver(null, + new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + } } } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 4c11fa9..343c507 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -38,6 +38,7 @@ import android.os.Process; import android.os.SystemClock; import android.os.SystemProperties; import android.os.WorkSource; +import android.telephony.DataConnectionRealTimeInfo; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; @@ -87,7 +88,7 @@ public final class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - private static final int VERSION = 101 + (USE_OLD_HISTORY ? 1000 : 0); + private static final int VERSION = 103 + (USE_OLD_HISTORY ? 1000 : 0); // Maximum number of items we will record in the history. private static final int MAX_HISTORY_ITEMS = 2000; @@ -284,7 +285,7 @@ public final class BatteryStatsImpl extends BatteryStats { int mBluetoothState = -1; final StopwatchTimer[] mBluetoothStateTimer = new StopwatchTimer[NUM_BLUETOOTH_STATES]; - boolean mMobileRadioActive; + int mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; StopwatchTimer mMobileRadioActiveTimer; StopwatchTimer mMobileRadioActivePerAppTimer; LongSamplingCounter mMobileRadioActiveAdjustedTime; @@ -306,7 +307,9 @@ public final class BatteryStatsImpl extends BatteryStats { */ int mDischargeStartLevel; int mDischargeUnplugLevel; + int mDischargePlugLevel; int mDischargeCurrentLevel; + int mCurrentBatteryLevel; int mLowDischargeAmountSinceCharge; int mHighDischargeAmountSinceCharge; int mDischargeScreenOnUnplugLevel; @@ -2736,40 +2739,41 @@ public final class BatteryStatsImpl extends BatteryStats { } } - public void noteDataConnectionActive(int type, boolean active, long timestampNs) { - if (ConnectivityManager.isNetworkTypeMobile(type)) { - final long elapsedRealtime = SystemClock.elapsedRealtime(); - final long uptime = SystemClock.uptimeMillis(); - if (mMobileRadioActive != active) { - long realElapsedRealtimeMs; - if (active) { + public void noteMobileRadioPowerState(int powerState, long timestampNs) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); + if (mMobileRadioPowerState != powerState) { + long realElapsedRealtimeMs; + final boolean active = + powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM + || powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH; + if (active) { + realElapsedRealtimeMs = elapsedRealtime; + mHistoryCur.states |= HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG; + } else { + realElapsedRealtimeMs = timestampNs / (1000*1000); + long lastUpdateTimeMs = mMobileRadioActiveTimer.getLastUpdateTimeMs(); + if (realElapsedRealtimeMs < lastUpdateTimeMs) { + Slog.wtf(TAG, "Data connection inactive timestamp " + realElapsedRealtimeMs + + " is before start time " + lastUpdateTimeMs); realElapsedRealtimeMs = elapsedRealtime; - mHistoryCur.states |= HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG; - } else { - realElapsedRealtimeMs = timestampNs / (1000*1000); - long lastUpdateTimeMs = mMobileRadioActiveTimer.getLastUpdateTimeMs(); - if (realElapsedRealtimeMs < lastUpdateTimeMs) { - Slog.wtf(TAG, "Data connection inactive timestamp " + realElapsedRealtimeMs - + " is before start time " + lastUpdateTimeMs); - realElapsedRealtimeMs = elapsedRealtime; - } else if (realElapsedRealtimeMs < elapsedRealtime) { - mMobileRadioActiveAdjustedTime.addCountLocked(elapsedRealtime - - realElapsedRealtimeMs); - } - mHistoryCur.states &= ~HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG; - } - if (DEBUG_HISTORY) Slog.v(TAG, "Mobile network active " + active + " to: " - + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(elapsedRealtime, uptime); - mMobileRadioActive = active; - if (active) { - mMobileRadioActiveTimer.startRunningLocked(elapsedRealtime); - mMobileRadioActivePerAppTimer.startRunningLocked(elapsedRealtime); - } else { - mMobileRadioActiveTimer.stopRunningLocked(realElapsedRealtimeMs); - updateNetworkActivityLocked(NET_UPDATE_MOBILE, realElapsedRealtimeMs); - mMobileRadioActivePerAppTimer.stopRunningLocked(realElapsedRealtimeMs); + } else if (realElapsedRealtimeMs < elapsedRealtime) { + mMobileRadioActiveAdjustedTime.addCountLocked(elapsedRealtime + - realElapsedRealtimeMs); } + mHistoryCur.states &= ~HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG; + } + if (DEBUG_HISTORY) Slog.v(TAG, "Mobile network active " + active + " to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtime, uptime); + mMobileRadioPowerState = powerState; + if (active) { + mMobileRadioActiveTimer.startRunningLocked(elapsedRealtime); + mMobileRadioActivePerAppTimer.startRunningLocked(elapsedRealtime); + } else { + mMobileRadioActiveTimer.stopRunningLocked(realElapsedRealtimeMs); + updateNetworkActivityLocked(NET_UPDATE_MOBILE, realElapsedRealtimeMs); + mMobileRadioActivePerAppTimer.stopRunningLocked(realElapsedRealtimeMs); } } } @@ -5520,7 +5524,9 @@ public final class BatteryStatsImpl extends BatteryStats { initTimes(uptime, realtime); mDischargeStartLevel = 0; mDischargeUnplugLevel = 0; + mDischargePlugLevel = -1; mDischargeCurrentLevel = 0; + mCurrentBatteryLevel = 0; initDischarge(); clearHistoryLocked(); } @@ -5580,13 +5586,13 @@ public final class BatteryStatsImpl extends BatteryStats { if (end) { Slog.w(TAG, "New history ends before old history!"); } else if (!out.same(mHistoryReadTmp)) { - long now = getHistoryBaseTime() + SystemClock.elapsedRealtime(); PrintWriter pw = new FastPrintWriter(new LogWriter(android.util.Log.WARN, TAG)); pw.println("Histories differ!"); pw.println("Old history:"); - (new HistoryPrinter()).printNextItem(pw, out, now, false, true); + (new HistoryPrinter()).printNextItem(pw, out, 0, false, true); pw.println("New history:"); - (new HistoryPrinter()).printNextItem(pw, mHistoryReadTmp, now, false, true); + (new HistoryPrinter()).printNextItem(pw, mHistoryReadTmp, 0, false, + true); pw.flush(); } } @@ -5664,7 +5670,12 @@ public final class BatteryStatsImpl extends BatteryStats { return false; } + final long lastRealtime = out.time; + final long lastWalltime = out.currentTime; readHistoryDelta(mHistoryBuffer, out); + if (out.cmd != HistoryItem.CMD_CURRENT_TIME && lastWalltime != 0) { + out.currentTime = lastWalltime + (out.time - lastRealtime); + } return true; } @@ -5721,7 +5732,8 @@ public final class BatteryStatsImpl extends BatteryStats { mDischargeStartLevel = mHistoryCur.batteryLevel; pullPendingStateUpdatesLocked(); addHistoryRecordLocked(mSecRealtime, mSecUptime); - mDischargeCurrentLevel = mDischargeUnplugLevel = mHistoryCur.batteryLevel; + mDischargeCurrentLevel = mDischargeUnplugLevel = mDischargePlugLevel + = mCurrentBatteryLevel = mHistoryCur.batteryLevel; mOnBatteryTimeBase.reset(uptime, realtime); mOnBatteryScreenOffTimeBase.reset(uptime, realtime); if ((mHistoryCur.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) == 0) { @@ -5902,7 +5914,7 @@ public final class BatteryStatsImpl extends BatteryStats { if (DEBUG_HISTORY) Slog.v(TAG, "Battery plugged to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(mSecRealtime, mSecUptime); - mDischargeCurrentLevel = level; + mDischargeCurrentLevel = mDischargePlugLevel = level; if (level < mDischargeUnplugLevel) { mLowDischargeAmountSinceCharge += mDischargeUnplugLevel-level-1; mHighDischargeAmountSinceCharge += mDischargeUnplugLevel-level; @@ -5966,6 +5978,10 @@ public final class BatteryStatsImpl extends BatteryStats { startRecordingHistory(elapsedRealtime, uptime, true); } } + mCurrentBatteryLevel = level; + if (mDischargePlugLevel < 0) { + mDischargePlugLevel = level; + } if (onBattery != mOnBattery) { mHistoryCur.batteryLevel = (byte)level; mHistoryCur.batteryStatus = (byte)status; @@ -6219,6 +6235,42 @@ public final class BatteryStatsImpl extends BatteryStats { return mOnBatteryScreenOffTimeBase.computeRealtime(curTime, which); } + @Override + public long computeBatteryTimeRemaining(long curTime) { + if (!mOnBattery) { + return -1; + } + int discharge = (getLowDischargeAmountSinceCharge()+getHighDischargeAmountSinceCharge())/2; + if (discharge < 2) { + return -1; + } + long duration = computeBatteryRealtime(curTime, STATS_SINCE_CHARGED); + if (duration < 1000*1000) { + return -1; + } + long usPerLevel = duration/discharge; + return usPerLevel * mCurrentBatteryLevel; + } + + @Override + public long computeChargeTimeRemaining(long curTime) { + if (true || mOnBattery) { + // Not yet working. + return -1; + } + int curLevel = mCurrentBatteryLevel; + int plugLevel = mDischargePlugLevel; + if (plugLevel < 0 || curLevel < (plugLevel+1)) { + return -1; + } + long duration = computeBatteryRealtime(curTime, STATS_SINCE_UNPLUGGED); + if (duration < 1000*1000) { + return -1; + } + long usPerLevel = duration/(curLevel-plugLevel); + return usPerLevel * (100-curLevel); + } + long getBatteryUptimeLocked() { return mOnBatteryTimeBase.getUptime(SystemClock.uptimeMillis() * 1000); } @@ -6717,7 +6769,9 @@ public final class BatteryStatsImpl extends BatteryStats { mOnBatteryTimeBase.readSummaryFromParcel(in); mOnBatteryScreenOffTimeBase.readSummaryFromParcel(in); mDischargeUnplugLevel = in.readInt(); + mDischargePlugLevel = in.readInt(); mDischargeCurrentLevel = in.readInt(); + mCurrentBatteryLevel = in.readInt(); mLowDischargeAmountSinceCharge = in.readInt(); mHighDischargeAmountSinceCharge = in.readInt(); mDischargeAmountScreenOnSinceCharge = in.readInt(); @@ -6744,7 +6798,7 @@ public final class BatteryStatsImpl extends BatteryStats { mNetworkByteActivityCounters[i].readSummaryFromParcelLocked(in); mNetworkPacketActivityCounters[i].readSummaryFromParcelLocked(in); } - mMobileRadioActive = false; + mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; mMobileRadioActiveTimer.readSummaryFromParcelLocked(in); mMobileRadioActivePerAppTimer.readSummaryFromParcelLocked(in); mMobileRadioActiveAdjustedTime.readSummaryFromParcelLocked(in); @@ -6969,7 +7023,9 @@ public final class BatteryStatsImpl extends BatteryStats { mOnBatteryTimeBase.writeSummaryToParcel(out, NOW_SYS, NOWREAL_SYS); mOnBatteryScreenOffTimeBase.writeSummaryToParcel(out, NOW_SYS, NOWREAL_SYS); out.writeInt(mDischargeUnplugLevel); + out.writeInt(mDischargePlugLevel); out.writeInt(mDischargeCurrentLevel); + out.writeInt(mCurrentBatteryLevel); out.writeInt(getLowDischargeAmountSinceCharge()); out.writeInt(getHighDischargeAmountSinceCharge()); out.writeInt(getDischargeAmountScreenOnSinceCharge()); @@ -7257,7 +7313,7 @@ public final class BatteryStatsImpl extends BatteryStats { mNetworkByteActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase, in); mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase, in); } - mMobileRadioActive = false; + mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; mMobileRadioActiveTimer = new StopwatchTimer(null, -400, null, mOnBatteryTimeBase, in); mMobileRadioActivePerAppTimer = new StopwatchTimer(null, -401, null, mOnBatteryTimeBase, in); @@ -7279,7 +7335,9 @@ public final class BatteryStatsImpl extends BatteryStats { null, mOnBatteryTimeBase, in); } mDischargeUnplugLevel = in.readInt(); + mDischargePlugLevel = in.readInt(); mDischargeCurrentLevel = in.readInt(); + mCurrentBatteryLevel = in.readInt(); mLowDischargeAmountSinceCharge = in.readInt(); mHighDischargeAmountSinceCharge = in.readInt(); mDischargeAmountScreenOn = in.readInt(); @@ -7397,7 +7455,9 @@ public final class BatteryStatsImpl extends BatteryStats { mBluetoothStateTimer[i].writeToParcel(out, uSecRealtime); } out.writeInt(mDischargeUnplugLevel); + out.writeInt(mDischargePlugLevel); out.writeInt(mDischargeCurrentLevel); + out.writeInt(mCurrentBatteryLevel); out.writeInt(mLowDischargeAmountSinceCharge); out.writeInt(mHighDischargeAmountSinceCharge); out.writeInt(mDischargeAmountScreenOn); @@ -7494,6 +7554,7 @@ public final class BatteryStatsImpl extends BatteryStats { pr.println("*** Data connection type #" + i + ":"); mPhoneDataConnectionsTimer[i].logState(pr, " "); } + pr.println("*** mMobileRadioPowerState=" + mMobileRadioPowerState); pr.println("*** Mobile network active timer:"); mMobileRadioActiveTimer.logState(pr, " "); pr.println("*** Mobile network active adjusted timer:"); diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java index 5538dca..4a26b4b 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -55,6 +55,11 @@ public class RuntimeInit { private static final native void nativeFinishInit(); private static final native void nativeSetExitWithoutCleanup(boolean exitWithoutCleanup); + private static int Clog_e(String tag, String msg, Throwable tr) { + return Log.println_native(Log.LOG_ID_CRASH, Log.ERROR, tag, + msg + '\n' + Log.getStackTraceString(tr)); + } + /** * Use this to log a message when a thread exits due to an uncaught * exception. The framework catches these for the main threads, so @@ -68,7 +73,7 @@ public class RuntimeInit { mCrashing = true; if (mApplicationObject == null) { - Slog.e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e); + Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e); } else { StringBuilder message = new StringBuilder(); message.append("FATAL EXCEPTION: ").append(t.getName()).append("\n"); @@ -77,7 +82,7 @@ public class RuntimeInit { message.append("Process: ").append(processName).append(", "); } message.append("PID: ").append(Process.myPid()); - Slog.e(TAG, message.toString(), e); + Clog_e(TAG, message.toString(), e); } // Bring up crash dialog, wait for it to be dismissed @@ -85,9 +90,9 @@ public class RuntimeInit { mApplicationObject, new ApplicationErrorReport.CrashInfo(e)); } catch (Throwable t2) { try { - Slog.e(TAG, "Error reporting crash", t2); + Clog_e(TAG, "Error reporting crash", t2); } catch (Throwable t3) { - // Even Slog.e() fails! Oh well. + // Even Clog_e() fails! Oh well. } } finally { // Try everything to make sure this process goes away. diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java index b776226..5d7d322 100644 --- a/core/java/com/android/internal/view/menu/MenuBuilder.java +++ b/core/java/com/android/internal/view/menu/MenuBuilder.java @@ -392,8 +392,8 @@ public class MenuBuilder implements Menu { private MenuItem addInternal(int group, int id, int categoryOrder, CharSequence title) { final int ordering = getOrdering(categoryOrder); - final MenuItemImpl item = new MenuItemImpl(this, group, id, categoryOrder, - ordering, title, mDefaultShowAsAction); + final MenuItemImpl item = createNewMenuItem(group, id, categoryOrder, ordering, title, + mDefaultShowAsAction); if (mCurrentMenuInfo != null) { // Pass along the current menu info @@ -405,7 +405,14 @@ public class MenuBuilder implements Menu { return item; } - + + // Layoutlib overrides this method to return its custom implementation of MenuItemImpl + private MenuItemImpl createNewMenuItem(int group, int id, int categoryOrder, int ordering, + CharSequence title, int defaultShowAsAction) { + return new MenuItemImpl(this, group, id, categoryOrder, ordering, title, + defaultShowAsAction); + } + public MenuItem add(CharSequence title) { return addInternal(0, 0, 0, title); } |
