diff options
Diffstat (limited to 'core/java')
88 files changed, 2334 insertions, 1144 deletions
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java index 02a329d..da48709 100644 --- a/core/java/android/animation/Animator.java +++ b/core/java/android/animation/Animator.java @@ -16,12 +16,7 @@ package android.animation; -import android.content.res.Configuration; import android.content.res.ConstantState; -import android.content.res.Resources; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.animation.AnimationUtils; import java.util.ArrayList; @@ -30,29 +25,6 @@ import java.util.ArrayList; * started, ended, and have <code>AnimatorListeners</code> added to them. */ public abstract class Animator implements Cloneable { - /** - * Set this hint when duration for the animation does not need to be scaled. By default, no - * scaling is applied to the duration. - */ - public static final int HINT_NO_SCALE = 0; - - /** - * Set this scale hint (using {@link #setDurationScaleHint(int, Resources)} when the animation's - * moving distance is proportional to the screen size. (e.g. a view coming in from the bottom of - * the screen to top/center). With this scale hint set, the animation duration will be - * automatically scaled based on screen size. - */ - public static final int HINT_DISTANCE_PROPORTIONAL_TO_SCREEN_SIZE = 1; - - /** - * Set this scale hint (using {@link #setDurationScaleHint(int, Resources)}) if the animation - * has pre-defined moving distance in dp that does not vary from device to device. This is - * extremely useful when the animation needs to run on both phones/tablets and TV, because TV - * has inflated dp and therefore will have a longer visual arc for the same animation than on - * the phone. This hint is used to calculate a scaling factor to compensate for different - * visual arcs while maintaining the same angular velocity for the animation. - */ - public static final int HINT_DISTANCE_DEFINED_IN_DP = 2; /** * The set of listeners to be sent events through the life of an animation. @@ -83,24 +55,6 @@ public abstract class Animator implements Cloneable { private AnimatorConstantState mConstantState; /** - * Scaling factor for an animation that moves across the whole screen. - */ - float mScreenSizeBasedDurationScale = 1.0f; - - /** - * Scaling factor for an animation that is defined to move the same amount of dp across all - * devices. - */ - float mDpBasedDurationScale = 1.0f; - - /** - * By default, the scaling assumes the animation moves across the entire screen. - */ - int mDurationScaleHint = HINT_NO_SCALE; - - private final static boolean ANIM_DEBUG = false; - - /** * Starts this animation. If the animation has a nonzero startDelay, the animation will start * running after that delay elapses. A non-delayed animation will have its initial * value(s) set immediately, followed by calls to @@ -230,78 +184,6 @@ public abstract class Animator implements Cloneable { public abstract long getDuration(); /** - * Hints how duration scaling factor should be calculated. The duration will not be scaled when - * hint is set to {@link #HINT_NO_SCALE}. Otherwise, the duration will be automatically scaled - * per device to achieve the same look and feel across different devices. In order to do - * that, the same angular velocity of the animation will be needed on different devices in - * users' field of view. Therefore, the duration scale factor is determined by the ratio of the - * angular movement on current devices to that on the baseline device (i.e. Nexus 5). - * - * @param hint an indicator on how the animation is defined. The hint could be - * {@link #HINT_NO_SCALE}, {@link #HINT_DISTANCE_PROPORTIONAL_TO_SCREEN_SIZE} or - * {@link #HINT_DISTANCE_DEFINED_IN_DP}. - * @param res The resources {@see android.content.res.Resources} for getting display metrics - */ - public void setDurationScaleHint(int hint, Resources res) { - if (ANIM_DEBUG) { - Log.d("ANIM_DEBUG", "distance based duration hint: " + hint); - } - if (hint == mDurationScaleHint) { - return; - } - mDurationScaleHint = hint; - if (hint != HINT_NO_SCALE) { - int uiMode = res.getConfiguration().uiMode & Configuration.UI_MODE_TYPE_MASK; - DisplayMetrics metrics = res.getDisplayMetrics(); - float width = metrics.widthPixels / metrics.xdpi; - float height = metrics.heightPixels / metrics.ydpi; - float viewingDistance = AnimationUtils.getViewingDistance(width, height, uiMode); - if (ANIM_DEBUG) { - Log.d("ANIM_DEBUG", "width, height, viewing distance, uimode: " - + width + ", " + height + ", " + viewingDistance + ", " + uiMode); - } - mScreenSizeBasedDurationScale = AnimationUtils - .getScreenSizeBasedDurationScale(width, height, viewingDistance); - mDpBasedDurationScale = AnimationUtils.getDpBasedDurationScale( - metrics.density, metrics.xdpi, viewingDistance); - if (ANIM_DEBUG) { - Log.d("ANIM_DEBUG", "screen based scale, dp based scale: " + - mScreenSizeBasedDurationScale + ", " + mDpBasedDurationScale); - } - } - } - - // Copies duration scale hint and scaling factors to the new animation. - void copyDurationScaleInfoTo(Animator anim) { - anim.mDurationScaleHint = mDurationScaleHint; - anim.mScreenSizeBasedDurationScale = mScreenSizeBasedDurationScale; - anim.mDpBasedDurationScale = mDpBasedDurationScale; - } - - /** - * @return The scaled duration calculated based on distance of movement (as defined by the - * animation) and perceived velocity (derived from the duration set on the animation for - * baseline device) - */ - public long getDistanceBasedDuration() { - return (long) (getDuration() * getDistanceBasedDurationScale()); - } - - /** - * @return scaling factor of duration based on the duration scale hint. A scaling factor of 1 - * means no scaling will be applied to the duration. - */ - float getDistanceBasedDurationScale() { - if (mDurationScaleHint == HINT_DISTANCE_PROPORTIONAL_TO_SCREEN_SIZE) { - return mScreenSizeBasedDurationScale; - } else if (mDurationScaleHint == HINT_DISTANCE_DEFINED_IN_DP) { - return mDpBasedDurationScale; - } else { - return 1f; - } - } - - /** * The time interpolator used in calculating the elapsed fraction of the * animation. The interpolator determines whether the animation runs with * linear or non-linear motion, such as acceleration and deceleration. The diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java index 224e8e9..427ecce 100644 --- a/core/java/android/animation/AnimatorInflater.java +++ b/core/java/android/animation/AnimatorInflater.java @@ -70,13 +70,6 @@ public class AnimatorInflater { private static final int VALUE_TYPE_COLOR = 3; private static final int VALUE_TYPE_UNDEFINED = 4; - /** - * Enum values used in XML attributes to indicate the duration scale hint. - */ - private static final int HINT_NO_SCALE = 0; - private static final int HINT_PROPORTIONAL_TO_SCREEN = 1; - private static final int HINT_DEFINED_IN_DP = 2; - private static final boolean DBG_ANIMATOR_INFLATER = false; // used to calculate changing configs for resource references @@ -698,9 +691,6 @@ public class AnimatorInflater { int ordering = a.getInt(R.styleable.AnimatorSet_ordering, TOGETHER); createAnimatorFromXml(res, theme, parser, attrs, (AnimatorSet) anim, ordering, pixelSize); - final int hint = a.getInt(R.styleable.AnimatorSet_durationScaleHint, - HINT_NO_SCALE); - anim.setDurationScaleHint(hint, res); a.recycle(); } else if (name.equals("propertyValuesHolder")) { PropertyValuesHolder[] values = loadValues(res, theme, parser, @@ -790,6 +780,31 @@ public class AnimatorInflater { return valuesArray; } + // When no value type is provided in keyframe, we need to infer the type from the value. i.e. + // if value is defined in the style of a color value, then the color type is returned. + // Otherwise, default float type is returned. + private static int inferValueTypeOfKeyframe(Resources res, Theme theme, AttributeSet attrs) { + int valueType; + TypedArray a; + if (theme != null) { + a = theme.obtainStyledAttributes(attrs, R.styleable.Keyframe, 0, 0); + } else { + a = res.obtainAttributes(attrs, R.styleable.Keyframe); + } + + TypedValue keyframeValue = a.peekValue(R.styleable.Keyframe_value); + boolean hasValue = (keyframeValue != null); + // When no value type is provided, check whether it's a color type first. + // If not, fall back to default value type (i.e. float type). + if (hasValue && isColorType(keyframeValue.type)) { + valueType = VALUE_TYPE_COLOR; + } else { + valueType = VALUE_TYPE_FLOAT; + } + a.recycle(); + return valueType; + } + private static void dumpKeyframes(Object[] keyframes, String header) { if (keyframes == null || keyframes.length == 0) { return; @@ -817,6 +832,9 @@ public class AnimatorInflater { type != XmlPullParser.END_DOCUMENT) { String name = parser.getName(); if (name.equals("keyframe")) { + if (valueType == VALUE_TYPE_UNDEFINED) { + valueType = inferValueTypeOfKeyframe(res, theme, Xml.asAttributeSet(parser)); + } Keyframe keyframe = loadKeyframe(res, theme, Xml.asAttributeSet(parser), valueType); if (keyframe != null) { if (keyframes == null) { @@ -1037,9 +1055,6 @@ public class AnimatorInflater { anim.setInterpolator(interpolator); } - final int hint = arrayAnimator.getInt(R.styleable.Animator_durationScaleHint, - HINT_NO_SCALE); - anim.setDurationScaleHint(hint, res); arrayAnimator.recycle(); if (arrayObjectAnimator != null) { arrayObjectAnimator.recycle(); diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java index dd5f18e..6503d89 100644 --- a/core/java/android/animation/AnimatorSet.java +++ b/core/java/android/animation/AnimatorSet.java @@ -519,7 +519,6 @@ public final class AnimatorSet extends Animator { for (Node node : mNodes) { node.animation.setAllowRunningAsynchronously(false); - copyDurationScaleInfoTo(node.animation); } if (mDuration >= 0) { @@ -1095,7 +1094,8 @@ public final class AnimatorSet extends Animator { public Node clone() { try { Node node = (Node) super.clone(); - node.animation = (Animator) animation.clone(); + node.animation = animation.clone(); + node.done = false; return node; } catch (CloneNotSupportedException e) { throw new AssertionError(); diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index 275e78e..a455f8b 100644 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -17,12 +17,9 @@ package android.animation; import android.annotation.CallSuper; -import android.content.res.Configuration; -import android.content.res.Resources; import android.os.Looper; import android.os.Trace; import android.util.AndroidRuntimeException; -import android.util.DisplayMetrics; import android.util.Log; import android.view.Choreographer; import android.view.animation.AccelerateDecelerateInterpolator; @@ -564,7 +561,7 @@ public class ValueAnimator extends Animator { } private void updateScaledDuration() { - mDuration = (long)(mUnscaledDuration * sDurationScale * getDistanceBasedDurationScale()); + mDuration = (long)(mUnscaledDuration * sDurationScale); } /** @@ -1486,6 +1483,11 @@ public class ValueAnimator extends Animator { anim.mPaused = false; anim.mResumed = false; anim.mStartListenersCalled = false; + anim.mStartTime = 0; + anim.mStartTimeCommitted = false; + anim.mPauseTime = 0; + anim.mCurrentFraction = 0; + anim.mDelayStartTime = 0; PropertyValuesHolder[] oldValues = mValues; if (oldValues != null) { diff --git a/core/java/android/annotation/BinderThread.java b/core/java/android/annotation/BinderThread.java new file mode 100644 index 0000000..c69ba10 --- /dev/null +++ b/core/java/android/annotation/BinderThread.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015 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.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that the annotated method should only be called on the binder thread. + * If the annotated element is a class, then all methods in the class should be called + * on the binder thread. + * <p> + * Example: + * <pre>{@code + * (@BinderThread + * public BeamShareData createBeamShareData() { ... } + * }</pre> + * + * {@hide} + */ +@Retention(SOURCE) +@Target({METHOD,CONSTRUCTOR,TYPE}) +public @interface BinderThread { +}
\ No newline at end of file diff --git a/core/java/android/annotation/MainThread.java b/core/java/android/annotation/MainThread.java new file mode 100644 index 0000000..18a4283 --- /dev/null +++ b/core/java/android/annotation/MainThread.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015 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.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that the annotated method should only be called on the main thread. + * If the annotated element is a class, then all methods in the class should be called + * on the main thread. + * <p> + * Example: + * <pre>{@code + * @MainThread + * public void deliverResult(D data) { ... } + * }</pre> + * + * {@hide} + */ +@Retention(SOURCE) +@Target({METHOD,CONSTRUCTOR,TYPE}) +public @interface MainThread { +}
\ No newline at end of file diff --git a/core/java/android/annotation/RequiresPermission.java b/core/java/android/annotation/RequiresPermission.java new file mode 100644 index 0000000..4aed5c1 --- /dev/null +++ b/core/java/android/annotation/RequiresPermission.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2015 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.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that the annotated element requires (or may require) one or more permissions. + * <p/> + * Example of requiring a single permission: + * <pre>{@code + * @RequiresPermission(Manifest.permission.SET_WALLPAPER) + * public abstract void setWallpaper(Bitmap bitmap) throws IOException; + * + * @RequiresPermission(ACCESS_COARSE_LOCATION) + * public abstract Location getLastKnownLocation(String provider); + * }</pre> + * Example of requiring at least one permission from a set: + * <pre>{@code + * @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) + * public abstract Location getLastKnownLocation(String provider); + * }</pre> + * Example of requiring multiple permissions: + * <pre>{@code + * @RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) + * public abstract Location getLastKnownLocation(String provider); + * }</pre> + * Example of requiring separate read and write permissions for a content provider: + * <pre>{@code + * @RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS)) + * @RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS)) + * public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks"); + * }</pre> + * + * @hide + */ +@Retention(SOURCE) +@Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD}) +public @interface RequiresPermission { + /** + * The name of the permission that is required, if precisely one permission + * is required. If more than one permission is required, specify either + * {@link #allOf()} or {@link #anyOf()} instead. + * <p> + * If specified, {@link #anyOf()} and {@link #allOf()} must both be null. + */ + String value() default ""; + + /** + * Specifies a list of permission names that are all required. + * <p> + * If specified, {@link #anyOf()} and {@link #value()} must both be null. + */ + String[] allOf() default {}; + + /** + * Specifies a list of permission names where at least one is required + * <p> + * If specified, {@link #allOf()} and {@link #value()} must both be null. + */ + String[] anyOf() default {}; + + /** + * If true, the permission may not be required in all cases (e.g. it may only be + * enforced on certain platforms, or for certain call parameters, etc. + */ + boolean conditional() default false; + + /** + * Specifies that the given permission is required for read operations + */ + @Target(FIELD) + @interface Read { + RequiresPermission value(); + } + + /** + * Specifies that the given permission is required for write operations + */ + @Target(FIELD) + @interface Write { + RequiresPermission value(); + } +} diff --git a/core/java/android/annotation/UiThread.java b/core/java/android/annotation/UiThread.java new file mode 100644 index 0000000..b814600 --- /dev/null +++ b/core/java/android/annotation/UiThread.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015 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.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that the annotated method or constructor should only be called on the UI thread. + * If the annotated element is a class, then all methods in the class should be called + * on the UI thread. + * <p> + * Example: + * <pre>{@code + * @UiThread + * public abstract void setText(@NonNull String text) { ... } + * }</pre> + * + * {@hide} + */ +@Retention(SOURCE) +@Target({METHOD,CONSTRUCTOR,TYPE}) +public @interface UiThread { +}
\ No newline at end of file diff --git a/core/java/android/annotation/WorkerThread.java b/core/java/android/annotation/WorkerThread.java new file mode 100644 index 0000000..dd12e05 --- /dev/null +++ b/core/java/android/annotation/WorkerThread.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015 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.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that the annotated method should only be called on a worker thread. + * If the annotated element is a class, then all methods in the class should be called + * on a worker thread. + * <p> + * Example: + * <pre>{@code + * (@WorkerThread + * protected abstract FilterResults performFiltering(CharSequence constraint); + * }</pre> + * + * {@hide} + */ +@Retention(SOURCE) +@Target({METHOD,CONSTRUCTOR,TYPE}) +public @interface WorkerThread { +}
\ No newline at end of file diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 4ccde1c..69cba78 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -6483,6 +6483,18 @@ public class Activity extends ContextThemeWrapper } /** + * Shows the user the system defined message for telling the user how to exit + * lock task mode. The task containing this activity must be in lock task mode at the time + * of this call for the message to be displayed. + */ + public void showLockTaskEscapeMessage() { + try { + ActivityManagerNative.getDefault().showLockTaskEscapeMessage(mToken); + } catch (RemoteException e) { + } + } + + /** * Interface for informing a translucent {@link Activity} once all visible activities below it * have completed drawing. This is necessary only after an {@link Activity} has been made * opaque using {@link Activity#convertFromTranslucent()} and before it has been drawn diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index d56dc1e..bde8f39 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -16,6 +16,8 @@ package android.app; +import android.annotation.NonNull; + /** * Activity manager local system service interface. * @@ -27,4 +29,23 @@ public abstract class ActivityManagerInternal { public abstract int startIsolatedProcess(String entryPoint, String[] mainArgs, String processName, String abiOverride, int uid, Runnable crashHandler); + + /** + * Acquires a sleep token with the specified tag. + * + * @param tag A string identifying the purpose of the token (eg. "Dream"). + */ + public abstract SleepToken acquireSleepToken(@NonNull String tag); + + /** + * Sleep tokens cause the activity manager to put the top activity to sleep. + * They are used by components such as dreams that may hide and block interaction + * with underlying activities. + */ + public static abstract class SleepToken { + /** + * Releases the sleep token. + */ + public abstract void release(); + } } diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index add7af2..b11c509 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -2345,6 +2345,14 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case SHOW_LOCK_TASK_ESCAPE_MESSAGE_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + final IBinder token = data.readStrongBinder(); + showLockTaskEscapeMessage(token); + reply.writeNoException(); + return true; + } + case SET_TASK_DESCRIPTION_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder token = data.readStrongBinder(); @@ -2505,6 +2513,14 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case UPDATE_DEVICE_OWNER_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + String packageName = data.readString(); + updateDeviceOwner(packageName); + reply.writeNoException(); + return true; + } + case GET_PACKAGE_PROCESS_STATE_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); String pkg = data.readString(); @@ -5552,6 +5568,19 @@ class ActivityManagerProxy implements IActivityManager } @Override + public void showLockTaskEscapeMessage(IBinder token) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + mRemote.transact(SHOW_LOCK_TASK_ESCAPE_MESSAGE_TRANSACTION, data, reply, + IBinder.FLAG_ONEWAY); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + @Override public void setTaskDescription(IBinder token, ActivityManager.TaskDescription values) throws RemoteException { Parcel data = Parcel.obtain(); @@ -5780,6 +5809,18 @@ class ActivityManagerProxy implements IActivityManager } @Override + public void updateDeviceOwner(String packageName) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeString(packageName); + mRemote.transact(UPDATE_DEVICE_OWNER_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + @Override public int getPackageProcessState(String packageName) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 10d6d01..9bad9bb 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -255,23 +255,18 @@ public final class ActivityThread { } } - static final class AcquiringProviderRecord { - IActivityManager.ContentProviderHolder holder; - boolean acquiring = true; - int requests = 1; - // Set if there was a runtime exception when trying to acquire the provider. - RuntimeException runtimeException = null; - } - // The lock of mProviderMap protects the following variables. - final ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap = new ArrayMap<>(); - final ArrayMap<ProviderKey, AcquiringProviderRecord> mAcquiringProviderMap = new ArrayMap<>(); - final ArrayMap<IBinder, ProviderRefCount> mProviderRefCountMap = new ArrayMap<>(); - final ArrayMap<IBinder, ProviderClientRecord> mLocalProviders = new ArrayMap<>(); - final ArrayMap<ComponentName, ProviderClientRecord> mLocalProvidersByName = new ArrayMap<>(); + final ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap + = new ArrayMap<ProviderKey, ProviderClientRecord>(); + final ArrayMap<IBinder, ProviderRefCount> mProviderRefCountMap + = new ArrayMap<IBinder, ProviderRefCount>(); + final ArrayMap<IBinder, ProviderClientRecord> mLocalProviders + = new ArrayMap<IBinder, ProviderClientRecord>(); + final ArrayMap<ComponentName, ProviderClientRecord> mLocalProvidersByName + = new ArrayMap<ComponentName, ProviderClientRecord>(); final ArrayMap<Activity, ArrayList<OnActivityPausedListener>> mOnPauseListeners - = new ArrayMap<>(); + = new ArrayMap<Activity, ArrayList<OnActivityPausedListener>>(); final GcIdler mGcIdler = new GcIdler(); boolean mGcIdlerScheduled = false; @@ -351,7 +346,7 @@ public final class ActivityThread { } } - static final class ProviderClientRecord { + final class ProviderClientRecord { final String[] mNames; final IContentProvider mProvider; final ContentProvider mLocalProvider; @@ -2541,11 +2536,16 @@ public final class ActivityThread { if (cmd.requestType == ActivityManager.ASSIST_CONTEXT_FULL) { data.putParcelable(AssistStructure.ASSIST_KEY, new AssistStructure(r.activity)); AssistContent content = new AssistContent(); - Intent intent = new Intent(r.activity.getIntent()); - intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_WRITE_URI_PERMISSION - | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)); - intent.removeUnsafeExtras(); - content.setIntent(intent); + Intent activityIntent = r.activity.getIntent(); + if (activityIntent != null) { + Intent intent = new Intent(activityIntent); + intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_WRITE_URI_PERMISSION + | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)); + intent.removeUnsafeExtras(); + content.setIntent(intent); + } else { + content.setIntent(new Intent()); + } r.activity.onProvideAssistContent(content); data.putParcelable(AssistContent.ASSIST_KEY, content); } @@ -4711,74 +4711,23 @@ public final class ActivityThread { public final IContentProvider acquireProvider( Context c, String auth, int userId, boolean stable) { - final ProviderKey key = new ProviderKey(auth, userId); - final IContentProvider provider = acquireExistingProvider(c, key, stable); + final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable); if (provider != null) { return provider; } - AcquiringProviderRecord r; - boolean first = false; - synchronized (mAcquiringProviderMap) { - r = mAcquiringProviderMap.get(key); - if (r == null) { - r = new AcquiringProviderRecord(); - mAcquiringProviderMap.put(key, r); - first = true; - } else { - r.requests++; - } - } + // There is a possible race here. Another thread may try to acquire + // the same provider at the same time. When this happens, we want to ensure + // that the first one wins. + // Note that we cannot hold the lock while acquiring and installing the + // provider since it might take a long time to run and it could also potentially + // be re-entrant in the case where the provider is in the same process. IActivityManager.ContentProviderHolder holder = null; try { - if (first) { - // Multiple threads may try to acquire the same provider at the same time. - // When this happens, we only let the first one really gets provider. - // Other threads just wait for its result. - // Note that we cannot hold the lock while acquiring and installing the - // provider since it might take a long time to run and it could also potentially - // be re-entrant in the case where the provider is in the same process. - holder = ActivityManagerNative.getDefault().getContentProvider( - getApplicationThread(), auth, userId, stable); - } else { - synchronized (r) { - while (r.acquiring) { - try { - r.wait(); - } catch (InterruptedException e) { - } - } - holder = r.holder; - } - } + holder = ActivityManagerNative.getDefault().getContentProvider( + getApplicationThread(), auth, userId, stable); } catch (RemoteException ex) { - } catch (RuntimeException e) { - synchronized (r) { - r.runtimeException = e; - } - } finally { - if (first) { - synchronized (r) { - r.holder = holder; - r.acquiring = false; - r.notifyAll(); - } - } - - synchronized (mAcquiringProviderMap) { - if (--r.requests == 0) { - mAcquiringProviderMap.remove(key); - } - } - - if (r.runtimeException != null) { - // Was set when the first thread tried to acquire the provider, - // but we should make sure it is thrown for all threads trying to - // acquire the provider. - throw r.runtimeException; - } } - if (holder == null) { Slog.e(TAG, "Failed to find provider info for " + auth); return null; @@ -4861,12 +4810,8 @@ public final class ActivityThread { public final IContentProvider acquireExistingProvider( Context c, String auth, int userId, boolean stable) { - return acquireExistingProvider(c, new ProviderKey(auth, userId), stable); - } - - final IContentProvider acquireExistingProvider( - Context c, ProviderKey key, boolean stable) { synchronized (mProviderMap) { + final ProviderKey key = new ProviderKey(auth, userId); final ProviderClientRecord pr = mProviderMap.get(key); if (pr == null) { return null; @@ -4877,7 +4822,7 @@ public final class ActivityThread { if (!jBinder.isBinderAlive()) { // The hosting process of the provider has died; we can't // use this one. - Log.i(TAG, "Acquiring provider " + key.authority + " for user " + key.userId + Log.i(TAG, "Acquiring provider " + auth + " for user " + userId + ": existing object's process dead"); handleUnstableProviderDiedLocked(jBinder, true); return null; @@ -5199,12 +5144,18 @@ public final class ActivityThread { if (DEBUG_PROVIDER) { Slog.v(TAG, "installProvider: lost the race, updating ref count"); } - // The provider has already been installed, so we need - // to increase reference count to the existing one, but - // only if release is needed (that is, it is not running - // in the system process or local to the process). + // We need to transfer our new reference to the existing + // ref count, releasing the old one... but only if + // release is needed (that is, it is not running in the + // system process). if (!noReleaseNeeded) { incProviderRefLocked(prc, stable); + try { + ActivityManagerNative.getDefault().removeContentProvider( + holder.connection, stable); + } catch (RemoteException e) { + //do nothing content provider object is dead any way + } } } else { ProviderClientRecord client = installProviderAuthoritiesLocked( diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java index 179957d..9d1d312 100644 --- a/core/java/android/app/AlarmManager.java +++ b/core/java/android/app/AlarmManager.java @@ -597,6 +597,15 @@ public class AlarmManager } } + /** @hide */ + public long getNextWakeFromIdleTime() { + try { + return mService.getNextWakeFromIdleTime(); + } catch (RemoteException ex) { + return Long.MAX_VALUE; + } + } + /** * Gets information about the next alarm clock currently scheduled. * diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index dfe7e18..10f5960 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -62,6 +62,9 @@ import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.SystemProperties; @@ -81,7 +84,9 @@ import com.android.internal.util.UserIcons; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; +import java.util.Objects; /*package*/ final class ApplicationPackageManager extends PackageManager { @@ -98,6 +103,9 @@ final class ApplicationPackageManager extends PackageManager { @GuardedBy("mLock") private PackageInstaller mInstaller; + @GuardedBy("mDelegates") + private final ArrayList<MoveCallbackDelegate> mDelegates = new ArrayList<>(); + UserManager getUserManager() { synchronized (mLock) { if (mUserManager == null) { @@ -1410,57 +1418,100 @@ final class ApplicationPackageManager extends PackageManager { } @Override - public void movePackage(String packageName, IPackageMoveObserver observer, int flags) { + public String getInstallerPackageName(String packageName) { + try { + return mPM.getInstallerPackageName(packageName); + } catch (RemoteException e) { + // Should never happen! + } + return null; + } + + @Override + public int getMoveStatus(int moveId) { try { - mPM.movePackage(packageName, observer, flags); + return mPM.getMoveStatus(moveId); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } } @Override - public void movePackageAndData(String packageName, String volumeUuid, - IPackageMoveObserver observer) { + public void registerMoveCallback(MoveCallback callback, Handler handler) { + synchronized (mDelegates) { + final MoveCallbackDelegate delegate = new MoveCallbackDelegate(callback, + handler.getLooper()); + try { + mPM.registerMoveCallback(delegate); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + mDelegates.add(delegate); + } + } + + @Override + public void unregisterMoveCallback(MoveCallback callback) { + synchronized (mDelegates) { + for (Iterator<MoveCallbackDelegate> i = mDelegates.iterator(); i.hasNext();) { + final MoveCallbackDelegate delegate = i.next(); + if (delegate.mCallback == callback) { + try { + mPM.unregisterMoveCallback(delegate); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + i.remove(); + } + } + } + } + + @Override + public int movePackage(String packageName, VolumeInfo vol) { try { - mPM.movePackageAndData(packageName, volumeUuid, observer); + final String volumeUuid; + if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.id)) { + volumeUuid = StorageManager.UUID_PRIVATE_INTERNAL; + } else if (vol.isPrimaryPhysical()) { + volumeUuid = StorageManager.UUID_PRIMARY_PHYSICAL; + } else { + volumeUuid = Preconditions.checkNotNull(vol.fsUuid); + } + + return mPM.movePackage(packageName, volumeUuid); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } } @Override - public @NonNull VolumeInfo getApplicationCurrentVolume(ApplicationInfo app) { + public @Nullable VolumeInfo getPackageCurrentVolume(ApplicationInfo app) { final StorageManager storage = mContext.getSystemService(StorageManager.class); if (app.isInternal()) { - return Preconditions.checkNotNull( - storage.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL)); + return storage.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL); } else if (app.isExternalAsec()) { - final List<VolumeInfo> vols = storage.getVolumes(); - for (VolumeInfo vol : vols) { - if ((vol.getType() == VolumeInfo.TYPE_PUBLIC) && vol.isPrimary()) { - return vol; - } - } - throw new IllegalStateException("Failed to find primary public volume"); + return storage.getPrimaryPhysicalVolume(); } else { - return Preconditions.checkNotNull(storage.findVolumeByUuid(app.volumeUuid)); + return storage.findVolumeByUuid(app.volumeUuid); } } @Override - public @NonNull List<VolumeInfo> getApplicationCandidateVolumes(ApplicationInfo app) { + public @NonNull List<VolumeInfo> getPackageCandidateVolumes(ApplicationInfo app) { final StorageManager storage = mContext.getSystemService(StorageManager.class); + final VolumeInfo currentVol = getPackageCurrentVolume(app); final List<VolumeInfo> vols = storage.getVolumes(); final List<VolumeInfo> candidates = new ArrayList<>(); for (VolumeInfo vol : vols) { - if (isCandidateVolume(app, vol)) { + if (Objects.equals(vol, currentVol) || isPackageCandidateVolume(app, vol)) { candidates.add(vol); } } return candidates; } - private static boolean isCandidateVolume(ApplicationInfo app, VolumeInfo vol) { + private static boolean isPackageCandidateVolume(ApplicationInfo app, VolumeInfo vol) { // Private internal is always an option if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.getId())) { return true; @@ -1473,10 +1524,14 @@ final class ApplicationPackageManager extends PackageManager { return false; } - // Moving into an ASEC on public primary is only an option when app is - // internal, or already in ASEC - if ((vol.getType() == VolumeInfo.TYPE_PUBLIC) && vol.isPrimary()) { - return app.isInternal() || app.isExternalAsec(); + // Gotta be able to write there + if (!vol.isMountedWritable()) { + return false; + } + + // Moving into an ASEC on public primary is only option internal + if (vol.isPrimaryPhysical()) { + return app.isInternal(); } // Otherwise we can move to any private volume @@ -1484,13 +1539,66 @@ final class ApplicationPackageManager extends PackageManager { } @Override - public String getInstallerPackageName(String packageName) { + public int movePrimaryStorage(VolumeInfo vol) { try { - return mPM.getInstallerPackageName(packageName); + final String volumeUuid; + if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.id)) { + volumeUuid = StorageManager.UUID_PRIVATE_INTERNAL; + } else if (vol.isPrimaryPhysical()) { + volumeUuid = StorageManager.UUID_PRIMARY_PHYSICAL; + } else { + volumeUuid = Preconditions.checkNotNull(vol.fsUuid); + } + + return mPM.movePrimaryStorage(volumeUuid); } catch (RemoteException e) { - // Should never happen! + throw e.rethrowAsRuntimeException(); } - return null; + } + + public @Nullable VolumeInfo getPrimaryStorageCurrentVolume() { + final StorageManager storage = mContext.getSystemService(StorageManager.class); + final String volumeUuid = storage.getPrimaryStorageUuid(); + if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, volumeUuid)) { + return storage.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL); + } else if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, volumeUuid)) { + return storage.getPrimaryPhysicalVolume(); + } else { + return storage.findVolumeByUuid(volumeUuid); + } + } + + public @NonNull List<VolumeInfo> getPrimaryStorageCandidateVolumes() { + final StorageManager storage = mContext.getSystemService(StorageManager.class); + final VolumeInfo currentVol = getPrimaryStorageCurrentVolume(); + final List<VolumeInfo> vols = storage.getVolumes(); + final List<VolumeInfo> candidates = new ArrayList<>(); + for (VolumeInfo vol : vols) { + if (Objects.equals(vol, currentVol) || isPrimaryStorageCandidateVolume(vol)) { + candidates.add(vol); + } + } + return candidates; + } + + private static boolean isPrimaryStorageCandidateVolume(VolumeInfo vol) { + // Private internal is always an option + if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.getId())) { + return true; + } + + // Gotta be able to write there + if (!vol.isMountedWritable()) { + return false; + } + + // We can move to public volumes on legacy devices + if ((vol.getType() == VolumeInfo.TYPE_PUBLIC) && vol.getDisk().isDefaultPrimary()) { + return true; + } + + // Otherwise we can move to any private volume + return (vol.getType() == VolumeInfo.TYPE_PRIVATE); } @Override @@ -1941,6 +2049,45 @@ final class ApplicationPackageManager extends PackageManager { return null; } + /** {@hide} */ + private static class MoveCallbackDelegate extends IPackageMoveObserver.Stub implements + Handler.Callback { + private static final int MSG_STARTED = 1; + private static final int MSG_STATUS_CHANGED = 2; + + final MoveCallback mCallback; + final Handler mHandler; + + public MoveCallbackDelegate(MoveCallback callback, Looper looper) { + mCallback = callback; + mHandler = new Handler(looper, this); + } + + @Override + public boolean handleMessage(Message msg) { + final int moveId = msg.arg1; + switch (msg.what) { + case MSG_STARTED: + mCallback.onStarted(moveId, (String) msg.obj); + return true; + case MSG_STATUS_CHANGED: + mCallback.onStatusChanged(moveId, msg.arg2, (long) msg.obj); + return true; + } + return false; + } + + @Override + public void onStarted(int moveId, String title) { + mHandler.obtainMessage(MSG_STARTED, moveId, 0, title).sendToTarget(); + } + + @Override + public void onStatusChanged(int moveId, int status, long estMillis) { + mHandler.obtainMessage(MSG_STATUS_CHANGED, moveId, status, estMillis).sendToTarget(); + } + } + private final ContextImpl mContext; private final IPackageManager mPM; diff --git a/core/java/android/app/AssistStructure.java b/core/java/android/app/AssistStructure.java index 1e159a3..9946d79 100644 --- a/core/java/android/app/AssistStructure.java +++ b/core/java/android/app/AssistStructure.java @@ -218,6 +218,7 @@ final public class AssistStructure implements Parcelable { static final int FLAGS_FOCUSED = 0x00000020; static final int FLAGS_ACCESSIBILITY_FOCUSED = 0x04000000; static final int FLAGS_SELECTED = 0x00000040; + static final int FLAGS_ASSIST_BLOCKED = 0x00000080; static final int FLAGS_ACTIVATED = 0x40000000; static final int FLAGS_CHECKABLE = 0x00000100; static final int FLAGS_CHECKED = 0x00000200; @@ -356,6 +357,10 @@ final public class AssistStructure implements Parcelable { return mFlags&ViewNode.FLAGS_VISIBILITY_MASK; } + public boolean isAssistBlocked() { + return (mFlags&ViewNode.FLAGS_ASSIST_BLOCKED) == 0; + } + public boolean isEnabled() { return (mFlags&ViewNode.FLAGS_DISABLED) == 0; } @@ -484,6 +489,12 @@ final public class AssistStructure implements Parcelable { } @Override + public void setAssistBlocked(boolean state) { + mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_ASSIST_BLOCKED) + | (state ? 0 : ViewNode.FLAGS_ASSIST_BLOCKED); + } + + @Override public void setEnabled(boolean state) { mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_DISABLED) | (state ? 0 : ViewNode.FLAGS_DISABLED); diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 59de281..00558fe 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -467,6 +467,8 @@ public interface IActivityManager extends IInterface { public int getLockTaskModeState() throws RemoteException; + public void showLockTaskEscapeMessage(IBinder token) throws RemoteException; + public void setTaskDescription(IBinder token, ActivityManager.TaskDescription values) throws RemoteException; public void setTaskResizeable(int taskId, boolean resizeable) throws RemoteException; @@ -493,6 +495,7 @@ public interface IActivityManager extends IInterface { public void setVoiceKeepAwake(IVoiceInteractionSession session, boolean keepAwake) throws RemoteException; public void updateLockTaskPackages(int userId, String[] packages) throws RemoteException; + public void updateDeviceOwner(String packageName) throws RemoteException; public int getPackageProcessState(String packageName) throws RemoteException; @@ -834,4 +837,6 @@ public interface IActivityManager extends IInterface { int NOTE_ALARM_START_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+291; int NOTE_ALARM_FINISH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+292; int GET_PACKAGE_PROCESS_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+293; + int SHOW_LOCK_TASK_ESCAPE_MESSAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+294; + int UPDATE_DEVICE_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+295; } diff --git a/core/java/android/app/IAlarmManager.aidl b/core/java/android/app/IAlarmManager.aidl index d5719f5..327c00b 100644 --- a/core/java/android/app/IAlarmManager.aidl +++ b/core/java/android/app/IAlarmManager.aidl @@ -33,6 +33,7 @@ interface IAlarmManager { boolean setTime(long millis); void setTimeZone(String zone); void remove(in PendingIntent operation); + long getNextWakeFromIdleTime(); AlarmManager.AlarmClockInfo getNextAlarmClock(int userId); } diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index e275df0..ac8d5d8 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -67,6 +67,8 @@ interface INotificationManager void cancelNotificationFromListener(in INotificationListener token, String pkg, String tag, int id); void cancelNotificationsFromListener(in INotificationListener token, in String[] keys); + void setNotificationsShownFromListener(in INotificationListener token, in String[] keys); + ParceledListSlice getActiveNotificationsFromListener(in INotificationListener token, in String[] keys, int trim); void requestHintsFromListener(in INotificationListener token, int hints); int getHintsFromListener(in INotificationListener token); diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 56cd53e..ebb3c43 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -16,6 +16,8 @@ package android.app; +import android.Manifest; +import android.annotation.RequiresPermission; import android.app.trust.ITrustManager; import android.content.Context; import android.content.Intent; @@ -111,6 +113,7 @@ public class KeyguardManager { * * @see #reenableKeyguard() */ + @RequiresPermission(Manifest.permission.DISABLE_KEYGUARD) public void disableKeyguard() { try { mWM.disableKeyguard(mToken, mTag); @@ -132,6 +135,7 @@ public class KeyguardManager { * * @see #disableKeyguard() */ + @RequiresPermission(Manifest.permission.DISABLE_KEYGUARD) public void reenableKeyguard() { try { mWM.reenableKeyguard(mToken); @@ -302,6 +306,7 @@ public class KeyguardManager { * once the user has gotten past the keyguard. */ @Deprecated + @RequiresPermission(Manifest.permission.DISABLE_KEYGUARD) public void exitKeyguardSecurely(final OnKeyguardExitResult callback) { try { mWM.exitKeyguardSecurely(new IOnKeyguardExitResult.Stub() { diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index e7f8f6d..2cf23af 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -5648,6 +5648,13 @@ public class Notification implements Parcelable /** * Value to be used with {@link #setPricingInformation} to indicate that the content + * referred by the notification item is available currently as a pre-order, and the price + * value provided is the purchase price for the item. + */ + public static final String CONTENT_PRICING_PREORDER = "android.contentPrice.preorder"; + + /** + * Value to be used with {@link #setPricingInformation} to indicate that the content * referred by the notification item is available as part of a subscription based service, * and the price value provided is the subscription price for the service. */ diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index fe284ce..aea413d 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -264,6 +264,20 @@ public class DeviceAdminReceiver extends BroadcastReceiver { public static final String EXTRA_CHOOSE_PRIVATE_KEY_RESPONSE = "android.app.extra.CHOOSE_PRIVATE_KEY_RESPONSE"; /** + * Broadcast action: notify device owner that there is a pending system update. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_NOTIFY_PENDING_SYSTEM_UPDATE = "android.app.action.NOTIFY_PENDING_SYSTEM_UPDATE"; + + /** + * A long type extra for {@link #onSystemUpdatePending} recording the system time as given by + * {@link System#currentTimeMillis()} when the current pending system update is first available. + * @hide + */ + public static final String EXTRA_SYSTEM_UPDATE_RECEIVED_TIME = "android.app.extra.SYSTEM_UPDATE_RECEIVED_TIME"; + + /** * Name under which a DevicePolicy component publishes information * about itself. This meta-data must reference an XML resource containing * a device-admin tag. @@ -486,6 +500,22 @@ public class DeviceAdminReceiver extends BroadcastReceiver { } /** + * Allows the receiver to be notified when information about a pending system update is + * available from the system update service. The same pending system update can trigger multiple + * calls to this method, so it is necessary to examine the incoming parameters for details about + * the update. + * <p> + * This callback is only applicable to device owners. + * + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + * @param receivedTime The time as given by {@link System#currentTimeMillis()} indicating when + * the current pending update was first available. -1 if no pending update is available. + */ + public void onSystemUpdatePending(Context context, Intent intent, long receivedTime) { + } + + /** * Intercept standard device administrator broadcasts. Implementations * should not override this method; it is better to implement the * convenience callbacks for each action. @@ -530,6 +560,9 @@ public class DeviceAdminReceiver extends BroadcastReceiver { onLockTaskModeExiting(context, intent); } else if (ACTION_READY_FOR_USER_INITIALIZATION.equals(action)) { onReadyForUserInitialization(context, intent); + } else if (ACTION_NOTIFY_PENDING_SYSTEM_UPDATE.equals(action)) { + long receivedTime = intent.getLongExtra(EXTRA_SYSTEM_UPDATE_RECEIVED_TIME, -1); + onSystemUpdatePending(context, intent, receivedTime); } } } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 9e2da61..47133d4 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2651,14 +2651,12 @@ public class DevicePolicyManager { /** * @hide - * Sets the given package as the device owner. The package must already be installed and there - * shouldn't be an existing device owner registered, for this call to succeed. Also, this - * method must be called before the device is provisioned. + * Sets the given package as the device owner. + * Same as {@link #setDeviceOwner(String, String)} but without setting a device owner name. * @param packageName the package name of the application to be registered as the device owner. * @return whether the package was successfully registered as the device owner. * @throws IllegalArgumentException if the package name is null or invalid - * @throws IllegalStateException if a device owner is already registered or the device has - * already been provisioned. + * @throws IllegalStateException If the preconditions mentioned are not met. */ public boolean setDeviceOwner(String packageName) throws IllegalArgumentException, IllegalStateException { @@ -2667,15 +2665,17 @@ public class DevicePolicyManager { /** * @hide - * Sets the given package as the device owner. The package must already be installed and there - * shouldn't be an existing device owner registered, for this call to succeed. Also, this - * method must be called before the device is provisioned. + * Sets the given package as the device owner. The package must already be installed. There + * must not already be a device owner. + * Only apps with the MANAGE_PROFILE_AND_DEVICE_OWNERS permission and the shell uid can call + * this method. + * Calling this after the setup phase of the primary user has completed is allowed only if + * the caller is the shell uid, and there are no additional users and no accounts. * @param packageName the package name of the application to be registered as the device owner. * @param ownerName the human readable name of the institution that owns this device. * @return whether the package was successfully registered as the device owner. * @throws IllegalArgumentException if the package name is null or invalid - * @throws IllegalStateException if a device owner is already registered or the device has - * already been provisioned. + * @throws IllegalStateException If the preconditions mentioned are not met. */ public boolean setDeviceOwner(String packageName, String ownerName) throws IllegalArgumentException, IllegalStateException { @@ -2961,14 +2961,18 @@ public class DevicePolicyManager { /** * @hide * Sets the given component as the profile owner of the given user profile. The package must - * already be installed and there shouldn't be an existing profile owner registered for this - * user. Only the system can call this API if the user has already completed setup. + * already be installed. There must not already be a profile owner for this user. + * Only apps with the MANAGE_PROFILE_AND_DEVICE_OWNERS permission and the shell uid can call + * this method. + * Calling this after the setup phase of the specified user has completed is allowed only if: + * - the caller is SYSTEM_UID. + * - or the caller is the shell uid, and there are no accounts on the specified user. * @param admin the component name to be registered as profile owner. * @param ownerName the human readable name of the organisation associated with this DPM. * @param userHandle the userId to set the profile owner for. * @return whether the component was successfully registered as the profile owner. - * @throws IllegalArgumentException if admin is null, the package isn't installed, or - * the user has already been set up. + * @throws IllegalArgumentException if admin is null, the package isn't installed, or the + * preconditions mentioned are not met. */ public boolean setProfileOwner(ComponentName admin, String ownerName, int userHandle) throws IllegalArgumentException { @@ -4301,4 +4305,24 @@ public class DevicePolicyManager { Log.w(TAG, "Failed talking with device policy service", re); } } + + /** + * Callable by the system update service to notify device owners about pending updates. + * The caller must hold {@link android.Manifest.permission#NOTIFY_PENDING_SYSTEM_UPDATE} + * permission. + * + * @param updateReceivedTime The time as given by {@link System#currentTimeMillis()} indicating + * when the current pending update was first available. -1 if no update is available. + * @hide + */ + @SystemApi + public void notifyPendingSystemUpdate(long updateReceivedTime) { + if (mService != null) { + try { + mService.notifyPendingSystemUpdate(updateReceivedTime); + } catch (RemoteException re) { + Log.w(TAG, "Could not notify device owner about pending system update", re); + } + } + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 1f7498e..087fc88 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -225,4 +225,6 @@ interface IDevicePolicyManager { boolean setKeyguardEnabledState(in ComponentName admin, boolean enabled); void setStatusBarEnabledState(in ComponentName who, boolean enabled); boolean getDoNotAskCredentialsOnBoot(); + + void notifyPendingSystemUpdate(in long updateReceivedTime); } diff --git a/core/java/android/app/backup/BlobBackupHelper.java b/core/java/android/app/backup/BlobBackupHelper.java index 8e4002d..cdc62dc 100644 --- a/core/java/android/app/backup/BlobBackupHelper.java +++ b/core/java/android/app/backup/BlobBackupHelper.java @@ -133,7 +133,7 @@ public abstract class BlobBackupHelper implements BackupHelper { out.writeInt(mCurrentBlobVersion); - final int N = state.size(); + final int N = (state != null) ? state.size() : 0; out.writeInt(N); for (int i = 0; i < N; i++) { out.writeUTF(state.keyAt(i)); diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl index 4ed1489..23659e3 100644 --- a/core/java/android/app/usage/IUsageStatsManager.aidl +++ b/core/java/android/app/usage/IUsageStatsManager.aidl @@ -30,4 +30,6 @@ interface IUsageStatsManager { ParceledListSlice queryConfigurationStats(int bucketType, long beginTime, long endTime, String callingPackage); UsageEvents queryEvents(long beginTime, long endTime, String callingPackage); + void setAppIdle(String packageName, boolean idle, int userId); + boolean isAppIdle(String packageName, int userId); } diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index bc6099a..8a01d66 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -19,6 +19,7 @@ package android.app.usage; import android.content.Context; import android.content.pm.ParceledListSlice; import android.os.RemoteException; +import android.os.UserHandle; import android.util.ArrayMap; import java.util.Collections; @@ -217,4 +218,20 @@ public final class UsageStatsManager { } return aggregatedStats; } + + /** + * Returns whether the specified app is currently considered idle. This will be true if the + * app hasn't been used directly or indirectly for a period of time defined by the system. This + * could be of the order of several hours or days. + * @param packageName The package name of the app to query + * @return whether the app is currently considered idle + */ + public boolean isAppIdle(String packageName) { + try { + return mService.isAppIdle(packageName, UserHandle.myUserId()); + } catch (RemoteException e) { + // fall through and return default + } + return false; + } } diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 2418e82..79e560f 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -792,7 +792,6 @@ public final class BluetoothAdapter { // mService is null, handle that case } } catch (RemoteException e) {Log.e(TAG, "", e);} - if (DBG) Log.d(TAG, "" + hashCode() + ": getState() : mService = null. Returning STATE_OFF"); return STATE_OFF; } diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index 393cf8e..fd65d56 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -26,6 +26,7 @@ import android.content.pm.ProviderInfo; import android.content.res.AssetFileDescriptor; import android.content.res.Configuration; import android.database.Cursor; +import android.database.MatrixCursor; import android.database.SQLException; import android.net.Uri; import android.os.AsyncTask; @@ -204,8 +205,13 @@ public abstract class ContentProvider implements ComponentCallbacks2 { validateIncomingUri(uri); uri = getUriWithoutUserId(uri); if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) { - return rejectQuery(uri, projection, selection, selectionArgs, sortOrder, - CancellationSignal.fromTransport(cancellationSignal)); + // The caller has no access to the data, so return an empty cursor with + // the columns in the requested order. The caller may ask for an invalid + // column and we would not catch that but this is not a problem in practice. + // We do not call ContentProvider#query with a modified where clause since + // the implementation is not guaranteed to be backed by a SQL database, hence + // it may not handle properly the tautology where clause we would have created. + return new MatrixCursor(projection, 0); } final String original = setCallingPackage(callingPkg); try { @@ -817,31 +823,6 @@ public abstract class ContentProvider implements ComponentCallbacks2 { } /** - * @hide - * Implementation when a caller has performed a query on the content - * provider, but that call has been rejected for the operation given - * to {@link #setAppOps(int, int)}. The default implementation - * rewrites the <var>selection</var> argument to include a condition - * that is never true (so will always result in an empty cursor) - * and calls through to {@link #query(android.net.Uri, String[], String, String[], - * String, android.os.CancellationSignal)} with that. - */ - public Cursor rejectQuery(Uri uri, String[] projection, - String selection, String[] selectionArgs, String sortOrder, - CancellationSignal cancellationSignal) { - // The read is not allowed... to fake it out, we replace the given - // selection statement with a dummy one that will always be false. - // This way we will get a cursor back that has the correct structure - // but contains no rows. - if (selection == null || selection.isEmpty()) { - selection = "'A' = 'B'"; - } else { - selection = "'A' = 'B' AND (" + selection + ")"; - } - return query(uri, projection, selection, selectionArgs, sortOrder, cancellationSignal); - } - - /** * Implement this to handle query requests from clients. * This method can be called from multiple threads, as described in * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 3bf3f85..5eacce3 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3098,7 +3098,6 @@ public abstract class Context { * {@link android.media.midi.MidiManager} for accessing the MIDI service. * * @see #getSystemService - * @hide */ public static final String MIDI_SERVICE = "midi"; diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index e2701ee..6c32873 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -339,8 +339,14 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * (e.g., HTTP rather than HTTPS; WebSockets rather than WebSockets Secure; XMPP, IMAP, STMP * without STARTTLS or TLS). If {@code false}, the app declares that it does not intend to use * cleartext network traffic, in which case platform components (e.g., HTTP stacks, - * {@code WebView}, {@code DownloadManager}, {@code MediaPlayer}) will refuse app's requests to - * use cleartext traffic. Third-party libraries are encouraged to honor this flag as well. + * {@code DownloadManager}, {@code MediaPlayer}) will refuse app's requests to use cleartext + * traffic. Third-party libraries are encouraged to honor this flag as well. + * + * <p>NOTE: {@code WebView} does not honor this flag. + * + * <p>This flag comes from + * {@link android.R.styleable#AndroidManifestApplication_usesCleartextTraffic + * android:usesCleartextTraffic} of the <application> tag. */ public static final int FLAG_USES_CLEARTEXT_TRAFFIC = 1<<27; diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 447c668..ae59bfc 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -50,7 +50,6 @@ import android.net.Uri; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.content.IntentSender; -import com.android.internal.os.IResultReceiver; /** * See {@link PackageManager} for documentation on most of the APIs @@ -431,8 +430,13 @@ interface IPackageManager { PackageCleanItem nextPackageToClean(in PackageCleanItem lastPackage); - void movePackage(String packageName, IPackageMoveObserver observer, int flags); - void movePackageAndData(String packageName, String volumeUuid, IPackageMoveObserver observer); + int getMoveStatus(int moveId); + + void registerMoveCallback(in IPackageMoveObserver callback); + void unregisterMoveCallback(in IPackageMoveObserver callback); + + int movePackage(in String packageName, in String volumeUuid); + int movePrimaryStorage(in String volumeUuid); boolean addPermissionAsync(in PermissionInfo info); diff --git a/core/java/android/content/pm/IPackageMoveObserver.aidl b/core/java/android/content/pm/IPackageMoveObserver.aidl index baa1595..50ab3b5 100644 --- a/core/java/android/content/pm/IPackageMoveObserver.aidl +++ b/core/java/android/content/pm/IPackageMoveObserver.aidl @@ -22,6 +22,6 @@ package android.content.pm; * @hide */ oneway interface IPackageMoveObserver { - void packageMoved(in String packageName, int returnCode); + void onStarted(int moveId, String title); + void onStatusChanged(int moveId, int status, long estMillis); } - diff --git a/core/java/android/content/pm/PackageCleanItem.java b/core/java/android/content/pm/PackageCleanItem.java index b1896aa..e1656d6 100644 --- a/core/java/android/content/pm/PackageCleanItem.java +++ b/core/java/android/content/pm/PackageCleanItem.java @@ -20,7 +20,7 @@ import android.os.Parcel; import android.os.Parcelable; /** @hide */ -public class PackageCleanItem { +public class PackageCleanItem implements Parcelable { public final int userId; public final String packageName; public final boolean andCode; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index a0cec50..e1c271d 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -42,6 +42,7 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.Environment; +import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; import android.os.storage.VolumeInfo; @@ -875,7 +876,8 @@ public abstract class PackageManager { * * @hide */ - public static final int MOVE_SUCCEEDED = 1; + public static final int MOVE_SUCCEEDED = -100; + /** * Error code that is passed to the {@link IPackageMoveObserver} by * {@link #movePackage(android.net.Uri, IPackageMoveObserver)} @@ -941,6 +943,7 @@ public abstract class PackageManager { * been installed on external media. * @hide */ + @Deprecated public static final int MOVE_INTERNAL = 0x00000001; /** @@ -948,8 +951,12 @@ public abstract class PackageManager { * the package should be moved to external media. * @hide */ + @Deprecated public static final int MOVE_EXTERNAL_MEDIA = 0x00000002; + /** {@hide} */ + public static final String EXTRA_MOVE_ID = "android.content.pm.extra.MOVE_ID"; + /** * Usable by the required verifier as the {@code verificationCode} argument * for {@link PackageManager#verifyPendingInstall} to indicate that it will @@ -1574,6 +1581,21 @@ public abstract class PackageManager { /** * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: This is a device dedicated to showing UI + * on a vehicle headunit. A headunit here is defined to be inside a + * vehicle that may or may not be moving. A headunit uses either a + * primary display in the center console and/or additional displays in + * the instrument cluster or elsewhere in the vehicle. Headunit display(s) + * have limited size and resolution. The user will likely be focused on + * driving so limiting driver distraction is a primary concern. User input + * can be a variety of hard buttons, touch, rotary controllers and even mouse- + * like interfaces. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: This is a device dedicated to showing UI * on a television. Television here is defined to be a typical living * room television experience: displayed on a big screen, where the user * is sitting far away from it, and the dominant form of input will be @@ -4168,17 +4190,42 @@ public abstract class PackageManager { * @hide */ @Deprecated - public abstract void movePackage(String packageName, IPackageMoveObserver observer, int flags); + public void movePackage(String packageName, IPackageMoveObserver observer, int flags) { + throw new UnsupportedOperationException(); + } /** {@hide} */ - public abstract void movePackageAndData(String packageName, String volumeUuid, - IPackageMoveObserver observer); + public static boolean isMoveStatusFinished(int status) { + return (status < 0 || status > 100); + } /** {@hide} */ - public abstract @Nullable VolumeInfo getApplicationCurrentVolume(ApplicationInfo app); + public static abstract class MoveCallback { + public abstract void onStarted(int moveId, String title); + public abstract void onStatusChanged(int moveId, int status, long estMillis); + } + + /** {@hide} */ + public abstract int getMoveStatus(int moveId); + + /** {@hide} */ + public abstract void registerMoveCallback(MoveCallback callback, Handler handler); + /** {@hide} */ + public abstract void unregisterMoveCallback(MoveCallback callback); /** {@hide} */ - public abstract @NonNull List<VolumeInfo> getApplicationCandidateVolumes(ApplicationInfo app); + public abstract int movePackage(String packageName, VolumeInfo vol); + /** {@hide} */ + public abstract @Nullable VolumeInfo getPackageCurrentVolume(ApplicationInfo app); + /** {@hide} */ + public abstract @NonNull List<VolumeInfo> getPackageCandidateVolumes(ApplicationInfo app); + + /** {@hide} */ + public abstract int movePrimaryStorage(VolumeInfo vol); + /** {@hide} */ + public abstract @Nullable VolumeInfo getPrimaryStorageCurrentVolume(); + /** {@hide} */ + public abstract @NonNull List<VolumeInfo> getPrimaryStorageCandidateVolumes(); /** * Returns the device identity that verifiers can use to associate their scheme to a particular diff --git a/core/java/android/content/pm/ParceledListSlice.java b/core/java/android/content/pm/ParceledListSlice.java index 335a45e..e5c2203 100644 --- a/core/java/android/content/pm/ParceledListSlice.java +++ b/core/java/android/content/pm/ParceledListSlice.java @@ -55,6 +55,7 @@ public class ParceledListSlice<T extends Parcelable> implements Parcelable { mList = list; } + @SuppressWarnings("unchecked") private ParceledListSlice(Parcel p, ClassLoader loader) { final int N = p.readInt(); mList = new ArrayList<T>(N); @@ -63,7 +64,7 @@ public class ParceledListSlice<T extends Parcelable> implements Parcelable { return; } - Parcelable.Creator<T> creator = p.readParcelableCreator(loader); + Parcelable.Creator<?> creator = p.readParcelableCreator(loader); Class<?> listElementClass = null; int i = 0; diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java index 24a7d33..3cda39a 100644 --- a/core/java/android/database/sqlite/SQLiteConnection.java +++ b/core/java/android/database/sqlite/SQLiteConnection.java @@ -91,8 +91,6 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen private static final String[] EMPTY_STRING_ARRAY = new String[0]; private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; - private static final Pattern TRIM_SQL_PATTERN = Pattern.compile("[\\s]*\\n+[\\s]*"); - private final CloseGuard mCloseGuard = CloseGuard.get(); private final SQLiteConnectionPool mPool; @@ -1203,7 +1201,11 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } private static String trimSqlForDisplay(String sql) { - return TRIM_SQL_PATTERN.matcher(sql).replaceAll(" "); + // Note: Creating and caching a regular expression is expensive at preload-time + // and stops compile-time initialization. This pattern is only used when + // dumping the connection, which is a rare (mainly error) case. So: + // DO NOT CACHE. + return sql.replaceAll("[\\s]*\\n+[\\s]*", " "); } /** @@ -1437,9 +1439,6 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } private static final class Operation { - private static final SimpleDateFormat sDateFormat = - new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); - public long mStartTime; public long mEndTime; public String mKind; @@ -1494,7 +1493,11 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } private String getFormattedStartTime() { - return sDateFormat.format(new Date(mStartTime)); + // Note: SimpleDateFormat is not thread-safe, cannot be compile-time created, and is + // relatively expensive to create during preloading. This method is only used + // when dumping a connection, which is a rare (mainly error) case. So: + // DO NOT CACHE. + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(mStartTime)); } } } diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java index 31e6e25..0cf8df1 100644 --- a/core/java/android/hardware/camera2/CameraCaptureSession.java +++ b/core/java/android/hardware/camera2/CameraCaptureSession.java @@ -65,6 +65,12 @@ import java.util.List; public abstract class CameraCaptureSession implements AutoCloseable { /** + * Used to identify invalid session ID. + * @hide + */ + public static final int SESSION_ID_NONE = -1; + + /** * Get the camera device that this session is created for. */ public abstract CameraDevice getDevice(); @@ -168,10 +174,11 @@ public abstract class CameraCaptureSession implements AutoCloseable { * @throws IllegalArgumentException if the request targets no Surfaces or Surfaces that are not * configured as outputs for this session; or a reprocess * capture request is submitted in a non-reprocessible capture - * session; or the capture targets a Surface in the middle - * of being {@link #prepare prepared}; or the handler is - * null, the listener is not null, and the calling thread has - * no looper. + * session; or the reprocess capture request was created with + * a {@link TotalCaptureResult} from a different session; or + * the capture targets a Surface in the middle of being + * {@link #prepare prepared}; or the handler is null, the + * listener is not null, and the calling thread has no looper. * * @see #captureBurst * @see #setRepeatingRequest @@ -226,7 +233,9 @@ public abstract class CameraCaptureSession implements AutoCloseable { * capture request is submitted in a non-reprocessible capture * session; or the list of requests contains both requests to * capture images from the camera and reprocess capture - * requests; or one of the captures targets a Surface in the + * requests; or one of the reprocess capture requests was + * created with a {@link TotalCaptureResult} from a different + * session; or one of the captures targets a Surface in the * middle of being {@link #prepare prepared}; or if the handler * is null, the listener is not null, and the calling thread * has no looper. diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 35727e8..19d17b1 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -158,6 +158,9 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> private final HashSet<Surface> mSurfaceSet; private final CameraMetadataNative mSettings; private boolean mIsReprocess; + // Each reprocess request must be tied to a reprocessible session ID. + // Valid only for reprocess requests (mIsReprocess == true). + private int mReprocessibleSessionId; private Object mUserTag; @@ -170,6 +173,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> mSettings = new CameraMetadataNative(); mSurfaceSet = new HashSet<Surface>(); mIsReprocess = false; + mReprocessibleSessionId = CameraCaptureSession.SESSION_ID_NONE; } /** @@ -182,6 +186,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> mSettings = new CameraMetadataNative(source.mSettings); mSurfaceSet = (HashSet<Surface>) source.mSurfaceSet.clone(); mIsReprocess = source.mIsReprocess; + mReprocessibleSessionId = source.mReprocessibleSessionId; mUserTag = source.mUserTag; } @@ -189,11 +194,36 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * Take ownership of passed-in settings. * * Used by the Builder to create a mutable CaptureRequest. + * + * @param settings Settings for this capture request. + * @param isReprocess Indicates whether to create a reprocess capture request. {@code true} + * to create a reprocess capture request. {@code false} to create a regular + * capture request. + * @param reprocessibleSessionId The ID of the camera capture session this capture is created + * for. This is used to validate if the application submits a + * reprocess capture request to the same session where + * the {@link TotalCaptureResult}, used to create the reprocess + * capture, came from. + * + * @throws IllegalArgumentException If creating a reprocess capture request with an invalid + * reprocessibleSessionId. + * + * @see CameraDevice#createReprocessCaptureRequest */ - private CaptureRequest(CameraMetadataNative settings, boolean isReprocess) { + private CaptureRequest(CameraMetadataNative settings, boolean isReprocess, + int reprocessibleSessionId) { mSettings = CameraMetadataNative.move(settings); mSurfaceSet = new HashSet<Surface>(); mIsReprocess = isReprocess; + if (isReprocess) { + if (reprocessibleSessionId == CameraCaptureSession.SESSION_ID_NONE) { + throw new IllegalArgumentException("Create a reprocess capture request with an " + + "invalid session ID: " + reprocessibleSessionId); + } + mReprocessibleSessionId = reprocessibleSessionId; + } else { + mReprocessibleSessionId = CameraCaptureSession.SESSION_ID_NONE; + } } /** @@ -277,6 +307,23 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> } /** + * Get the reprocessible session ID this reprocess capture request is associated with. + * + * @return the reprocessible session ID this reprocess capture request is associated with + * + * @throws IllegalStateException if this capture request is not a reprocess capture request. + * @hide + */ + public int getReprocessibleSessionId() { + if (mIsReprocess == false || + mReprocessibleSessionId == CameraCaptureSession.SESSION_ID_NONE) { + throw new IllegalStateException("Getting the reprocessible session ID for a "+ + "non-reprocess capture request is illegal."); + } + return mReprocessibleSessionId; + } + + /** * Determine whether this CaptureRequest is equal to another CaptureRequest. * * <p>A request is considered equal to another is if it's set of key/values is equal, it's @@ -298,7 +345,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> && Objects.equals(mUserTag, other.mUserTag) && mSurfaceSet.equals(other.mSurfaceSet) && mSettings.equals(other.mSettings) - && mIsReprocess == other.mIsReprocess; + && mIsReprocess == other.mIsReprocess + && mReprocessibleSessionId == other.mReprocessibleSessionId; } @Override @@ -347,6 +395,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> } mIsReprocess = (in.readInt() == 0) ? false : true; + mReprocessibleSessionId = CameraCaptureSession.SESSION_ID_NONE; } @Override @@ -397,10 +446,23 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * Initialize the builder using the template; the request takes * ownership of the template. * + * @param template Template settings for this capture request. + * @param reprocess Indicates whether to create a reprocess capture request. {@code true} + * to create a reprocess capture request. {@code false} to create a regular + * capture request. + * @param reprocessibleSessionId The ID of the camera capture session this capture is + * created for. This is used to validate if the application + * submits a reprocess capture request to the same session + * where the {@link TotalCaptureResult}, used to create the + * reprocess capture, came from. + * + * @throws IllegalArgumentException If creating a reprocess capture request with an invalid + * reprocessibleSessionId. * @hide */ - public Builder(CameraMetadataNative template, boolean reprocess) { - mRequest = new CaptureRequest(template, reprocess); + public Builder(CameraMetadataNative template, boolean reprocess, + int reprocessibleSessionId) { + mRequest = new CaptureRequest(template, reprocess, reprocessibleSessionId); } /** diff --git a/core/java/android/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java index 6f7dd78..fb3c098 100644 --- a/core/java/android/hardware/camera2/TotalCaptureResult.java +++ b/core/java/android/hardware/camera2/TotalCaptureResult.java @@ -50,6 +50,7 @@ import java.util.List; public final class TotalCaptureResult extends CaptureResult { private final List<CaptureResult> mPartialResults; + private final int mSessionId; /** * Takes ownership of the passed-in camera metadata and the partial results @@ -58,7 +59,7 @@ public final class TotalCaptureResult extends CaptureResult { * @hide */ public TotalCaptureResult(CameraMetadataNative results, CaptureRequest parent, - CaptureResultExtras extras, List<CaptureResult> partials) { + CaptureResultExtras extras, List<CaptureResult> partials, int sessionId) { super(results, parent, extras); if (partials == null) { @@ -66,6 +67,8 @@ public final class TotalCaptureResult extends CaptureResult { } else { mPartialResults = partials; } + + mSessionId = sessionId; } /** @@ -78,6 +81,7 @@ public final class TotalCaptureResult extends CaptureResult { super(results, sequenceId); mPartialResults = new ArrayList<>(); + mSessionId = CameraCaptureSession.SESSION_ID_NONE; } /** @@ -95,4 +99,14 @@ public final class TotalCaptureResult extends CaptureResult { public List<CaptureResult> getPartialResults() { return Collections.unmodifiableList(mPartialResults); } + + /** + * Get the ID of the session where the capture request of this result was submitted. + * + * @return The session ID + * @hide + */ + public int getSessionId() { + return mSessionId; + } } diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java index c74204d..3c19529 100644 --- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java @@ -156,9 +156,10 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { } else if (request.isReprocess() && !isReprocessible()) { throw new IllegalArgumentException("this capture session cannot handle reprocess " + "requests"); + } else if (request.isReprocess() && request.getReprocessibleSessionId() != mId) { + throw new IllegalArgumentException("capture request was created for another session"); } - checkNotClosed(); handler = checkHandler(handler, callback); @@ -185,12 +186,17 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { if (reprocess && !isReprocessible()) { throw new IllegalArgumentException("this capture session cannot handle reprocess " + "requests"); + } else if (reprocess && requests.get(0).getReprocessibleSessionId() != mId) { + throw new IllegalArgumentException("capture request was created for another session"); } for (int i = 1; i < requests.size(); i++) { if (requests.get(i).isReprocess() != reprocess) { throw new IllegalArgumentException("cannot mix regular and reprocess capture " + " requests"); + } else if (reprocess && requests.get(i).getReprocessibleSessionId() != mId) { + throw new IllegalArgumentException("capture request was created for another " + + "session"); } } diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 1e680dfd..ff4ad79 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -585,8 +585,8 @@ public class CameraDeviceImpl extends CameraDevice { return null; } - CaptureRequest.Builder builder = - new CaptureRequest.Builder(templatedRequest, /*reprocess*/false); + CaptureRequest.Builder builder = new CaptureRequest.Builder( + templatedRequest, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE); return builder; } @@ -601,7 +601,8 @@ public class CameraDeviceImpl extends CameraDevice { CameraMetadataNative resultMetadata = new CameraMetadataNative(inputResult.getNativeCopy()); - return new CaptureRequest.Builder(resultMetadata, /*reprocess*/true); + return new CaptureRequest.Builder(resultMetadata, /*reprocess*/true, + inputResult.getSessionId()); } } @@ -763,7 +764,7 @@ public class CameraDeviceImpl extends CameraDevice { if (callback != null) { mCaptureCallbackMap.put(requestId, new CaptureCallbackHolder(callback, - requestList, handler, repeating)); + requestList, handler, repeating, mNextSessionId - 1)); } else { if (DEBUG) { Log.d(TAG, "Listen for request " + requestId + " is null"); @@ -1095,9 +1096,10 @@ public class CameraDeviceImpl extends CameraDevice { private final CaptureCallback mCallback; private final List<CaptureRequest> mRequestList; private final Handler mHandler; + private final int mSessionId; CaptureCallbackHolder(CaptureCallback callback, List<CaptureRequest> requestList, - Handler handler, boolean repeating) { + Handler handler, boolean repeating, int sessionId) { if (callback == null || handler == null) { throw new UnsupportedOperationException( "Must have a valid handler and a valid callback"); @@ -1106,6 +1108,7 @@ public class CameraDeviceImpl extends CameraDevice { mHandler = handler; mRequestList = new ArrayList<CaptureRequest>(requestList); mCallback = callback; + mSessionId = sessionId; } public boolean isRepeating() { @@ -1140,6 +1143,10 @@ public class CameraDeviceImpl extends CameraDevice { return mHandler; } + public int getSessionId() { + return mSessionId; + } + } /** @@ -1643,8 +1650,8 @@ public class CameraDeviceImpl extends CameraDevice { List<CaptureResult> partialResults = mFrameNumberTracker.popPartialResults(frameNumber); - final TotalCaptureResult resultAsCapture = - new TotalCaptureResult(result, request, resultExtras, partialResults); + final TotalCaptureResult resultAsCapture = new TotalCaptureResult(result, + request, resultExtras, partialResults, holder.getSessionId()); // Final capture result resultDispatch = new Runnable() { @@ -1665,7 +1672,8 @@ public class CameraDeviceImpl extends CameraDevice { holder.getHandler().post(resultDispatch); // Collect the partials for a total result; or mark the frame as totally completed - mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult, isReprocess); + mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult, + isReprocess); // Fire onCaptureSequenceCompleted if (!isPartialResult) { diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 0d7b261..2257b0a 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -587,6 +587,25 @@ public class FingerprintManager { return false; } + /** + * Retrieves the authenticator token for binding keys to the lifecycle + * of the current set of fingerprints. Used only by internal clients. + * + * @hide + */ + public long getAuthenticatorId() { + if (mService != null) { + try { + return mService.getAuthenticatorId(); + } catch (RemoteException e) { + Log.v(TAG, "Remote exception in getAuthenticatorId(): ", e); + } + } else { + Log.w(TAG, "getAuthenticatorId(): Service not connected!"); + } + return 0; + } + private Handler mHandler = new Handler() { public void handleMessage(android.os.Message msg) { switch(msg.what) { @@ -792,4 +811,5 @@ public class FingerprintManager { } }; -}
\ No newline at end of file +} + diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index 51a0e4c..c5ec08c 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -63,4 +63,6 @@ interface IFingerprintService { // Gets the unique device id for hardware enumerated at i // long getHardwareDevice(int i); + // Gets the authenticator ID for fingerprint + long getAuthenticatorId(); } diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 481fc2f..1b57055 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -1440,7 +1440,8 @@ public class InputMethodService extends AbstractInputMethodService { void showWindowInner(boolean showInput) { boolean doShowInput = false; - boolean wasVisible = mWindowVisible; + final int previousImeWindowStatus = + (mWindowVisible ? IME_ACTIVE : 0) | (isInputViewShown() ? IME_VISIBLE : 0); mWindowVisible = true; if (!mShowInputRequested) { if (mInputStarted) { @@ -1485,9 +1486,12 @@ public class InputMethodService extends AbstractInputMethodService { startExtractingText(false); } - if (!wasVisible) { + final int nextImeWindowStatus = IME_ACTIVE | (isInputViewShown() ? IME_VISIBLE : 0); + if (previousImeWindowStatus != nextImeWindowStatus) { + mImm.setImeWindowStatus(mToken, nextImeWindowStatus, mBackDisposition); + } + if ((previousImeWindowStatus & IME_ACTIVE) == 0) { if (DEBUG) Log.v(TAG, "showWindow: showing!"); - mImm.setImeWindowStatus(mToken, IME_ACTIVE, mBackDisposition); onWindowShown(); mWindow.show(); } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 3e9b3d6..63f48cf 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -305,6 +305,9 @@ public class ConnectivityManager { * same network interface as {@link #TYPE_MOBILE} or it may use a different * one. This is used by applications needing to talk to the carrier's * Multimedia Messaging Service servers. + * + * @deprecated Applications should instead use {@link #requestNetwork} to request a network that + * provides the {@link NetworkCapabilities#NET_CAPABILITY_MMS} capability. */ public static final int TYPE_MOBILE_MMS = 2; /** @@ -312,6 +315,9 @@ public class ConnectivityManager { * same network interface as {@link #TYPE_MOBILE} or it may use a different * one. This is used by applications needing to talk to the carrier's * Secure User Plane Location servers for help locating the device. + * + * @deprecated Applications should instead use {@link #requestNetwork} to request a network that + * provides the {@link NetworkCapabilities#NET_CAPABILITY_SUPL} capability. */ public static final int TYPE_MOBILE_SUPL = 3; /** @@ -324,9 +330,10 @@ public class ConnectivityManager { /** * A High Priority Mobile data connection. This network type uses the * same network interface as {@link #TYPE_MOBILE} but the routing setup - * is different. Only requesting processes will have access to the - * Mobile DNS servers and only IP's explicitly requested via {@link #requestRouteToHost} - * will route over this interface if no default route exists. + * is different. + * + * @deprecated Applications should instead use {@link #requestNetwork} to request a network that + * uses the {@link NetworkCapabilities#TRANSPORT_CELLULAR} transport. */ public static final int TYPE_MOBILE_HIPRI = 5; /** @@ -386,7 +393,7 @@ public class ConnectivityManager { */ public static final int TYPE_MOBILE_IA = 14; -/** + /** * Emergency PDN connection for emergency calls * {@hide} */ @@ -736,7 +743,7 @@ public class ConnectivityManager { } /** - * Returns an array of of {@link NetworkCapabilities} objects, representing + * Returns an array of {@link android.net.NetworkCapabilities} objects, representing * the Networks that applications run by the given user will use by default. * @hide */ @@ -826,11 +833,11 @@ public class ConnectivityManager { } /** - * Get the {@link NetworkCapabilities} for the given {@link Network}. This + * Get the {@link android.net.NetworkCapabilities} for the given {@link Network}. This * will return {@code null} if the network is unknown. * * @param network The {@link Network} object identifying the network in question. - * @return The {@link NetworkCapabilities} for the network, or {@code null}. + * @return The {@link android.net.NetworkCapabilities} for the network, or {@code null}. */ public NetworkCapabilities getNetworkCapabilities(Network network) { try { @@ -854,6 +861,7 @@ public class ConnectivityManager { * always indicates failure. * * @deprecated Deprecated in favor of the cleaner {@link #requestNetwork} api. + * @removed */ public int startUsingNetworkFeature(int networkType, String feature) { NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature); @@ -901,6 +909,7 @@ public class ConnectivityManager { * always indicates failure. * * @deprecated Deprecated in favor of the cleaner {@link #requestNetwork} api. + * @removed */ public int stopUsingNetworkFeature(int networkType, String feature) { NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature); @@ -1179,6 +1188,7 @@ public class ConnectivityManager { * * @deprecated Deprecated in favor of the {@link #requestNetwork}, * {@link #bindProcessToNetwork} and {@link Network#getSocketFactory} api. + * @removed */ public boolean requestRouteToHost(int networkType, int hostAddress) { return requestRouteToHostAddress(networkType, NetworkUtils.intToInetAddress(hostAddress)); @@ -1197,6 +1207,7 @@ public class ConnectivityManager { * @hide * @deprecated Deprecated in favor of the {@link #requestNetwork} and * {@link #bindProcessToNetwork} api. + * @removed */ public boolean requestRouteToHostAddress(int networkType, InetAddress hostAddress) { try { @@ -2057,7 +2068,7 @@ public class ConnectivityManager { * changes capabilities but still satisfies the stated need. * * @param network The {@link Network} whose capabilities have changed. - * @param networkCapabilities The new {@link NetworkCapabilities} for this network. + * @param networkCapabilities The new {@link android.net.NetworkCapabilities} for this network. */ public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {} @@ -2299,7 +2310,7 @@ public class ConnectivityManager { } /** - * Request a network to satisfy a set of {@link NetworkCapabilities}. + * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}. * * This {@link NetworkRequest} will live until released via * {@link #unregisterNetworkCallback} or the calling application exits. @@ -2318,7 +2329,7 @@ public class ConnectivityManager { } /** - * Request a network to satisfy a set of {@link NetworkCapabilities}, limited + * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited * by a timeout. * * This function behaves identically to the non-timedout version, but if a suitable @@ -2365,7 +2376,7 @@ public class ConnectivityManager { /** - * Request a network to satisfy a set of {@link NetworkCapabilities}. + * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}. * * This function behaves identically to the version that takes a NetworkCallback, but instead * of {@link NetworkCallback} a {@link PendingIntent} is used. This means @@ -2441,17 +2452,21 @@ public class ConnectivityManager { } /** - * Request connectivityservice to refresh network capabilities for the given - * {@link network}. This method returns true if the network is still active, false - * otherwise. Notice the method call assumes the caller has registered for - * listening NetworkCapabilities updates. + * Requests bandwidth update for a given {@link Network} and returns whether the update request + * is accepted by ConnectivityService. Once accepted, ConnectivityService will poll underlying + * network connection for updated bandwidth information. The caller will be notified via + * {@link ConnectivityManager.NetworkCallback} if there is an update. Notice that this + * method assumes that the caller has previously called {@link #registerNetworkCallback} to + * listen for network changes. * * @param network{@link Network} specifying which network you're interested. + * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid. + * * @hide */ - public boolean requestBwUpdate(Network network) { + public boolean requestBandwidthUpdate(Network network) { try { - return mService.requestBwUpdate(network); + return mService.requestBandwidthUpdate(network); } catch (RemoteException e) { return false; } @@ -2563,7 +2578,12 @@ public class ConnectivityManager { if (NetworkUtils.bindProcessToNetwork(netId)) { // Set HTTP proxy system properties to match network. // TODO: Deprecate this static method and replace it with a non-static version. - Proxy.setHttpProxySystemProperty(getInstance().getDefaultProxy()); + try { + Proxy.setHttpProxySystemProperty(getInstance().getDefaultProxy()); + } catch (SecurityException e) { + // The process doesn't have ACCESS_NETWORK_STATE, so we can't fetch the proxy. + Log.e(TAG, "Can't set proxy properties", e); + } // Must flush DNS cache as new network may have different DNS resolutions. InetAddress.clearDnsCache(); // Must flush socket pool as idle sockets will be bound to previous network and may diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index d6c0693..efc76b3 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -134,7 +134,7 @@ interface IConnectivityManager void registerNetworkFactory(in Messenger messenger, in String name); - boolean requestBwUpdate(in Network network); + boolean requestBandwidthUpdate(in Network network); void unregisterNetworkFactory(in Messenger messenger); diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java index f10e530..3d065e3 100644 --- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java +++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java @@ -17,6 +17,7 @@ package android.nfc.cardemulation; import android.content.ComponentName; +import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; @@ -28,6 +29,7 @@ import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; +import android.os.ResultReceiver; import android.util.AttributeSet; import android.util.Log; import android.util.Xml; @@ -88,12 +90,24 @@ public final class ApduServiceInfo implements Parcelable { * The uid of the package the service belongs to */ final int mUid; + + /** + * Whether this service has dynamic resources + */ + final boolean mHasDynamicResources; + + /** + * Settings Activity for this service + */ + final String mSettingsActivityName; + /** * @hide */ public ApduServiceInfo(ResolveInfo info, boolean onHost, String description, ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups, - boolean requiresUnlock, int bannerResource, int uid) { + boolean requiresUnlock, int bannerResource, int uid, boolean hasDynamicResources, + String settingsActivityName) { this.mService = info; this.mDescription = description; this.mStaticAidGroups = new HashMap<String, AidGroup>(); @@ -108,6 +122,8 @@ public final class ApduServiceInfo implements Parcelable { } this.mBannerResourceId = bannerResource; this.mUid = uid; + this.mHasDynamicResources = hasDynamicResources; + this.mSettingsActivityName = settingsActivityName; } public ApduServiceInfo(PackageManager pm, ResolveInfo info, boolean onHost) throws @@ -156,6 +172,10 @@ public final class ApduServiceInfo implements Parcelable { false); mBannerResourceId = sa.getResourceId( com.android.internal.R.styleable.HostApduService_apduServiceBanner, -1); + mHasDynamicResources = sa.getBoolean( + com.android.internal.R.styleable.HostApduService_dynamicResources, false); + mSettingsActivityName = sa.getString( + com.android.internal.R.styleable.HostApduService_settingsActivity); sa.recycle(); } else { TypedArray sa = res.obtainAttributes(attrs, @@ -166,6 +186,10 @@ public final class ApduServiceInfo implements Parcelable { mRequiresDeviceUnlock = false; mBannerResourceId = sa.getResourceId( com.android.internal.R.styleable.OffHostApduService_apduServiceBanner, -1); + mHasDynamicResources = sa.getBoolean( + com.android.internal.R.styleable.OffHostApduService_dynamicResources, false); + mSettingsActivityName = sa.getString( + com.android.internal.R.styleable.HostApduService_settingsActivity); sa.recycle(); } @@ -359,6 +383,15 @@ public final class ApduServiceInfo implements Parcelable { return mService.loadLabel(pm); } + public CharSequence loadAppLabel(PackageManager pm) { + try { + return pm.getApplicationLabel(pm.getApplicationInfo( + mService.resolvePackageName, PackageManager.GET_META_DATA)); + } catch (PackageManager.NameNotFoundException e) { + return null; + } + } + public Drawable loadIcon(PackageManager pm) { return mService.loadIcon(pm); } @@ -377,6 +410,11 @@ public final class ApduServiceInfo implements Parcelable { return null; } } + public boolean hasDynamicResources() { + return mHasDynamicResources; + } + + public String getSettingsActivityName() { return mSettingsActivityName; } @Override public String toString() { @@ -430,6 +468,8 @@ public final class ApduServiceInfo implements Parcelable { dest.writeInt(mRequiresDeviceUnlock ? 1 : 0); dest.writeInt(mBannerResourceId); dest.writeInt(mUid); + dest.writeInt(mHasDynamicResources ? 1 : 0); + dest.writeString(mSettingsActivityName); }; public static final Parcelable.Creator<ApduServiceInfo> CREATOR = @@ -452,8 +492,11 @@ public final class ApduServiceInfo implements Parcelable { boolean requiresUnlock = source.readInt() != 0; int bannerResource = source.readInt(); int uid = source.readInt(); + boolean dynamicResources = source.readInt() != 0; + String settingsActivityName = source.readString(); return new ApduServiceInfo(info, onHost, description, staticAidGroups, - dynamicAidGroups, requiresUnlock, bannerResource, uid); + dynamicAidGroups, requiresUnlock, bannerResource, uid, dynamicResources, + settingsActivityName); } @Override @@ -479,5 +522,6 @@ public final class ApduServiceInfo implements Parcelable { pw.println(" AID: " + aid); } } + pw.println(" Settings Activity: " + mSettingsActivityName); } } diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java index 64c2bc2..b94d4a6 100644 --- a/core/java/android/nfc/cardemulation/CardEmulation.java +++ b/core/java/android/nfc/cardemulation/CardEmulation.java @@ -90,6 +90,37 @@ public final class CardEmulation { public static final String CATEGORY_OTHER = "other"; /** + * Ordered broadcast that can be sent to your app to + * request a description and banner to be shown in + * Android Settings UI. + * When sent to you, this broadcast will contain the + * {@link #EXTRA_SERVICE_COMPONENT} extra to identify + * the service. + * + * Note that this broadcast will only be sent to your + * app, if a card emulation service in your app has requested + * its resources to be loaded dynamically. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_REQUEST_SERVICE_RESOURCES = + "android.nfc.cardemulation.action.REQUEST_SERVICE_RESOURCES"; + + /** + * The description of the service. Note that this must + * be localized by your app, as the String will be shown + * as is. + */ + public static final String EXTRA_DESCRIPTION = + "android.nfc.cardemulation.extra.DESCRIPTION"; + + /** + * The resource ID of the service banner to be shown + * for this service. + */ + public static final String EXTRA_BANNER_RES_ID = + "android.nfc.cardemulation.extra.BANNER_RES_ID"; + + /** * Return value for {@link #getSelectionModeForCategory(String)}. * * <p>In this mode, the user has set a default service for this diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index b302f95..931cd3e 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -16,11 +16,13 @@ package android.os; +import android.provider.DocumentsContract.Document; import android.system.ErrnoException; import android.system.Os; import android.text.TextUtils; import android.util.Log; import android.util.Slog; +import android.webkit.MimeTypeMap; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; @@ -34,6 +36,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.Comparator; +import java.util.Objects; import java.util.regex.Pattern; import java.util.zip.CRC32; import java.util.zip.CheckedInputStream; @@ -533,4 +536,76 @@ public class FileUtils { } return null; } + + /** + * Generates a unique file name under the given parent directory. If the display name doesn't + * have an extension that matches the requested MIME type, the default extension for that MIME + * type is appended. If a file already exists, the name is appended with a numerical value to + * make it unique. + * + * For example, the display name 'example' with 'text/plain' MIME might produce + * 'example.txt' or 'example (1).txt', etc. + * + * @throws FileNotFoundException + */ + public static File buildUniqueFile(File parent, String mimeType, String displayName) + throws FileNotFoundException { + String name; + String ext; + + if (Document.MIME_TYPE_DIR.equals(mimeType)) { + name = displayName; + ext = null; + } else { + String mimeTypeFromExt; + + // Extract requested extension from display name + final int lastDot = displayName.lastIndexOf('.'); + if (lastDot >= 0) { + name = displayName.substring(0, lastDot); + ext = displayName.substring(lastDot + 1); + mimeTypeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension( + ext.toLowerCase()); + } else { + name = displayName; + ext = null; + mimeTypeFromExt = null; + } + + if (mimeTypeFromExt == null) { + mimeTypeFromExt = "application/octet-stream"; + } + + final String extFromMimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType( + mimeType); + if (Objects.equals(mimeType, mimeTypeFromExt) || Objects.equals(ext, extFromMimeType)) { + // Extension maps back to requested MIME type; allow it + } else { + // No match; insist that create file matches requested MIME + name = displayName; + ext = extFromMimeType; + } + } + + File file = buildFile(parent, name, ext); + + // If conflicting file, try adding counter suffix + int n = 0; + while (file.exists()) { + if (n++ >= 32) { + throw new FileNotFoundException("Failed to create unique file"); + } + file = buildFile(parent, name + " (" + n + ")", ext); + } + + return file; + } + + private static File buildFile(File parent, String name, String ext) { + if (TextUtils.isEmpty(ext)) { + return new File(parent, name); + } else { + return new File(parent, name + "." + ext); + } + } } diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index af23f11..8c1f44f 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -34,6 +34,7 @@ import java.io.ObjectOutputStream; import java.io.ObjectStreamClass; import java.io.Serializable; import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -114,15 +115,15 @@ import java.util.Set; * later reading.</p> * * <p>There are also some methods that provide a more efficient way to work - * with Parcelables: {@link #writeTypedArray}, - * {@link #writeTypedList(List)}, - * {@link #readTypedArray} and {@link #readTypedList}. These methods + * with Parcelables: {@link #writeTypedObject}, {@link #writeTypedArray}, + * {@link #writeTypedList}, {@link #readTypedObject}, + * {@link #createTypedArray} and {@link #createTypedArrayList}. These methods * do not write the class information of the original object: instead, the * caller of the read function must know what type to expect and pass in the * appropriate {@link Parcelable.Creator Parcelable.Creator} instead to * properly construct the new object and read its data. (To more efficient - * write and read a single Parceable object, you can directly call - * {@link Parcelable#writeToParcel Parcelable.writeToParcel} and + * write and read a single Parceable object that is not null, you can directly + * call {@link Parcelable#writeToParcel Parcelable.writeToParcel} and * {@link Parcelable.Creator#createFromParcel Parcelable.Creator.createFromParcel} * yourself.)</p> * @@ -1222,6 +1223,24 @@ public final class Parcel { } /** + * Flatten the Parcelable object into the parcel. + * + * @param val The Parcelable object to be written. + * @param parcelableFlags Contextual flags as per + * {@link Parcelable#writeToParcel(Parcel, int) Parcelable.writeToParcel()}. + * + * @see #readTypedObject + */ + public final <T extends Parcelable> void writeTypedObject(T val, int parcelableFlags) { + if (val != null) { + writeInt(1); + val.writeToParcel(this, parcelableFlags); + } else { + writeInt(0); + } + } + + /** * Flatten a generic object in to a parcel. The given Object value may * currently be one of the following types: * @@ -1375,8 +1394,7 @@ public final class Parcel { writeString(null); return; } - String name = p.getClass().getName(); - writeString(name); + writeParcelableCreator(p); p.writeToParcel(this, parcelableFlags); } @@ -2138,6 +2156,25 @@ public final class Parcel { } /** + * Read and return a typed Parcelable object from a parcel. + * Returns null if the previous written object was null. + * The object <em>must</em> have previous been written via + * {@link #writeTypedObject} with the same object type. + * + * @return A newly created object of the type that was previously + * written. + * + * @see #writeTypedObject + */ + public final <T> T readTypedObject(Parcelable.Creator<T> c) { + if (readInt() != 0) { + return c.createFromParcel(this); + } else { + return null; + } + } + + /** * Write a heterogeneous array of Parcelable objects into the Parcel. * Each object in the array is written along with its class name, so * that the correct class can later be instantiated. As a result, this @@ -2277,78 +2314,94 @@ public final class Parcel { * @throws BadParcelableException Throws BadParcelableException if there * was an error trying to instantiate the Parcelable. */ + @SuppressWarnings("unchecked") public final <T extends Parcelable> T readParcelable(ClassLoader loader) { - Parcelable.Creator<T> creator = readParcelableCreator(loader); + Parcelable.Creator<?> creator = readParcelableCreator(loader); if (creator == null) { return null; } if (creator instanceof Parcelable.ClassLoaderCreator<?>) { - return ((Parcelable.ClassLoaderCreator<T>)creator).createFromParcel(this, loader); + Parcelable.ClassLoaderCreator<?> classLoaderCreator = + (Parcelable.ClassLoaderCreator<?>) creator; + return (T) classLoaderCreator.createFromParcel(this, loader); } - return creator.createFromParcel(this); + return (T) creator.createFromParcel(this); } /** @hide */ - public final <T extends Parcelable> T readCreator(Parcelable.Creator<T> creator, + @SuppressWarnings("unchecked") + public final <T extends Parcelable> T readCreator(Parcelable.Creator<?> creator, ClassLoader loader) { if (creator instanceof Parcelable.ClassLoaderCreator<?>) { - return ((Parcelable.ClassLoaderCreator<T>)creator).createFromParcel(this, loader); + Parcelable.ClassLoaderCreator<?> classLoaderCreator = + (Parcelable.ClassLoaderCreator<?>) creator; + return (T) classLoaderCreator.createFromParcel(this, loader); } - return creator.createFromParcel(this); + return (T) creator.createFromParcel(this); } /** @hide */ - public final <T extends Parcelable> Parcelable.Creator<T> readParcelableCreator( - ClassLoader loader) { + public final Parcelable.Creator<?> readParcelableCreator(ClassLoader loader) { String name = readString(); if (name == null) { return null; } - Parcelable.Creator<T> creator; + Parcelable.Creator<?> creator; synchronized (mCreators) { - HashMap<String,Parcelable.Creator> map = mCreators.get(loader); + HashMap<String,Parcelable.Creator<?>> map = mCreators.get(loader); if (map == null) { - map = new HashMap<String,Parcelable.Creator>(); + map = new HashMap<>(); mCreators.put(loader, map); } creator = map.get(name); if (creator == null) { try { - Class c = loader == null ? - Class.forName(name) : Class.forName(name, true, loader); - Field f = c.getField("CREATOR"); - creator = (Parcelable.Creator)f.get(null); + // If loader == null, explicitly emulate Class.forName(String) "caller + // classloader" behavior. + ClassLoader parcelableClassLoader = + (loader == null ? getClass().getClassLoader() : loader); + // Avoid initializing the Parcelable class until we know it implements + // Parcelable and has the necessary CREATOR field. http://b/1171613. + Class<?> parcelableClass = Class.forName(name, false /* initialize */, + parcelableClassLoader); + if (!Parcelable.class.isAssignableFrom(parcelableClass)) { + throw new BadParcelableException("Parcelable protocol requires that the " + + "class implements Parcelable"); + } + Field f = parcelableClass.getField("CREATOR"); + if ((f.getModifiers() & Modifier.STATIC) == 0) { + throw new BadParcelableException("Parcelable protocol requires " + + "the CREATOR object to be static on class " + name); + } + Class<?> creatorType = f.getType(); + if (!Parcelable.Creator.class.isAssignableFrom(creatorType)) { + // Fail before calling Field.get(), not after, to avoid initializing + // parcelableClass unnecessarily. + throw new BadParcelableException("Parcelable protocol requires a " + + "Parcelable.Creator object called " + + "CREATOR on class " + name); + } + creator = (Parcelable.Creator<?>) f.get(null); } catch (IllegalAccessException e) { - Log.e(TAG, "Illegal access when unmarshalling: " - + name, e); + Log.e(TAG, "Illegal access when unmarshalling: " + name, e); throw new BadParcelableException( "IllegalAccessException when unmarshalling: " + name); } catch (ClassNotFoundException e) { - Log.e(TAG, "Class not found when unmarshalling: " - + name, e); + Log.e(TAG, "Class not found when unmarshalling: " + name, e); throw new BadParcelableException( "ClassNotFoundException when unmarshalling: " + name); } - catch (ClassCastException e) { - throw new BadParcelableException("Parcelable protocol requires a " - + "Parcelable.Creator object called " - + " CREATOR on class " + name); - } catch (NoSuchFieldException e) { throw new BadParcelableException("Parcelable protocol requires a " - + "Parcelable.Creator object called " - + " CREATOR on class " + name); - } - catch (NullPointerException e) { - throw new BadParcelableException("Parcelable protocol requires " - + "the CREATOR object to be static on class " + name); + + "Parcelable.Creator object called " + + "CREATOR on class " + name); } if (creator == null) { throw new BadParcelableException("Parcelable protocol requires a " - + "Parcelable.Creator object called " - + " CREATOR on class " + name); + + "non-null Parcelable.Creator object called " + + "CREATOR on class " + name); } map.put(name, creator); @@ -2371,7 +2424,7 @@ public final class Parcel { } Parcelable[] p = new Parcelable[N]; for (int i = 0; i < N; i++) { - p[i] = (Parcelable) readParcelable(loader); + p[i] = readParcelable(loader); } return p; } @@ -2426,8 +2479,8 @@ public final class Parcel { // Cache of previously looked up CREATOR.createFromParcel() methods for // particular classes. Keys are the names of the classes, values are // Method objects. - private static final HashMap<ClassLoader,HashMap<String,Parcelable.Creator>> - mCreators = new HashMap<ClassLoader,HashMap<String,Parcelable.Creator>>(); + private static final HashMap<ClassLoader,HashMap<String,Parcelable.Creator<?>>> + mCreators = new HashMap<>(); /** @hide for internal use only. */ static protected final Parcel obtain(int obj) { diff --git a/core/java/android/os/Parcelable.java b/core/java/android/os/Parcelable.java index 594fbb2..448b591 100644 --- a/core/java/android/os/Parcelable.java +++ b/core/java/android/os/Parcelable.java @@ -19,9 +19,8 @@ package android.os; /** * Interface for classes whose instances can be written to * and restored from a {@link Parcel}. Classes implementing the Parcelable - * interface must also have a static field called <code>CREATOR</code>, which - * is an object implementing the {@link Parcelable.Creator Parcelable.Creator} - * interface. + * interface must also have a non-null static field called <code>CREATOR</code> + * of a type that implements the {@link Parcelable.Creator} interface. * * <p>A typical implementation of Parcelable is:</p> * diff --git a/core/java/android/os/storage/DiskInfo.java b/core/java/android/os/storage/DiskInfo.java index 9f38de8..9623695 100644 --- a/core/java/android/os/storage/DiskInfo.java +++ b/core/java/android/os/storage/DiskInfo.java @@ -36,7 +36,10 @@ import java.util.Objects; * @hide */ public class DiskInfo implements Parcelable { - public static final String EXTRA_DISK_ID = "android.os.storage.extra.DISK_ID"; + public static final String ACTION_DISK_SCANNED = + "android.os.storage.action.DISK_SCANNED"; + public static final String EXTRA_DISK_ID = + "android.os.storage.extra.DISK_ID"; public static final int FLAG_ADOPTABLE = 1 << 0; public static final int FLAG_DEFAULT_PRIMARY = 1 << 1; @@ -68,6 +71,9 @@ public class DiskInfo implements Parcelable { if (TextUtils.isEmpty(label)) { return false; } + if (label.equalsIgnoreCase("ata")) { + return false; + } if (label.toLowerCase().contains("generic")) { return false; } @@ -93,6 +99,14 @@ public class DiskInfo implements Parcelable { } } + public boolean isAdoptable() { + return (flags & FLAG_ADOPTABLE) != 0; + } + + public boolean isDefaultPrimary() { + return (flags & FLAG_DEFAULT_PRIMARY) != 0; + } + public boolean isSd() { return (flags & FLAG_SD) != 0; } @@ -101,10 +115,6 @@ public class DiskInfo implements Parcelable { return (flags & FLAG_USB) != 0; } - public boolean isAdoptable() { - return (flags & FLAG_ADOPTABLE) != 0; - } - @Override public String toString() { final CharArrayWriter writer = new CharArrayWriter(); diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java index 0a8187e..0b1031c 100644 --- a/core/java/android/os/storage/IMountService.java +++ b/core/java/android/os/storage/IMountService.java @@ -1063,6 +1063,38 @@ public interface IMountService extends IInterface { _data.recycle(); } } + + @Override + public String getPrimaryStorageUuid() throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + String _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + mRemote.transact(Stub.TRANSACTION_getPrimaryStorageUuid, _data, _reply, 0); + _reply.readException(); + _result = _reply.readString(); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } + + @Override + public void setPrimaryStorageUuid(String volumeUuid) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(volumeUuid); + mRemote.transact(Stub.TRANSACTION_setPrimaryStorageUuid, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } } private static final String DESCRIPTOR = "IMountService"; @@ -1169,6 +1201,9 @@ public interface IMountService extends IInterface { static final int TRANSACTION_setVolumeNickname = IBinder.FIRST_CALL_TRANSACTION + 52; static final int TRANSACTION_setVolumeUserFlags = IBinder.FIRST_CALL_TRANSACTION + 53; + static final int TRANSACTION_getPrimaryStorageUuid = IBinder.FIRST_CALL_TRANSACTION + 54; + static final int TRANSACTION_setPrimaryStorageUuid = IBinder.FIRST_CALL_TRANSACTION + 55; + /** * Cast an IBinder object into an IMountService interface, generating a * proxy if needed. @@ -1669,6 +1704,20 @@ public interface IMountService extends IInterface { reply.writeNoException(); return true; } + case TRANSACTION_getPrimaryStorageUuid: { + data.enforceInterface(DESCRIPTOR); + String volumeUuid = getPrimaryStorageUuid(); + reply.writeNoException(); + reply.writeString(volumeUuid); + return true; + } + case TRANSACTION_setPrimaryStorageUuid: { + data.enforceInterface(DESCRIPTOR); + String volumeUuid = data.readString(); + setPrimaryStorageUuid(volumeUuid); + reply.writeNoException(); + return true; + } } return super.onTransact(code, data, reply, flags); } @@ -1969,4 +2018,7 @@ public interface IMountService extends IInterface { public void setVolumeNickname(String volId, String nickname) throws RemoteException; public void setVolumeUserFlags(String volId, int flags, int mask) throws RemoteException; + + public String getPrimaryStorageUuid() throws RemoteException; + public void setPrimaryStorageUuid(String volumeUuid) throws RemoteException; } diff --git a/core/java/android/os/storage/IMountServiceListener.java b/core/java/android/os/storage/IMountServiceListener.java index 8e878a4..fcb4779 100644 --- a/core/java/android/os/storage/IMountServiceListener.java +++ b/core/java/android/os/storage/IMountServiceListener.java @@ -98,10 +98,11 @@ public interface IMountServiceListener extends IInterface { reply.writeNoException(); return true; } - case TRANSACTION_onDiskUnsupported: { + case TRANSACTION_onDiskScanned: { data.enforceInterface(DESCRIPTOR); final DiskInfo disk = (DiskInfo) data.readParcelable(null); - onDiskUnsupported(disk); + final int volumeCount = data.readInt(); + onDiskScanned(disk, volumeCount); reply.writeNoException(); return true; } @@ -207,13 +208,14 @@ public interface IMountServiceListener extends IInterface { } @Override - public void onDiskUnsupported(DiskInfo disk) throws RemoteException { + public void onDiskScanned(DiskInfo disk, int volumeCount) throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeParcelable(disk, 0); - mRemote.transact(Stub.TRANSACTION_onDiskUnsupported, _data, _reply, + _data.writeInt(volumeCount); + mRemote.transact(Stub.TRANSACTION_onDiskScanned, _data, _reply, android.os.IBinder.FLAG_ONEWAY); _reply.readException(); } finally { @@ -224,12 +226,10 @@ public interface IMountServiceListener extends IInterface { } static final int TRANSACTION_onUsbMassStorageConnectionChanged = (IBinder.FIRST_CALL_TRANSACTION + 0); - static final int TRANSACTION_onStorageStateChanged = (IBinder.FIRST_CALL_TRANSACTION + 1); - static final int TRANSACTION_onVolumeStateChanged = (IBinder.FIRST_CALL_TRANSACTION + 2); static final int TRANSACTION_onVolumeMetadataChanged = (IBinder.FIRST_CALL_TRANSACTION + 3); - static final int TRANSACTION_onDiskUnsupported = (IBinder.FIRST_CALL_TRANSACTION + 4); + static final int TRANSACTION_onDiskScanned = (IBinder.FIRST_CALL_TRANSACTION + 4); } /** @@ -255,5 +255,5 @@ public interface IMountServiceListener extends IInterface { public void onVolumeMetadataChanged(VolumeInfo vol) throws RemoteException; - public void onDiskUnsupported(DiskInfo disk) throws RemoteException; + public void onDiskScanned(DiskInfo disk, int volumeCount) throws RemoteException; } diff --git a/core/java/android/os/storage/StorageEventListener.java b/core/java/android/os/storage/StorageEventListener.java index ad2fae0..6a0140e 100644 --- a/core/java/android/os/storage/StorageEventListener.java +++ b/core/java/android/os/storage/StorageEventListener.java @@ -44,6 +44,6 @@ public class StorageEventListener { public void onVolumeMetadataChanged(VolumeInfo vol) { } - public void onDiskUnsupported(DiskInfo disk) { + public void onDiskScanned(DiskInfo disk, int volumeCount) { } } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index efa3ef2..747fb40 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -69,6 +69,13 @@ public class StorageManager { /** {@hide} */ public static final String PROP_PRIMARY_PHYSICAL = "ro.vold.primary_physical"; + /** {@hide} */ + public static final String PROP_FORCE_ADOPTABLE = "persist.fw.force_adoptable"; + + /** {@hide} */ + public static final String UUID_PRIVATE_INTERNAL = null; + /** {@hide} */ + public static final String UUID_PRIMARY_PHYSICAL = "primary_physical"; /** {@hide} */ public static final int FLAG_ALL_METADATA = 1 << 0; @@ -87,7 +94,7 @@ public class StorageManager { private static final int MSG_STORAGE_STATE_CHANGED = 1; private static final int MSG_VOLUME_STATE_CHANGED = 2; private static final int MSG_VOLUME_METADATA_CHANGED = 3; - private static final int MSG_DISK_UNSUPPORTED = 4; + private static final int MSG_DISK_SCANNED = 4; final StorageEventListener mCallback; final Handler mHandler; @@ -114,8 +121,8 @@ public class StorageManager { mCallback.onVolumeMetadataChanged((VolumeInfo) args.arg1); args.recycle(); return true; - case MSG_DISK_UNSUPPORTED: - mCallback.onDiskUnsupported((DiskInfo) args.arg1); + case MSG_DISK_SCANNED: + mCallback.onDiskScanned((DiskInfo) args.arg1, args.argi2); args.recycle(); return true; } @@ -154,10 +161,11 @@ public class StorageManager { } @Override - public void onDiskUnsupported(DiskInfo disk) { + public void onDiskScanned(DiskInfo disk, int volumeCount) { final SomeArgs args = SomeArgs.obtain(); args.arg1 = disk; - mHandler.obtainMessage(MSG_DISK_UNSUPPORTED, args).sendToTarget(); + args.argi2 = volumeCount; + mHandler.obtainMessage(MSG_DISK_SCANNED, args).sendToTarget(); } } @@ -532,17 +540,26 @@ public class StorageManager { /** {@hide} */ public @Nullable String getBestVolumeDescription(VolumeInfo vol) { String descrip = vol.getDescription(); - if (vol.disk != null) { if (TextUtils.isEmpty(descrip)) { descrip = vol.disk.getDescription(); } } - return descrip; } /** {@hide} */ + public @Nullable VolumeInfo getPrimaryPhysicalVolume() { + final List<VolumeInfo> vols = getVolumes(); + for (VolumeInfo vol : vols) { + if (vol.isPrimaryPhysical()) { + return vol; + } + } + return null; + } + + /** {@hide} */ public void mount(String volId) { try { mMountService.mount(volId); @@ -626,6 +643,24 @@ public class StorageManager { } /** {@hide} */ + public String getPrimaryStorageUuid() { + try { + return mMountService.getPrimaryStorageUuid(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** {@hide} */ + public void setPrimaryStorageUuid(String volumeUuid) { + try { + mMountService.setPrimaryStorageUuid(volumeUuid); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** {@hide} */ public @Nullable StorageVolume getStorageVolume(File file) { return getStorageVolume(getVolumeList(), file); } diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java index f3498d5..4e9cfc7 100644 --- a/core/java/android/os/storage/VolumeInfo.java +++ b/core/java/android/os/storage/VolumeInfo.java @@ -242,6 +242,10 @@ public class VolumeInfo implements Parcelable { return (mountFlags & MOUNT_FLAG_PRIMARY) != 0; } + public boolean isPrimaryPhysical() { + return isPrimary() && (getType() == TYPE_PUBLIC); + } + public boolean isVisible() { return (mountFlags & MOUNT_FLAG_VISIBLE) != 0; } diff --git a/core/java/android/provider/Browser.java b/core/java/android/provider/Browser.java index 69a05c4..bae06b8 100644 --- a/core/java/android/provider/Browser.java +++ b/core/java/android/provider/Browser.java @@ -16,6 +16,7 @@ package android.provider; +import android.annotation.RequiresPermission; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; @@ -32,6 +33,9 @@ import android.provider.BrowserContract.Searches; import android.util.Log; import android.webkit.WebIconDatabase; +import static android.Manifest.permission.READ_HISTORY_BOOKMARKS; +import static android.Manifest.permission.WRITE_HISTORY_BOOKMARKS; + public class Browser { private static final String LOGTAG = "browser"; @@ -41,6 +45,8 @@ public class Browser { * {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS} permission and writing to it * requires the {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS} permission. */ + @RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS)) + @RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS)) public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks"); /** @@ -122,6 +128,8 @@ public class Browser { * {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS} permission and writing to it * requires the {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS} permission. */ + @RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS)) + @RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS)) public static final Uri SEARCHES_URI = Uri.parse("content://browser/searches"); /** @@ -233,6 +241,7 @@ public class Browser { * * @param cr The ContentResolver used to access the database. */ + @RequiresPermission(READ_HISTORY_BOOKMARKS) public static final Cursor getAllBookmarks(ContentResolver cr) throws IllegalStateException { return cr.query(Bookmarks.CONTENT_URI, @@ -248,6 +257,7 @@ public class Browser { * * @param cr The ContentResolver used to access the database. */ + @RequiresPermission(READ_HISTORY_BOOKMARKS) public static final Cursor getAllVisitedUrls(ContentResolver cr) throws IllegalStateException { return cr.query(Combined.CONTENT_URI, @@ -308,6 +318,7 @@ public class Browser { * @param real If true, this is an actual visit, and should add to the * number of visits. If false, the user entered it manually. */ + @RequiresPermission(allOf = {READ_HISTORY_BOOKMARKS, WRITE_HISTORY_BOOKMARKS}) public static final void updateVisitedHistory(ContentResolver cr, String url, boolean real) { long now = System.currentTimeMillis(); @@ -358,6 +369,7 @@ public class Browser { * @param cr The ContentResolver used to access the database. * @hide pending API council approval */ + @RequiresPermission(READ_HISTORY_BOOKMARKS) public static final String[] getVisitedHistory(ContentResolver cr) { Cursor c = null; String[] str = null; @@ -393,6 +405,7 @@ public class Browser { * * @param cr The ContentResolver used to access the database. */ + @RequiresPermission(allOf = {READ_HISTORY_BOOKMARKS, WRITE_HISTORY_BOOKMARKS}) public static final void truncateHistory(ContentResolver cr) { // TODO make a single request to the provider to do this in a single transaction Cursor cursor = null; @@ -424,6 +437,7 @@ public class Browser { * @param cr The ContentResolver used to access the database. * @return boolean True if the history can be cleared. */ + @RequiresPermission(READ_HISTORY_BOOKMARKS) public static final boolean canClearHistory(ContentResolver cr) { Cursor cursor = null; boolean ret = false; @@ -446,6 +460,7 @@ public class Browser { * Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS} * @param cr The ContentResolver used to access the database. */ + @RequiresPermission(WRITE_HISTORY_BOOKMARKS) public static final void clearHistory(ContentResolver cr) { deleteHistoryWhere(cr, null); } @@ -461,6 +476,7 @@ public class Browser { * @param whereClause String to limit the items affected. * null means all items. */ + @RequiresPermission(allOf = {READ_HISTORY_BOOKMARKS, WRITE_HISTORY_BOOKMARKS}) private static final void deleteHistoryWhere(ContentResolver cr, String whereClause) { Cursor cursor = null; try { @@ -486,6 +502,7 @@ public class Browser { * @param end Last date to remove. If -1, all dates after begin. * Non-inclusive. */ + @RequiresPermission(WRITE_HISTORY_BOOKMARKS) public static final void deleteHistoryTimeFrame(ContentResolver cr, long begin, long end) { String whereClause; @@ -511,6 +528,7 @@ public class Browser { * @param cr The ContentResolver used to access the database. * @param url url to remove. */ + @RequiresPermission(WRITE_HISTORY_BOOKMARKS) public static final void deleteFromHistory(ContentResolver cr, String url) { cr.delete(History.CONTENT_URI, History.URL + "=?", new String[] { url }); @@ -523,6 +541,7 @@ public class Browser { * @param cr The ContentResolver used to access the database. * @param search The string to add to the searches database. */ + @RequiresPermission(allOf = {READ_HISTORY_BOOKMARKS, WRITE_HISTORY_BOOKMARKS}) public static final void addSearchUrl(ContentResolver cr, String search) { // The content provider will take care of updating existing searches instead of duplicating ContentValues values = new ContentValues(); @@ -536,6 +555,7 @@ public class Browser { * Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS} * @param cr The ContentResolver used to access the database. */ + @RequiresPermission(WRITE_HISTORY_BOOKMARKS) public static final void clearSearches(ContentResolver cr) { // FIXME: Should this clear the urls to which these searches lead? // (i.e. remove google.com/query= blah blah blah) @@ -557,6 +577,7 @@ public class Browser { * @param listener IconListener that gets the icons once they are * retrieved. */ + @RequiresPermission(READ_HISTORY_BOOKMARKS) public static final void requestAllIcons(ContentResolver cr, String where, WebIconDatabase.IconListener listener) { // Do nothing: this is no longer used. diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index bf7f3cb..396cf19 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -2201,7 +2201,6 @@ public final class ContactsContract { public static final long DAYS_KEPT_MILLISECONDS = 1000L * 60L * 60L * 24L * (long)DAYS_KEPT; } - protected interface RawContactsColumns { /** * A reference to the {@link ContactsContract.Contacts#_ID} that this @@ -8921,4 +8920,147 @@ public final class ContactsContract { public static final String EXTRA_DATA_SET = "android.provider.extra.DATA_SET"; } } + + /** + * @hide + */ + protected interface MetadataSyncColumns { + + /** + * The raw contact backup id. + * A reference to the {@link ContactsContract.RawContacts#BACKUP_ID} that save the + * persistent unique id for each raw contact within its source system. + * + * @hide + */ + public static final String RAW_CONTACT_BACKUP_ID = "raw_contact_backup_id"; + + /** + * The account type to which the raw_contact of this item is associated. See + * {@link RawContacts#ACCOUNT_TYPE} + * + * @hide + */ + public static final String ACCOUNT_TYPE = "account_type"; + + /** + * The account name to which the raw_contact of this item is associated. See + * {@link RawContacts#ACCOUNT_NAME} + * + * @hide + */ + public static final String ACCOUNT_NAME = "account_name"; + + /** + * The data set within the account that the raw_contact of this row belongs to. This allows + * multiple sync adapters for the same account type to distinguish between + * each others' data. + * {@link RawContacts#DATA_SET} + * + * @hide + */ + public static final String DATA_SET = "data_set"; + + /** + * A text column contains the Json string got from People API. The Json string contains + * all the metadata related to the raw contact, i.e., all the data fields and + * aggregation exceptions. + * + * Here is an example of the Json string got from the actual schema. + * <pre> + * { + * "unique_contact_id": { + * "account_type": "CUSTOM_ACCOUNT", + * "custom_account_type": "facebook", + * "account_name": "android-test", + * "contact_id": "1111111", + * "data_set": "FOCUS" + * }, + * "contact_prefs": { + * "send_to_voicemail": true, + * "starred": false, + * "pinned": 2 + * }, + * "aggregation_data": [ + * { + * "type": "TOGETHER", + * "contact_ids": [ + * { + * "account_type": "GOOGLE_ACCOUNT", + * "account_name": "android-test2", + * "contact_id": "2222222", + * "data_set": "GOOGLE_PLUS" + * }, + * { + * "account_type": "GOOGLE_ACCOUNT", + * "account_name": "android-test3", + * "contact_id": "3333333", + * "data_set": "CUSTOM", + * "custom_data_set": "custom type" + * } + * ] + * } + * ], + * "field_data": [ + * { + * "field_data_id": "1001", + * "field_data_prefs": { + * "is_primary": true, + * "is_super_primary": true + * }, + * "usage_stats": [ + * { + * "usage_type": "CALL", + * "last_time_used": 10000001, + * "usage_count": 10 + * } + * ] + * } + * ] + * } + * </pre> + * + * @hide + */ + public static final String DATA = "data"; + + /** + * The "deleted" flag: "0" by default, "1" if the row has been marked + * for deletion. When {@link android.content.ContentResolver#delete} is + * called on a raw contact, updating MetadataSync table to set the flag of the raw contact + * as "1", then metadata sync adapter deletes the raw contact metadata on the server. + * <P>Type: INTEGER</P> + * + * @hide + */ + public static final String DELETED = "deleted"; + } + + /** + * Constants for the metadata sync table. This table is used to cache the metadata_sync data + * from server before it is merged into other CP2 tables. + * + * @hide + */ + public static final class MetadataSync implements BaseColumns, MetadataSyncColumns { + + /** The authority for the contacts metadata */ + public static final String METADATA_AUTHORITY = "com.android.contacts.metadata"; + + /** A content:// style uri to the authority for the contacts metadata */ + public static final Uri METADATA_AUTHORITY_URI = Uri.parse( + "content://" + METADATA_AUTHORITY); + + /** + * This utility class cannot be instantiated + */ + private MetadataSync() { + } + + /** + * The content:// style URI for this table. + */ + public static final Uri CONTENT_URI = Uri.withAppendedPath(METADATA_AUTHORITY_URI, + "metadata_sync"); + } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index a622a21..00c851b 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -189,6 +189,36 @@ public final class Settings { "android.settings.USAGE_ACCESS_SETTINGS"; /** + * Activity Category: Show application settings related to usage access. + * <p> + * An activity that provides a user interface for adjusting usage access related + * preferences for its containing application. Optional but recommended for apps that + * use {@link android.Manifest.permission#PACKAGE_USAGE_STATS}. + * <p> + * The activity may define meta-data to describe what usage access is + * used for within their app with {@link #METADATA_USAGE_ACCESS_REASON}, which + * will be displayed in Settings. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String INTENT_CATEGORY_USAGE_ACCESS_CONFIG = + "android.intent.category.USAGE_ACCESS_CONFIG"; + + /** + * Metadata key: Reason for needing usage access. + * <p> + * A key for metadata attached to an activity that receives action + * {@link #INTENT_CATEGORY_USAGE_ACCESS_CONFIG}, shown to the + * user as description of how the app uses usage access. + * <p> + */ + public static final String METADATA_USAGE_ACCESS_REASON = + "android.settings.metadata.USAGE_ACCESS_REASON"; + + /** * Activity Action: Show settings to allow configuration of security and * location privacy. * <p> @@ -5361,6 +5391,12 @@ public final class Settings { public static final String SMS_DEFAULT_APPLICATION = "sms_default_application"; /** + * Specifies the package name currently configured to be the default dialer application + * @hide + */ + public static final String DIALER_DEFAULT_APPLICATION = "dialer_default_application"; + + /** * Specifies the package name currently configured to be the emergency assistance application * * @see android.telephony.TelephonyManager#ACTION_EMERGENCY_ASSISTANCE diff --git a/core/java/android/security/NetworkSecurityPolicy.java b/core/java/android/security/NetworkSecurityPolicy.java index 70cd388..7e87717 100644 --- a/core/java/android/security/NetworkSecurityPolicy.java +++ b/core/java/android/security/NetworkSecurityPolicy.java @@ -46,9 +46,9 @@ public class NetworkSecurityPolicy { * without TLS or STARTTLS) is permitted for this process. * * <p>When cleartext network traffic is not permitted, the platform's components (e.g. HTTP and - * FTP stacks, {@link android.webkit.WebView}, {@link android.app.DownloadManager}, - * {@link android.media.MediaPlayer}) will refuse this process's requests to use cleartext - * traffic. Third-party libraries are strongly encouraged to honor this setting as well. + * FTP stacks, {@link android.app.DownloadManager}, {@link android.media.MediaPlayer}) will + * refuse this process's requests to use cleartext traffic. Third-party libraries are strongly + * encouraged to honor this setting as well. * * <p>This flag is honored on a best effort basis because it's impossible to prevent all * cleartext traffic from Android applications given the level of access provided to them. For @@ -56,6 +56,8 @@ public class NetworkSecurityPolicy { * because it cannot determine whether its traffic is in cleartext. However, most network * traffic from applications is handled by higher-level network stacks/components which can * honor this aspect of the policy. + * + * <p>NOTE: {@link android.webkit.WebView} does not honor this flag. */ public boolean isCleartextTrafficPermitted() { return libcore.net.NetworkSecurityPolicy.isCleartextTrafficPermitted(); diff --git a/core/java/android/service/chooser/ChooserTarget.java b/core/java/android/service/chooser/ChooserTarget.java index d21cc3c..f0ca276 100644 --- a/core/java/android/service/chooser/ChooserTarget.java +++ b/core/java/android/service/chooser/ChooserTarget.java @@ -78,7 +78,8 @@ public final class ChooserTarget implements Parcelable { * <p>The creator of a target may supply a ranking score. This score is assumed to be relative * to the other targets supplied by the same * {@link ChooserTargetService#onGetChooserTargets(ComponentName, IntentFilter) query}. - * Scores should be in the range from 0.0f (unlikely match) to 1.0f (very relevant match).</p> + * Scores should be in the range from 0.0f (unlikely match) to 1.0f (very relevant match). + * Scores for a set of targets do not need to sum to 1.</p> * * <p>Before being sent, the PendingIntent supplied will be * {@link Intent#fillIn(Intent, int) filled in} by the Intent originally supplied @@ -113,7 +114,8 @@ public final class ChooserTarget implements Parcelable { * <p>The creator of a target may supply a ranking score. This score is assumed to be relative * to the other targets supplied by the same * {@link ChooserTargetService#onGetChooserTargets(ComponentName, IntentFilter) query}. - * Scores should be in the range from 0.0f (unlikely match) to 1.0f (very relevant match).</p> + * Scores should be in the range from 0.0f (unlikely match) to 1.0f (very relevant match). + * Scores for a set of targets do not need to sum to 1.</p> * * <p>Before being sent, the IntentSender supplied will be * {@link Intent#fillIn(Intent, int) filled in} by the Intent originally supplied @@ -144,6 +146,32 @@ public final class ChooserTarget implements Parcelable { mIntentSender = intentSender; } + /** + * Construct a deep link target for presentation by a chooser UI. + * + * <p>A target is composed of a title and an icon for presentation to the user. + * The UI presenting this target may truncate the title if it is too long to be presented + * in the available space, as well as crop, resize or overlay the supplied icon.</p> + * + * <p>The creator of a target may supply a ranking score. This score is assumed to be relative + * to the other targets supplied by the same + * {@link ChooserTargetService#onGetChooserTargets(ComponentName, IntentFilter) query}. + * Scores should be in the range from 0.0f (unlikely match) to 1.0f (very relevant match). + * Scores for a set of targets do not need to sum to 1.</p> + * + * <p>Before being sent, the Intent supplied will be + * {@link Intent#fillIn(Intent, int) filled in} by the Intent originally supplied + * to the chooser.</p> + * + * <p>Take care not to place custom {@link android.os.Parcelable} types into + * the Intent as extras, as the system will not be able to unparcel it to merge + * additional extras.</p> + * + * @param title title of this target that will be shown to a user + * @param icon icon to represent this target + * @param score ranking score for this target between 0.0f and 1.0f, inclusive + * @param intent Intent to fill in and send if the user chooses this target + */ public ChooserTarget(CharSequence title, Bitmap icon, float score, Intent intent) { mTitle = title; mIcon = icon; @@ -358,6 +386,10 @@ public final class ChooserTarget implements Parcelable { } dest.writeFloat(mScore); IntentSender.writeIntentSenderOrNullToParcel(mIntentSender, dest); + dest.writeInt(mIntent != null ? 1 : 0); + if (mIntent != null) { + mIntent.writeToParcel(dest, 0); + } } public static final Creator<ChooserTarget> CREATOR diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index cc7f880..35b8819 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -358,6 +358,20 @@ public abstract class NotificationListenerService extends Service { } /** + * Inform the notification manager that these notifications have been viewed by the + * user. + * @param keys Notifications to mark as seen. + */ + public final void setNotificationsShown(String[] keys) { + if (!isBound()) return; + try { + getNotificationInterface().setNotificationsShownFromListener(mWrapper, keys); + } catch (android.os.RemoteException ex) { + Log.v(TAG, "Unable to contact notification manager", ex); + } + } + + /** * Sets the notification trim that will be received via {@link #onNotificationPosted}. * * <p> diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java index 5cf2c5c..b055efe 100644 --- a/core/java/android/view/ScaleGestureDetector.java +++ b/core/java/android/view/ScaleGestureDetector.java @@ -130,7 +130,7 @@ public class ScaleGestureDetector { private float mFocusY; private boolean mQuickScaleEnabled; - private boolean mButtonScaleEnabled; + private boolean mStylusScaleEnabled; private float mCurrSpan; private float mPrevSpan; @@ -162,7 +162,7 @@ public class ScaleGestureDetector { private static final float SCALE_FACTOR = .5f; private static final int ANCHORED_SCALE_MODE_NONE = 0; private static final int ANCHORED_SCALE_MODE_DOUBLE_TAP = 1; - private static final int ANCHORED_SCALE_MODE_BUTTON = 2; + private static final int ANCHORED_SCALE_MODE_STYLUS = 2; /** @@ -212,9 +212,14 @@ public class ScaleGestureDetector { mMinSpan = res.getDimensionPixelSize(com.android.internal.R.dimen.config_minScalingSpan); mHandler = handler; // Quick scale is enabled by default after JB_MR2 - if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.JELLY_BEAN_MR2) { + final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; + if (targetSdkVersion > Build.VERSION_CODES.JELLY_BEAN_MR2) { setQuickScaleEnabled(true); } + // Stylus scale is enabled by default after LOLLIPOP_MR1 + if (targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) { + setStylusScaleEnabled(true); + } } /** @@ -315,14 +320,11 @@ public class ScaleGestureDetector { } final int count = event.getPointerCount(); - final int toolType = event.getToolType(0); - final boolean isButtonTool = toolType == MotionEvent.TOOL_TYPE_STYLUS - || toolType == MotionEvent.TOOL_TYPE_MOUSE; - final boolean isAnchoredScaleButtonDown = isButtonTool && (count == 1) + final boolean isStylusButtonDown = (event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS) && (event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0; final boolean anchoredScaleCancelled = - mAnchoredScaleMode == ANCHORED_SCALE_MODE_BUTTON && !isAnchoredScaleButtonDown; + mAnchoredScaleMode == ANCHORED_SCALE_MODE_STYLUS && !isStylusButtonDown; final boolean streamComplete = action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled; @@ -347,12 +349,12 @@ public class ScaleGestureDetector { } } - if (!mInProgress && mButtonScaleEnabled && !inAnchoredScaleMode() - && !streamComplete && isAnchoredScaleButtonDown) { + if (!mInProgress && mStylusScaleEnabled && !inAnchoredScaleMode() + && !streamComplete && isStylusButtonDown) { // Start of a button scale gesture mAnchoredScaleStartX = event.getX(); mAnchoredScaleStartY = event.getY(); - mAnchoredScaleMode = ANCHORED_SCALE_MODE_BUTTON; + mAnchoredScaleMode = ANCHORED_SCALE_MODE_STYLUS; mInitialSpan = 0; } @@ -503,24 +505,22 @@ public class ScaleGestureDetector { } /** - * Sets whether the associates {@link OnScaleGestureListener} should receive onScale callbacks - * when the user presses a {@value MotionEvent#BUTTON_SECONDARY} (right mouse button, stylus - * first button) and drags the pointer on the screen. Note that this is enabled by default if - * the app targets API 23 and newer. + * Sets whether the associates {@link OnScaleGestureListener} should receive + * onScale callbacks when the user uses a stylus and presses the button. + * Note that this is enabled by default if the app targets API 23 and newer. * - * @param scales true to enable stylus or mouse scaling, false to disable. + * @param scales true to enable stylus scaling, false to disable. */ - public void setSecondaryButtonScaleEnabled(boolean scales) { - mButtonScaleEnabled = scales; + public void setStylusScaleEnabled(boolean scales) { + mStylusScaleEnabled = scales; } /** - * Return whether the button scale gesture, in which the user presses a - * {@value MotionEvent#BUTTON_SECONDARY} (right mouse button, stylus first button) and drags the - * pointer on the screen, should perform scaling. {@see #setButtonScaleEnabled(boolean)}. + * Return whether the stylus scale gesture, in which the user uses a stylus + * and presses the button, should preform scaling. {@see #setButtonScaleEnabled(boolean)}. */ - public boolean isSecondaryButtonScaleEnabled() { - return mButtonScaleEnabled; + public boolean isStylusScaleEnabled() { + return mStylusScaleEnabled; } /** diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 52e0235..5c6ce76 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -631,6 +631,7 @@ import java.util.concurrent.atomic.AtomicInteger; * </p> * * @attr ref android.R.styleable#View_alpha + * @attr ref android.R.styleable#View_assistBlocked * @attr ref android.R.styleable#View_background * @attr ref android.R.styleable#View_clickable * @attr ref android.R.styleable#View_contentDescription @@ -2324,6 +2325,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * 1 PFLAG3_IS_LAID_OUT * 1 PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT * 1 PFLAG3_CALLED_SUPER + * 1 PFLAG3_APPLYING_INSETS + * 1 PFLAG3_FITTING_SYSTEM_WINDOWS + * 1 PFLAG3_NESTED_SCROLLING_ENABLED + * 1 PFLAG3_ASSIST_BLOCKED * |-------|-------|-------|-------| */ @@ -2381,6 +2386,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, static final int DRAG_MASK = PFLAG2_DRAG_CAN_ACCEPT | PFLAG2_DRAG_HOVERED; /** + * <p>Indicates that we are allowing {@link android.view.ViewAssistStructure} to traverse + * into this view.<p> + */ + static final int PFLAG3_ASSIST_BLOCKED = 0x100; + + /** * Always allow a user to over-scroll this view, provided it is a * view that can scroll. * @@ -3869,6 +3880,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, viewFlagMasks |= SAVE_DISABLED_MASK; } break; + case com.android.internal.R.styleable.View_assistBlocked: + if (a.getBoolean(attr, false)) { + mPrivateFlags3 |= PFLAG3_ASSIST_BLOCKED; + } + break; case com.android.internal.R.styleable.View_duplicateParentState: if (a.getBoolean(attr, false)) { viewFlagValues |= DUPLICATE_PARENT_STATE; @@ -5775,7 +5791,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } else { structure.setId(id, null, null, null); } - structure.setDimens(mLeft, mTop, mScrollX, mScrollY, mRight-mLeft, mBottom-mTop); + structure.setDimens(mLeft, mTop, mScrollX, mScrollY, mRight - mLeft, mBottom - mTop); structure.setVisibility(getVisibility()); structure.setEnabled(isEnabled()); if (isClickable()) { @@ -5890,8 +5906,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * {@link #onProvideVirtualAssistStructure}. */ public void dispatchProvideAssistStructure(ViewAssistStructure structure) { - onProvideAssistStructure(structure); - onProvideVirtualAssistStructure(structure); + if (!isAssistBlocked()) { + onProvideAssistStructure(structure); + onProvideVirtualAssistStructure(structure); + } else { + structure.setClassName(getAccessibilityClassName().toString()); + structure.setAssistBlocked(true); + } } /** @@ -7458,6 +7479,42 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Indicates whether this view will participate in data collection through + * {@link android.view.ViewAssistStructure}. If true, it will not provide any data + * for itself or its children. If false, the normal data collection will be allowed. + * + * @return Returns false if assist data collection is not blocked, else true. + * + * @see #setAssistBlocked(boolean) + * @attr ref android.R.styleable#View_assistBlocked + */ + public boolean isAssistBlocked() { + return (mPrivateFlags3 & PFLAG3_ASSIST_BLOCKED) != 0; + } + + /** + * Controls whether assist data collection from this view and its children is enabled + * (that is, whether {@link #onProvideAssistStructure} and + * {@link #onProvideVirtualAssistStructure} will be called). The default value is false, + * allowing normal assist collection. Setting this to false will disable assist collection. + * + * @param enabled Set to true to <em>disable</em> assist data collection, or false + * (the default) to allow it. + * + * @see #isAssistBlocked() + * @see #onProvideAssistStructure + * @see #onProvideVirtualAssistStructure + * @attr ref android.R.styleable#View_assistBlocked + */ + public void setAssistBlocked(boolean enabled) { + if (enabled) { + mPrivateFlags3 |= PFLAG3_ASSIST_BLOCKED; + } else { + mPrivateFlags3 &= ~PFLAG3_ASSIST_BLOCKED; + } + } + + /** * Indicates whether this view will save its state (that is, * whether its {@link #onSaveInstanceState} method will be called). * @@ -15301,9 +15358,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, RenderNode renderNode = null; Bitmap cache = null; - int layerType = getLayerType(); + int layerType = getLayerType(); // TODO: signify cache state with just 'cache' local if (layerType == LAYER_TYPE_SOFTWARE || (!drawingWithRenderNode && layerType != LAYER_TYPE_NONE)) { + // If not drawing with RenderNode, treat HW layers as SW layerType = LAYER_TYPE_SOFTWARE; buildDrawingCache(true); cache = getDrawingCache(true); @@ -15330,10 +15388,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, sy = mScrollY; } - final boolean hasNoCache = cache == null || drawingWithRenderNode; - final boolean offsetForScroll = cache == null - && !drawingWithRenderNode - && layerType != LAYER_TYPE_HARDWARE; + final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode; + final boolean offsetForScroll = cache == null && !drawingWithRenderNode; int restoreTo = -1; if (!drawingWithRenderNode || transformToApply != null) { @@ -15406,17 +15462,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_ALPHA; } parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION; - if (hasNoCache) { + if (!drawingWithDrawingCache) { final int multipliedAlpha = (int) (255 * alpha); if (!onSetAlpha(multipliedAlpha)) { - int layerFlags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG; - if ((parentFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0 - || layerType != LAYER_TYPE_NONE) { - layerFlags |= Canvas.CLIP_TO_LAYER_SAVE_FLAG; - } if (drawingWithRenderNode) { renderNode.setAlpha(alpha * getAlpha() * getTransitionAlpha()); } else if (layerType == LAYER_TYPE_NONE) { + int layerFlags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG; + if ((parentFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0) { + layerFlags |= Canvas.CLIP_TO_LAYER_SAVE_FLAG; + } canvas.saveLayerAlpha(sx, sy, sx + getWidth(), sy + getHeight(), multipliedAlpha, layerFlags); } @@ -15451,33 +15506,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - if (hasNoCache) { - boolean layerRendered = false; - if (layerType == LAYER_TYPE_HARDWARE && !drawingWithRenderNode) { - final HardwareLayer layer = getHardwareLayer(); - if (layer != null && layer.isValid()) { - int restoreAlpha = mLayerPaint.getAlpha(); - mLayerPaint.setAlpha((int) (alpha * 255)); - ((DisplayListCanvas) canvas).drawHardwareLayer(layer, 0, 0, mLayerPaint); - mLayerPaint.setAlpha(restoreAlpha); - layerRendered = true; - } else { - canvas.saveLayer(sx, sy, sx + getWidth(), sy + getHeight(), mLayerPaint); - } - } - - if (!layerRendered) { - if (!drawingWithRenderNode) { - // Fast path for layouts with no backgrounds - if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { - mPrivateFlags &= ~PFLAG_DIRTY_MASK; - dispatchDraw(canvas); - } else { - draw(canvas); - } - } else { + if (!drawingWithDrawingCache) { + if (drawingWithRenderNode) { + mPrivateFlags &= ~PFLAG_DIRTY_MASK; + ((DisplayListCanvas) canvas).drawRenderNode(renderNode, parentFlags); + } else { + // Fast path for layouts with no backgrounds + if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; - ((DisplayListCanvas) canvas).drawRenderNode(renderNode, parentFlags); + dispatchDraw(canvas); + } else { + draw(canvas); } } } else if (cache != null) { diff --git a/core/java/android/view/ViewAssistStructure.java b/core/java/android/view/ViewAssistStructure.java index 7d263c5..346b8ec 100644 --- a/core/java/android/view/ViewAssistStructure.java +++ b/core/java/android/view/ViewAssistStructure.java @@ -32,6 +32,8 @@ public abstract class ViewAssistStructure { public abstract void setVisibility(int visibility); + public abstract void setAssistBlocked(boolean state); + public abstract void setEnabled(boolean state); public abstract void setClickable(boolean state); diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index a237afd..27304f5 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -1040,14 +1040,10 @@ public class ViewDebug { return methods; } - final ArrayList<Method> declaredMethods = new ArrayList(); - klass.getDeclaredMethodsUnchecked(false, declaredMethods); + methods = klass.getDeclaredMethodsUnchecked(false); final ArrayList<Method> foundMethods = new ArrayList<Method>(); - final int count = declaredMethods.size(); - for (int i = 0; i < count; i++) { - final Method method = declaredMethods.get(i); - + for (final Method method : methods) { // Ensure the method return and parameter types can be resolved. try { method.getReturnType(); diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 8f2be99..4324e75 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -2881,21 +2881,23 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ public void dispatchProvideAssistStructure(ViewAssistStructure structure) { super.dispatchProvideAssistStructure(structure); - if (structure.getChildCount() == 0) { - final int childrenCount = getChildCount(); - if (childrenCount > 0) { - structure.setChildCount(childrenCount); - final ArrayList<View> preorderedList = buildOrderedChildList(); - final boolean customOrder = preorderedList == null - && isChildrenDrawingOrderEnabled(); - final View[] children = mChildren; - for (int i=0; i<childrenCount; i++) { - final int childIndex = customOrder - ? getChildDrawingOrder(childrenCount, i) : i; - final View child = (preorderedList == null) - ? children[childIndex] : preorderedList.get(childIndex); - ViewAssistStructure cstructure = structure.newChild(i); - child.dispatchProvideAssistStructure(cstructure); + if (!isAssistBlocked()) { + if (structure.getChildCount() == 0) { + final int childrenCount = getChildCount(); + if (childrenCount > 0) { + structure.setChildCount(childrenCount); + final ArrayList<View> preorderedList = buildOrderedChildList(); + final boolean customOrder = preorderedList == null + && isChildrenDrawingOrderEnabled(); + final View[] children = mChildren; + for (int i=0; i<childrenCount; i++) { + final int childIndex = customOrder + ? getChildDrawingOrder(childrenCount, i) : i; + final View child = (preorderedList == null) + ? children[childIndex] : preorderedList.get(childIndex); + ViewAssistStructure cstructure = structure.newChild(i); + child.dispatchProvideAssistStructure(cstructure); + } } } } diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java index 0417921..4d1209a 100644 --- a/core/java/android/view/animation/AnimationUtils.java +++ b/core/java/android/view/animation/AnimationUtils.java @@ -16,7 +16,6 @@ package android.view.animation; -import android.content.res.Configuration; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -45,16 +44,6 @@ public class AnimationUtils { private static final int TOGETHER = 0; private static final int SEQUENTIALLY = 1; - private static final float RECOMMENDED_FIELD_OF_VIEW_FOR_TV = 40f; - private static final float ESTIMATED_VIEWING_DISTANCE_FOR_WATCH = 11f; - private static final float AVERAGE_VIEWING_DISTANCE_FOR_PHONES = 14.2f; - private static final float N5_DIAGONAL_VIEW_ANGLE = 19.58f; - private static final float N5_DENSITY = 3.0f; - private static final float N5_DPI = 443f; - - private static final float COTANGENT_OF_HALF_TV_ANGLE = (float) (1 / Math.tan(Math.toRadians - (RECOMMENDED_FIELD_OF_VIEW_FOR_TV / 2))); - /** * Returns the current animation time in milliseconds. This time should be used when invoking @@ -378,78 +367,4 @@ public class AnimationUtils { } return interpolator; } - - /** - * Derives the viewing distance of a device based on the device size (in inches), and the - * device type. - * @hide - */ - public static float getViewingDistance(float width, float height, int uiMode) { - if (uiMode == Configuration.UI_MODE_TYPE_TELEVISION) { - // TV - return (width / 2) * COTANGENT_OF_HALF_TV_ANGLE; - } else if (uiMode == Configuration.UI_MODE_TYPE_WATCH) { - // Watch - return ESTIMATED_VIEWING_DISTANCE_FOR_WATCH; - } else { - // Tablet, phone, etc - return AVERAGE_VIEWING_DISTANCE_FOR_PHONES; - } - } - - /** - * Calculates the duration scaling factor of an animation based on the hint that the animation - * will move across the entire screen. A scaling factor of 1 means the duration on this given - * device will be the same as the duration set through - * {@link android.animation.Animator#setDuration(long)}. The calculation uses Nexus 5 as a - * baseline device. That is, the duration of the animation on a given device will scale its - * duration so that it has the same look and feel as the animation on Nexus 5. In order to - * achieve the same perceived effect of the animation across different devices, we maintain - * the same angular speed of the same animation in users' field of view. Therefore, the - * duration scale factor is determined by the ratio of the angular movement on current - * devices to that on the baseline device. - * - * @param width width of the screen (in inches) - * @param height height of the screen (in inches) - * @param viewingDistance the viewing distance of the device (i.e. watch, phone, TV, etc) in - * inches - * @return scaling factor (or multiplier) of the duration set through - * {@link android.animation.Animator#setDuration(long)} on current device. - * @hide - */ - public static float getScreenSizeBasedDurationScale(float width, float height, - float viewingDistance) { - // Animation's moving distance is proportional to the screen size. - float diagonal = (float) Math.sqrt(width * width + height * height); - float diagonalViewAngle = (float) Math.toDegrees(Math.atan((diagonal / 2f) - / viewingDistance) * 2); - return diagonalViewAngle / N5_DIAGONAL_VIEW_ANGLE; - } - - /** - * Calculates the duration scaling factor of an animation under the assumption that the - * animation is defined to move the same amount of distance (in dp) across all devices. A - * scaling factor of 1 means the duration on this given device will be the same as the - * duration set through {@link android.animation.Animator#setDuration(long)}. The calculation - * uses Nexus 5 as a baseline device. That is, the duration of the animation on a given - * device will scale its duration so that it has the same look and feel as the animation on - * Nexus 5. In order to achieve the same perceived effect of the animation across different - * devices, we maintain the same angular velocity of the same animation in users' field of - * view. Therefore, the duration scale factor is determined by the ratio of the angular - * movement on current devices to that on the baseline device. - * - * @param density logical density of the display. {@link android.util.DisplayMetrics#density} - * @param dpi pixels per inch - * @param viewingDistance viewing distance of the device (in inches) - * @return the scaling factor of duration - * @hide - */ - public static float getDpBasedDurationScale(float density, float dpi, - float viewingDistance) { - // Angle in users' field of view per dp: - float anglePerDp = (float) Math.atan2((density / dpi) / 2, viewingDistance) * 2; - float baselineAnglePerDp = (float) Math.atan2((N5_DENSITY / N5_DPI) / 2, - AVERAGE_VIEWING_DISTANCE_FOR_PHONES) * 2; - return anglePerDp / baselineAnglePerDp; - } } diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index 943beb0..453e4f5 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -900,7 +900,9 @@ public abstract class WebSettings { * and therefore secure policy, this setting should be disabled. * Note that this setting affects only JavaScript access to file scheme * resources. Other access to such resources, for example, from image HTML - * elements, is unaffected. + * elements, is unaffected. To prevent possible violation of same domain policy + * on {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH} and earlier + * devices, you should explicitly set this value to {@code false}. * <p> * The default value is true for API level * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH_MR1} and below, @@ -920,7 +922,9 @@ public abstract class WebSettings { * the value of {@link #getAllowUniversalAccessFromFileURLs} is true. * Note too, that this setting affects only JavaScript access to file scheme * resources. Other access to such resources, for example, from image HTML - * elements, is unaffected. + * elements, is unaffected. To prevent possible violation of same domain policy + * on {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH} and earlier + * devices, you should explicitly set this value to {@code false}. * <p> * The default value is true for API level * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH_MR1} and below, diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index 3340c73..9782d72 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -76,6 +76,18 @@ public final class WebViewFactory { private static boolean sAddressSpaceReserved = false; private static PackageInfo sPackageInfo; + // Error codes for loadWebViewNativeLibraryFromPackage + public static final int LIBLOAD_SUCCESS = 0; + public static final int LIBLOAD_WRONG_PACKAGE_NAME = 1; + public static final int LIBLOAD_ADDRESS_SPACE_NOT_RESERVED = 2; + public static final int LIBLOAD_FAILED_WAITING_FOR_RELRO = 3; + public static final int LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES = 4; + + // native relro loading error codes + public static final int LIBLOAD_FAILED_TO_OPEN_RELRO_FILE = 5; + public static final int LIBLOAD_FAILED_TO_LOAD_LIBRARY = 6; + public static final int LIBLOAD_FAILED_JNI_CALL = 7; + private static class MissingWebViewPackageException extends AndroidRuntimeException { public MissingWebViewPackageException(String message) { super(message); } public MissingWebViewPackageException(Exception e) { super(e); } @@ -136,6 +148,18 @@ public final class WebViewFactory { return sPackageInfo; } + /** + * Load the native library for the given package name iff that package + * name is the same as the one providing the current webview. + */ + public static int loadWebViewNativeLibraryFromPackage(String packageName) { + sPackageInfo = findPreferredWebViewPackage(); + if (packageName != null && packageName.equals(sPackageInfo.packageName)) { + return loadNativeLibrary(); + } + return LIBLOAD_WRONG_PACKAGE_NAME; + } + static WebViewFactoryProvider getProvider() { synchronized (sProviderLock) { // For now the main purpose of this function (and the factory abstraction) is to keep @@ -434,32 +458,34 @@ public final class WebViewFactory { } } - private static void loadNativeLibrary() { + private static int loadNativeLibrary() { if (!sAddressSpaceReserved) { Log.e(LOGTAG, "can't load with relro file; address space not reserved"); - return; + return LIBLOAD_ADDRESS_SPACE_NOT_RESERVED; } try { getUpdateService().waitForRelroCreationCompleted(VMRuntime.getRuntime().is64Bit()); } catch (RemoteException e) { Log.e(LOGTAG, "error waiting for relro creation, proceeding without", e); - return; + return LIBLOAD_FAILED_WAITING_FOR_RELRO; } try { String[] args = getWebViewNativeLibraryPaths(); - boolean result = nativeLoadWithRelroFile(args[0] /* path32 */, + int result = nativeLoadWithRelroFile(args[0] /* path32 */, args[1] /* path64 */, CHROMIUM_WEBVIEW_NATIVE_RELRO_32, CHROMIUM_WEBVIEW_NATIVE_RELRO_64); - if (!result) { + if (result != LIBLOAD_SUCCESS) { Log.w(LOGTAG, "failed to load with relro file, proceeding without"); } else if (DEBUG) { Log.v(LOGTAG, "loaded with relro file"); } + return result; } catch (MissingWebViewPackageException e) { Log.e(LOGTAG, "Failed to list WebView package libraries for loadNativeLibrary", e); + return LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES; } } @@ -470,6 +496,6 @@ public final class WebViewFactory { private static native boolean nativeReserveAddressSpace(long addressSpaceToReserve); private static native boolean nativeCreateRelroFile(String lib32, String lib64, String relro32, String relro64); - private static native boolean nativeLoadWithRelroFile(String lib32, String lib64, + private static native int nativeLoadWithRelroFile(String lib32, String lib64, String relro32, String relro64); } diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java index c6b4d7e..113e597 100644 --- a/core/java/android/widget/DayPickerView.java +++ b/core/java/android/widget/DayPickerView.java @@ -196,9 +196,23 @@ class DayPickerView extends ViewGroup { } @Override + public void onRtlPropertiesChanged(@ResolvedLayoutDir int layoutDirection) { + super.onRtlPropertiesChanged(layoutDirection); + + requestLayout(); + } + + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - final ImageButton leftButton = mPrevButton; - final ImageButton rightButton = mNextButton; + final ImageButton leftButton; + final ImageButton rightButton; + if (isLayoutRtl()) { + leftButton = mNextButton; + rightButton = mPrevButton; + } else { + leftButton = mPrevButton; + rightButton = mNextButton; + } final int width = right - left; final int height = bottom - top; diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 955ad06..652fff2 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -39,7 +39,6 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.inputmethodservice.ExtractEditText; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Parcel; @@ -104,7 +103,6 @@ import android.widget.TextView.OnEditorActionListener; import com.android.internal.util.ArrayUtils; import com.android.internal.util.GrowingArrayUtils; -import com.android.internal.view.menu.MenuBuilder; import com.android.internal.widget.EditableInputConnection; import java.text.BreakIterator; @@ -1764,15 +1762,55 @@ public class Editor { } /** - * @return <code>true</code> if the cursor/current selection overlaps a {@link SuggestionSpan}. + * @return <code>true</code> if it's reasonable to offer to show suggestions depending on + * the current cursor position or selection range. This method is consistent with the + * method to show suggestions {@link SuggestionsPopupWindow#updateSuggestions}. */ - private boolean isCursorInsideSuggestionSpan() { + private boolean shouldOfferToShowSuggestions() { CharSequence text = mTextView.getText(); if (!(text instanceof Spannable)) return false; - SuggestionSpan[] suggestionSpans = ((Spannable) text).getSpans( - mTextView.getSelectionStart(), mTextView.getSelectionEnd(), SuggestionSpan.class); - return (suggestionSpans.length > 0); + final Spannable spannable = (Spannable) text; + final int selectionStart = mTextView.getSelectionStart(); + final int selectionEnd = mTextView.getSelectionEnd(); + final SuggestionSpan[] suggestionSpans = spannable.getSpans(selectionStart, selectionEnd, + SuggestionSpan.class); + if (suggestionSpans.length == 0) { + return false; + } + if (selectionStart == selectionEnd) { + // Spans overlap the cursor. + return true; + } + int minSpanStart = mTextView.getText().length(); + int maxSpanEnd = 0; + int unionOfSpansCoveringSelectionStartStart = mTextView.getText().length(); + int unionOfSpansCoveringSelectionStartEnd = 0; + for (int i = 0; i < suggestionSpans.length; i++) { + final int spanStart = spannable.getSpanStart(suggestionSpans[i]); + final int spanEnd = spannable.getSpanEnd(suggestionSpans[i]); + minSpanStart = Math.min(minSpanStart, spanStart); + maxSpanEnd = Math.max(maxSpanEnd, spanEnd); + if (selectionStart < spanStart || selectionStart > spanEnd) { + // The span doesn't cover the current selection start point. + continue; + } + unionOfSpansCoveringSelectionStartStart = + Math.min(unionOfSpansCoveringSelectionStartStart, spanStart); + unionOfSpansCoveringSelectionStartEnd = + Math.max(unionOfSpansCoveringSelectionStartEnd, spanEnd); + } + if (unionOfSpansCoveringSelectionStartStart >= unionOfSpansCoveringSelectionStartEnd) { + // No spans cover the selection start point. + return false; + } + if (minSpanStart < unionOfSpansCoveringSelectionStartStart + || maxSpanEnd > unionOfSpansCoveringSelectionStartEnd) { + // There is a span that is not covered by the union. In this case, we soouldn't offer + // to show suggestions as it's confusing. + return false; + } + return true; } /** @@ -1919,6 +1957,9 @@ public class Editor { if (mPositionListener != null) { mPositionListener.onScrollChanged(); } + if (mSelectionActionMode != null) { + mSelectionActionMode.invalidateContentRect(); + } } /** @@ -3020,18 +3061,8 @@ public class Editor { } private void populateMenuWithItems(Menu menu) { - final boolean legacy = mTextView.getContext().getApplicationInfo().targetSdkVersion < - Build.VERSION_CODES.LOLLIPOP; - final Context context = !legacy && menu instanceof MenuBuilder ? - ((MenuBuilder) menu).getContext() : - mTextView.getContext(); - final TypedArray styledAttributes = context.obtainStyledAttributes( - com.android.internal.R.styleable.SelectionModeDrawables); - if (mTextView.canCut()) { menu.add(0, TextView.ID_CUT, 0, com.android.internal.R.string.cut). - setIcon(styledAttributes.getResourceId( - R.styleable.SelectionModeDrawables_actionModeCutDrawable, 0)). setAlphabeticShortcut('x'). setShowAsAction( MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); @@ -3039,8 +3070,6 @@ public class Editor { if (mTextView.canCopy()) { menu.add(0, TextView.ID_COPY, 0, com.android.internal.R.string.copy). - setIcon(styledAttributes.getResourceId( - R.styleable.SelectionModeDrawables_actionModeCopyDrawable, 0)). setAlphabeticShortcut('c'). setShowAsAction( MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); @@ -3048,23 +3077,25 @@ public class Editor { if (mTextView.canPaste()) { menu.add(0, TextView.ID_PASTE, 0, com.android.internal.R.string.paste). - setIcon(styledAttributes.getResourceId( - R.styleable.SelectionModeDrawables_actionModePasteDrawable, 0)). setAlphabeticShortcut('v'). setShowAsAction( MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); } - menu.add(0, TextView.ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll). - setIcon(styledAttributes.getResourceId( - R.styleable.SelectionModeDrawables_actionModeSelectAllDrawable, 0)). - setAlphabeticShortcut('a'). - setShowAsAction( - MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + if (mTextView.canShare()) { + menu.add(0, TextView.ID_SHARE, 0, com.android.internal.R.string.share). + setShowAsAction( + MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } - updateReplaceItem(menu); + if (canSelectText() && !hasPasswordTransformationMethod()) { + menu.add(0, TextView.ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll). + setAlphabeticShortcut('a'). + setShowAsAction( + MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } - styledAttributes.recycle(); + updateReplaceItem(menu); } private void addIntentMenuItemsForTextProcessing(Menu menu) { @@ -3104,7 +3135,7 @@ public class Editor { } private void updateReplaceItem(Menu menu) { - boolean canReplace = mTextView.isSuggestionsEnabled() && isCursorInsideSuggestionSpan(); + boolean canReplace = mTextView.isSuggestionsEnabled() && shouldOfferToShowSuggestions(); boolean replaceItemExists = menu.findItem(TextView.ID_REPLACE) != null; if (canReplace && !replaceItemExists) { menu.add(0, TextView.ID_REPLACE, 0, com.android.internal.R.string.replace). @@ -4025,9 +4056,9 @@ public class Editor { positionCursor = true; } else if (offset + mTouchWordOffset < mPreviousOffset) { // User is shrinking the selection. - if (currLine > mPrevLine) { + if (currLine < mPrevLine) { // We're on a different line, so we'll snap to word boundaries. - offset = getWordStart(offset); + offset = start; } offset += mTouchWordOffset; positionCursor = true; diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java index 9ecdc9c..c959774 100644 --- a/core/java/android/widget/GridView.java +++ b/core/java/android/widget/GridView.java @@ -1854,20 +1854,19 @@ public class GridView extends AbsListView { moved = true; } break; - case FOCUS_LEFT: - if (selectedPosition > startOfRowPos) { - mLayoutMode = LAYOUT_MOVE_SELECTION; - setSelectionInt(Math.max(0, selectedPosition - 1)); - moved = true; - } - break; - case FOCUS_RIGHT: - if (selectedPosition < endOfRowPos) { - mLayoutMode = LAYOUT_MOVE_SELECTION; - setSelectionInt(Math.min(selectedPosition + 1, mItemCount - 1)); - moved = true; - } - break; + } + + final boolean isLayoutRtl = isLayoutRtl(); + if (selectedPosition > startOfRowPos && ((direction == FOCUS_LEFT && !isLayoutRtl) || + (direction == FOCUS_RIGHT && isLayoutRtl))) { + mLayoutMode = LAYOUT_MOVE_SELECTION; + setSelectionInt(Math.max(0, selectedPosition - 1)); + moved = true; + } else if (selectedPosition < endOfRowPos && ((direction == FOCUS_LEFT && isLayoutRtl) || + (direction == FOCUS_RIGHT && !isLayoutRtl))) { + mLayoutMode = LAYOUT_MOVE_SELECTION; + setSelectionInt(Math.min(selectedPosition + 1, mItemCount - 1)); + moved = true; } if (moved) { diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java index a50941b..10e4db3 100644 --- a/core/java/android/widget/RemoteViewsAdapter.java +++ b/core/java/android/widget/RemoteViewsAdapter.java @@ -1335,10 +1335,6 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback // If we are not connected, queue up the notifyDataSetChanged to be handled when we do // connect if (!mServiceConnection.isConnected()) { - if (mNotifyDataSetChangedAfterOnServiceConnected) { - return; - } - mNotifyDataSetChangedAfterOnServiceConnected = true; requestBindService(); return; diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java index 0249c22..2778f0f 100644 --- a/core/java/android/widget/SimpleMonthView.java +++ b/core/java/android/widget/SimpleMonthView.java @@ -162,7 +162,6 @@ class SimpleMonthView extends View { mTitleFormatter = new SimpleDateFormat(titleFormat, locale); mDayOfWeekFormatter = new SimpleDateFormat(DAY_OF_WEEK_FORMAT, locale); - setClickable(true); initPaints(res); } @@ -318,7 +317,8 @@ class SimpleMonthView extends View { final int x = (int) (event.getX() + 0.5f); final int y = (int) (event.getY() + 0.5f); - switch (event.getAction()) { + final int action = event.getAction(); + switch (action) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: final int touchedItem = getDayAtLocation(x, y); @@ -326,6 +326,10 @@ class SimpleMonthView extends View { mTouchedItem = touchedItem; invalidate(); } + if (action == MotionEvent.ACTION_DOWN && touchedItem < 0) { + // Touch something that's not an item, reject event. + return false; + } break; case MotionEvent.ACTION_UP: @@ -376,9 +380,16 @@ class SimpleMonthView extends View { for (int col = 0; col < DAYS_IN_WEEK; col++) { final int colCenter = colWidth * col + colWidth / 2; + final int colCenterRtl; + if (isLayoutRtl()) { + colCenterRtl = mPaddedWidth - colCenter; + } else { + colCenterRtl = colCenter; + } + final int dayOfWeek = (col + mWeekStart) % DAYS_IN_WEEK; final String label = getDayOfWeekLabel(dayOfWeek); - canvas.drawText(label, colCenter, rowCenter - halfLineHeight, p); + canvas.drawText(label, colCenterRtl, rowCenter - halfLineHeight, p); } } @@ -402,6 +413,13 @@ class SimpleMonthView extends View { for (int day = 1, col = findDayOffset(); day <= mDaysInMonth; day++) { final int colCenter = colWidth * col + colWidth / 2; + final int colCenterRtl; + if (isLayoutRtl()) { + colCenterRtl = mPaddedWidth - colCenter; + } else { + colCenterRtl = colCenter; + } + int stateMask = 0; if (day >= mEnabledDayStart && day <= mEnabledDayEnd) { @@ -413,12 +431,12 @@ class SimpleMonthView extends View { stateMask |= StateSet.VIEW_STATE_ACTIVATED; // Adjust the circle to be centered on the row. - canvas.drawCircle(colCenter, rowCenter, mDaySelectorRadius, mDaySelectorPaint); + canvas.drawCircle(colCenterRtl, rowCenter, mDaySelectorRadius, mDaySelectorPaint); } else if (mTouchedItem == day) { stateMask |= StateSet.VIEW_STATE_PRESSED; // Adjust the circle to be centered on the row. - canvas.drawCircle(colCenter, rowCenter, mDaySelectorRadius, mDayHighlightPaint); + canvas.drawCircle(colCenterRtl, rowCenter, mDaySelectorRadius, mDayHighlightPaint); } final boolean isDayToday = mToday == day; @@ -431,7 +449,7 @@ class SimpleMonthView extends View { } p.setColor(dayTextColor); - canvas.drawText(Integer.toString(day), colCenter, rowCenter - halfLineHeight, p); + canvas.drawText(Integer.toString(day), colCenterRtl, rowCenter - halfLineHeight, p); col++; @@ -583,6 +601,13 @@ class SimpleMonthView extends View { } @Override + public void onRtlPropertiesChanged(@ResolvedLayoutDir int layoutDirection) { + super.onRtlPropertiesChanged(layoutDirection); + + requestLayout(); + } + + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if (!changed) { return; @@ -657,8 +682,16 @@ class SimpleMonthView extends View { return -1; } + // Adjust for RTL after applying padding. + final int paddedXRtl; + if (isLayoutRtl()) { + paddedXRtl = mPaddedWidth - paddedX; + } else { + paddedXRtl = paddedX; + } + final int row = (paddedY - headerHeight) / mDayHeight; - final int col = (paddedX * DAYS_IN_WEEK) / mPaddedWidth; + final int col = (paddedXRtl * DAYS_IN_WEEK) / mPaddedWidth; final int index = col + row * DAYS_IN_WEEK; final int day = index + 1 - findDayOffset(); if (day < 1 || day > mDaysInMonth) { @@ -681,10 +714,15 @@ class SimpleMonthView extends View { final int index = id - 1 + findDayOffset(); - // Compute left edge. + // Compute left edge, taking into account RTL. final int col = index % DAYS_IN_WEEK; final int colWidth = mCellWidth; - final int left = getPaddingLeft() + col * colWidth; + final int left; + if (isLayoutRtl()) { + left = getWidth() - getPaddingRight() - (col + 1) * colWidth; + } else { + left = getPaddingLeft() + col * colWidth; + } // Compute top edge. final int row = index / DAYS_IN_WEEK; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 726b89a..3e8df08 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -8955,13 +8955,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener static final int ID_CUT = android.R.id.cut; static final int ID_COPY = android.R.id.copy; static final int ID_PASTE = android.R.id.paste; + static final int ID_SHARE = android.R.id.shareText; static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText; static final int ID_REPLACE = android.R.id.replaceText; /** * Called when a context menu option for the text view is selected. Currently * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut}, - * {@link android.R.id#copy} or {@link android.R.id#paste}. + * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}. * * @return true if the context menu item action was performed. */ @@ -9014,6 +9015,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max))); stopSelectionActionMode(); return true; + + case ID_SHARE: + shareSelectedText(); + return true; } return false; } @@ -9091,15 +9096,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * If provided, this ActionMode.Callback will be used to create the ActionMode when text * selection is initiated in this View. * - * The standard implementation populates the menu with a subset of Select All, Cut, Copy and - * Paste actions, depending on what this View supports. + * The standard implementation populates the menu with a subset of Select All, Cut, Copy, + * Paste and Share actions, depending on what this View supports. * * A custom implementation can add new entries in the default menu in its * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The * default actions can also be removed from the menu using * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, - * {@link android.R.id#cut}, {@link android.R.id#copy} or {@link android.R.id#paste} ids as - * parameters. + * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste} or + * {@link android.R.id#shareText} ids as parameters. * * Returning false from * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent @@ -9168,6 +9173,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return false; } + boolean canShare() { + return canCopy(); + } + boolean canPaste() { return (mText instanceof Editable && mEditor != null && mEditor.mKeyListener != null && @@ -9241,6 +9250,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + private void shareSelectedText() { + String selectedText = getSelectedText(); + if (selectedText != null && !selectedText.isEmpty()) { + Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); + sharingIntent.setType("text/plain"); + sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT); + sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText); + getContext().startActivity(Intent.createChooser(sharingIntent, null)); + stopSelectionActionMode(); + } + } + private void setPrimaryClip(ClipData clip) { ClipboardManager clipboard = (ClipboardManager) getContext(). getSystemService(Context.CLIPBOARD_SERVICE); diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java index f7e9648..c0c8aec 100644 --- a/core/java/android/widget/ZoomButtonsController.java +++ b/core/java/android/widget/ZoomButtonsController.java @@ -403,7 +403,7 @@ public class ZoomButtonsController implements View.OnTouchListener { // No longer care about configuration changes mContext.unregisterReceiver(mConfigurationChangedReceiver); - mWindowManager.removeView(mContainer); + mWindowManager.removeViewImmediate(mContainer); mHandler.removeCallbacks(mPostedVisibleInitializer); if (mCallback != null) { @@ -490,7 +490,7 @@ public class ZoomButtonsController implements View.OnTouchListener { setVisible(false); return true; } - + } else { dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT); } diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 8403e77..a6c39e6 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -401,6 +401,11 @@ public class ChooserActivity extends ResolverActivity { } @Override + public boolean shouldGetResolvedFilter() { + return true; + } + + @Override public int getCount() { int count = super.getCount(); if (mServiceTargets != null) { diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 3cd69a1..7f51d92 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -1062,7 +1062,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } else { currentResolveList = mOrigResolveList = mPm.queryIntentActivities(mIntent, PackageManager.MATCH_DEFAULT_ONLY - | (mFilterLastUsed ? PackageManager.GET_RESOLVED_FILTER : 0) + | (shouldGetResolvedFilter() ? PackageManager.GET_RESOLVED_FILTER : 0) | (shouldGetActivityMetadata() ? PackageManager.GET_META_DATA : 0) ); // Filter out any activities that the launched uid does not @@ -1188,6 +1188,10 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic // This space for rent } + public boolean shouldGetResolvedFilter() { + return mFilterLastUsed; + } + private void processGroup(List<ResolveInfo> rList, int start, int end, ResolveInfo ro, CharSequence roLabel) { // Process labels from start to i diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 48f0e71..5ed4f70 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -269,11 +269,11 @@ public class ZygoteInit { if (false) { Log.v(TAG, "Preloading " + line + "..."); } - // Load and explicitly initialize the given class. Use the tree-argument version - // of forName to avoid repeated stack lookups (to derive the caller's - // class-loader). Use true to force initialization, and null for the boot - // classpath class-loader (could as well cache the class-loader of this class in - // a variable). + // Load and explicitly initialize the given class. Use + // Class.forName(String, boolean, ClassLoader) to avoid repeated stack lookups + // (to derive the caller's class-loader). Use true to force initialization, and + // null for the boot classpath class-loader (could as well cache the + // class-loader of this class in a variable). Class.forName(line, true, null); count++; } catch (ClassNotFoundException e) { diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java index 414b7bc..b692a18 100644 --- a/core/java/com/android/internal/util/Preconditions.java +++ b/core/java/com/android/internal/util/Preconditions.java @@ -24,6 +24,12 @@ import java.util.Collection; */ public class Preconditions { + public static void checkArgument(boolean expression) { + if (!expression) { + throw new IllegalArgumentException(); + } + } + /** * Ensures that an object reference passed as a parameter to the calling * method is not null. diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java index a14e98d..3a1e0ca 100644 --- a/core/java/com/android/internal/widget/FloatingToolbar.java +++ b/core/java/com/android/internal/widget/FloatingToolbar.java @@ -346,6 +346,17 @@ public final class FloatingToolbar { }; private final Region mTouchableRegion = new Region(); + private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = + new ViewTreeObserver.OnComputeInternalInsetsListener() { + public void onComputeInternalInsets( + ViewTreeObserver.InternalInsetsInfo info) { + info.contentInsets.setEmpty(); + info.visibleInsets.setEmpty(); + info.touchableRegion.set(mTouchableRegion); + info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo + .TOUCHABLE_INSETS_REGION); + } + }; private boolean mDismissed = true; // tracks whether this popup is dismissed or dismissing. private boolean mHidden; // tracks whether this popup is hidden or hiding. @@ -382,21 +393,6 @@ public final class FloatingToolbar { mPopupWindow.dismiss(); } }); - // Make the touchable area of this popup be the area specified by mTouchableRegion. - mPopupWindow.getContentView() - .getRootView() - .getViewTreeObserver() - .addOnComputeInternalInsetsListener( - new ViewTreeObserver.OnComputeInternalInsetsListener() { - public void onComputeInternalInsets( - ViewTreeObserver.InternalInsetsInfo info) { - info.contentInsets.setEmpty(); - info.visibleInsets.setEmpty(); - info.touchableRegion.set(mTouchableRegion); - info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo - .TOUCHABLE_INSETS_REGION); - } - }); mMarginHorizontal = parent.getResources() .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin); mMarginVertical = parent.getResources() @@ -437,16 +433,15 @@ public final class FloatingToolbar { mHidden = false; mDismissed = false; - cancelAllAnimations(); + cancelDismissAndHideAnimations(); + cancelOverflowAnimations(); // Make sure a panel is set as the content. if (mContentContainer.getChildCount() == 0) { setMainPanelAsContent(); } preparePopupContent(); - // If we're yet to show the popup, set the container visibility to zero. - // The "show" animation will make this visible. - mContentContainer.setAlpha(0); mPopupWindow.showAtLocation(mParent, Gravity.NO_GRAVITY, x, y); + setTouchableSurfaceInsetsComputer(); runShowAnimation(); } @@ -454,12 +449,13 @@ public final class FloatingToolbar { * Gets rid of this popup. If the popup isn't currently showing, this will be a no-op. */ public void dismiss() { - if (!isShowing()) { + if (mDismissed) { return; } mHidden = false; mDismissed = true; + mHideAnimation.cancel(); runDismissAnimation(); setZeroTouchableSurface(); } @@ -502,7 +498,7 @@ public final class FloatingToolbar { return; } - cancelAllAnimations(); + cancelOverflowAnimations(); preparePopupContent(); mPopupWindow.update(x, y, getWidth(), getHeight()); } @@ -566,10 +562,12 @@ public final class FloatingToolbar { mHideAnimation.start(); } - private void cancelAllAnimations() { - mShowAnimation.cancel(); + private void cancelDismissAndHideAnimations() { mDismissAnimation.cancel(); mHideAnimation.cancel(); + } + + private void cancelOverflowAnimations() { mOpenOverflowAnimation.cancel(); mCloseOverflowAnimation.cancel(); } @@ -804,6 +802,19 @@ public final class FloatingToolbar { (int) mContentContainer.getX() + width, (int) mContentContainer.getY() + height); } + + /** + * Make the touchable area of this popup be the area specified by mTouchableRegion. + * This should be called after the popup window has been dismissed (dismiss/hide) + * and is probably being re-shown with a new content root view. + */ + private void setTouchableSurfaceInsetsComputer() { + ViewTreeObserver viewTreeObserver = mPopupWindow.getContentView() + .getRootView() + .getViewTreeObserver(); + viewTreeObserver.removeOnComputeInternalInsetsListener(mInsetsComputer); + viewTreeObserver.addOnComputeInternalInsetsListener(mInsetsComputer); + } } /** diff --git a/core/java/com/android/internal/widget/SwipeDismissLayout.java b/core/java/com/android/internal/widget/SwipeDismissLayout.java index 89990c2..191662c 100644 --- a/core/java/com/android/internal/widget/SwipeDismissLayout.java +++ b/core/java/com/android/internal/widget/SwipeDismissLayout.java @@ -111,7 +111,7 @@ public class SwipeDismissLayout extends FrameLayout { } private void init(Context context) { - ViewConfiguration vc = ViewConfiguration.get(getContext()); + ViewConfiguration vc = ViewConfiguration.get(context); mSlop = vc.getScaledTouchSlop(); mMinFlingVelocity = vc.getScaledMinimumFlingVelocity(); mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); @@ -290,7 +290,7 @@ public class SwipeDismissLayout extends FrameLayout { float deltaX = ev.getRawX() - mDownX; float deltaY = ev.getRawY() - mDownY; if ((deltaX * deltaX) + (deltaY * deltaY) > mSlop * mSlop) { - mSwiping = deltaX > mSlop * 2 && Math.abs(deltaY) < mSlop * 2; + mSwiping = deltaX > mSlop * 2 && Math.abs(deltaY) < Math.abs(deltaX); } else { mSwiping = false; } @@ -299,9 +299,9 @@ public class SwipeDismissLayout extends FrameLayout { private void updateDismiss(MotionEvent ev) { float deltaX = ev.getRawX() - mDownX; + mVelocityTracker.addMovement(ev); + mVelocityTracker.computeCurrentVelocity(1000); if (!mDismissed) { - mVelocityTracker.addMovement(ev); - mVelocityTracker.computeCurrentVelocity(1000); if (deltaX > (getWidth() * DISMISS_MIN_DRAG_WIDTH_RATIO) && ev.getRawX() >= mLastX) { @@ -311,7 +311,9 @@ public class SwipeDismissLayout extends FrameLayout { // Check if the user tried to undo this. if (mDismissed && mSwiping) { // Check if the user's finger is actually back - if (deltaX < (getWidth() * DISMISS_MIN_DRAG_WIDTH_RATIO)) { + if (deltaX < (getWidth() * DISMISS_MIN_DRAG_WIDTH_RATIO) || + // or user is flinging back left + mVelocityTracker.getXVelocity() < -mMinFlingVelocity) { mDismissed = false; } } diff --git a/core/java/com/android/internal/widget/ViewPager.java b/core/java/com/android/internal/widget/ViewPager.java index 5c08daf..441e640 100644 --- a/core/java/com/android/internal/widget/ViewPager.java +++ b/core/java/com/android/internal/widget/ViewPager.java @@ -27,9 +27,9 @@ import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; -import android.os.SystemClock; import android.util.AttributeSet; import android.util.Log; +import android.util.MathUtils; import android.view.FocusFinder; import android.view.Gravity; import android.view.KeyEvent; @@ -43,7 +43,6 @@ import android.view.ViewParent; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; -import android.view.accessibility.AccessibilityRecord; import android.view.animation.Interpolator; import android.widget.EdgeEffect; import android.widget.Scroller; @@ -84,8 +83,9 @@ import java.util.Comparator; */ public class ViewPager extends ViewGroup { private static final String TAG = "ViewPager"; - private static final boolean DEBUG = false; + private static final boolean DEBUG = true; + private static final int MAX_SCROLL_X = 2 << 23; private static final boolean USE_CACHE = false; private static final int DEFAULT_OFFSCREEN_PAGES = 1; @@ -108,9 +108,13 @@ public class ViewPager extends ViewGroup { static class ItemInfo { Object object; - int position; boolean scrolling; float widthFactor; + + /** Logical position of the item within the pager adapter. */ + int position; + + /** Offset between the starting edges of the item and its container. */ float offset; } @@ -146,6 +150,12 @@ public class ViewPager extends ViewGroup { private int mTopPageBounds; private int mBottomPageBounds; + /** + * The increment used to move in the "left" direction. Dependent on layout + * direction. + */ + private int mLeftIncr = -1; + // Offsets of the first and last items, if known. // Set during population, used to determine if we are at the beginning // or end of the pager data set during touch scrolling. @@ -198,14 +208,10 @@ public class ViewPager extends ViewGroup { // "catching" the flinging pager. private static final int CLOSE_ENOUGH = 2; // dp - private boolean mFakeDragging; - private long mFakeDragBeginTime; - private final EdgeEffect mLeftEdge; private final EdgeEffect mRightEdge; private boolean mFirstLayout = true; - private boolean mNeedCalculatePageOffsets = false; private boolean mCalledSuper; private int mDecorChildCount; @@ -473,7 +479,7 @@ public class ViewPager extends ViewGroup { mAdapterChangeListener = listener; } - private int getClientWidth() { + private int getPaddedWidth() { return getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); } @@ -504,36 +510,33 @@ public class ViewPager extends ViewGroup { return mCurItem; } - void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) { - setCurrentItemInternal(item, smoothScroll, always, 0); + boolean setCurrentItemInternal(int item, boolean smoothScroll, boolean always) { + return setCurrentItemInternal(item, smoothScroll, always, 0); } - void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) { + boolean setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) { if (mAdapter == null || mAdapter.getCount() <= 0) { setScrollingCacheEnabled(false); - return; + return false; } + + item = MathUtils.constrain(item, 0, mAdapter.getCount() - 1); if (!always && mCurItem == item && mItems.size() != 0) { setScrollingCacheEnabled(false); - return; + return false; } - if (item < 0) { - item = 0; - } else if (item >= mAdapter.getCount()) { - item = mAdapter.getCount() - 1; - } final int pageLimit = mOffscreenPageLimit; if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) { // We are doing a jump by more than one page. To avoid // glitches, we want to keep all current pages in the view // until the scroll ends. - for (int i=0; i<mItems.size(); i++) { + for (int i = 0; i < mItems.size(); i++) { mItems.get(i).scrolling = true; } } - final boolean dispatchSelected = mCurItem != item; + final boolean dispatchSelected = mCurItem != item; if (mFirstLayout) { // We don't have any idea how big we are yet and shouldn't have any pages either. // Just set things up and let the pending layout handle things. @@ -549,38 +552,55 @@ public class ViewPager extends ViewGroup { populate(item); scrollToItem(item, smoothScroll, velocity, dispatchSelected); } + + return true; } - private void scrollToItem(int item, boolean smoothScroll, int velocity, + private void scrollToItem(int position, boolean smoothScroll, int velocity, boolean dispatchSelected) { - final ItemInfo curInfo = infoForPosition(item); - int destX = 0; - if (curInfo != null) { - final int width = getClientWidth(); - destX = (int) (width * Math.max(mFirstOffset, - Math.min(curInfo.offset, mLastOffset))); - } + final int destX = getLeftEdgeForItem(position); + if (smoothScroll) { smoothScrollTo(destX, 0, velocity); + if (dispatchSelected && mOnPageChangeListener != null) { - mOnPageChangeListener.onPageSelected(item); + mOnPageChangeListener.onPageSelected(position); } if (dispatchSelected && mInternalPageChangeListener != null) { - mInternalPageChangeListener.onPageSelected(item); + mInternalPageChangeListener.onPageSelected(position); } } else { if (dispatchSelected && mOnPageChangeListener != null) { - mOnPageChangeListener.onPageSelected(item); + mOnPageChangeListener.onPageSelected(position); } if (dispatchSelected && mInternalPageChangeListener != null) { - mInternalPageChangeListener.onPageSelected(item); + mInternalPageChangeListener.onPageSelected(position); } + completeScroll(false); scrollTo(destX, 0); pageScrolled(destX); } } + private int getLeftEdgeForItem(int position) { + final ItemInfo info = infoForPosition(position); + if (info == null) { + return 0; + } + + final int width = getPaddedWidth(); + final int scaledOffset = (int) (width * MathUtils.constrain( + info.offset, mFirstOffset, mLastOffset)); + + if (isLayoutRtl()) { + final int itemWidth = (int) (width * info.widthFactor + 0.5f); + return MAX_SCROLL_X - itemWidth - scaledOffset; + } else { + return scaledOffset; + } + } + /** * Set a listener that will be invoked whenever the page changes or is incrementally * scrolled. See {@link OnPageChangeListener}. @@ -784,7 +804,7 @@ public class ViewPager extends ViewGroup { setScrollingCacheEnabled(true); setScrollState(SCROLL_STATE_SETTLING); - final int width = getClientWidth(); + final int width = getPaddedWidth(); final int halfWidth = width / 2; final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width); final float distance = halfWidth + halfWidth * @@ -968,7 +988,7 @@ public class ViewPager extends ViewGroup { float extraWidthLeft = 0.f; int itemIndex = curIndex - 1; ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; - final int clientWidth = getClientWidth(); + final int clientWidth = getPaddedWidth(); final float leftWidthNeeded = clientWidth <= 0 ? 0 : 2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth; for (int pos = mCurItem - 1; pos >= 0; pos--) { @@ -981,7 +1001,7 @@ public class ViewPager extends ViewGroup { mAdapter.destroyItem(this, pos, ii.object); if (DEBUG) { Log.i(TAG, "populate() - destroyItem() with pos: " + pos + - " view: " + ((View) ii.object)); + " view: " + ii.object); } itemIndex--; curIndex--; @@ -1015,7 +1035,7 @@ public class ViewPager extends ViewGroup { mAdapter.destroyItem(this, pos, ii.object); if (DEBUG) { Log.i(TAG, "populate() - destroyItem() with pos: " + pos + - " view: " + ((View) ii.object)); + " view: " + ii.object); } ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; } @@ -1099,49 +1119,51 @@ public class ViewPager extends ViewGroup { private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) { final int N = mAdapter.getCount(); - final int width = getClientWidth(); + final int width = getPaddedWidth(); final float marginOffset = width > 0 ? (float) mPageMargin / width : 0; + // Fix up offsets for later layout. if (oldCurInfo != null) { final int oldCurPosition = oldCurInfo.position; + // Base offsets off of oldCurInfo. if (oldCurPosition < curItem.position) { int itemIndex = 0; - ItemInfo ii = null; float offset = oldCurInfo.offset + oldCurInfo.widthFactor + marginOffset; - for (int pos = oldCurPosition + 1; - pos <= curItem.position && itemIndex < mItems.size(); pos++) { - ii = mItems.get(itemIndex); + for (int pos = oldCurPosition + 1; pos <= curItem.position && itemIndex < mItems.size(); pos++) { + ItemInfo ii = mItems.get(itemIndex); while (pos > ii.position && itemIndex < mItems.size() - 1) { itemIndex++; ii = mItems.get(itemIndex); } + while (pos < ii.position) { // We don't have an item populated for this, // ask the adapter for an offset. offset += mAdapter.getPageWidth(pos) + marginOffset; pos++; } + ii.offset = offset; offset += ii.widthFactor + marginOffset; } } else if (oldCurPosition > curItem.position) { int itemIndex = mItems.size() - 1; - ItemInfo ii = null; float offset = oldCurInfo.offset; - for (int pos = oldCurPosition - 1; - pos >= curItem.position && itemIndex >= 0; pos--) { - ii = mItems.get(itemIndex); + for (int pos = oldCurPosition - 1; pos >= curItem.position && itemIndex >= 0; pos--) { + ItemInfo ii = mItems.get(itemIndex); while (pos < ii.position && itemIndex > 0) { itemIndex--; ii = mItems.get(itemIndex); } + while (pos > ii.position) { // We don't have an item populated for this, // ask the adapter for an offset. offset -= mAdapter.getPageWidth(pos) + marginOffset; pos--; } + offset -= ii.widthFactor + marginOffset; ii.offset = offset; } @@ -1155,6 +1177,7 @@ public class ViewPager extends ViewGroup { mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE; mLastOffset = curItem.position == N - 1 ? curItem.offset + curItem.widthFactor - 1 : Float.MAX_VALUE; + // Previous pages for (int i = curIndex - 1; i >= 0; i--, pos--) { final ItemInfo ii = mItems.get(i); @@ -1165,8 +1188,10 @@ public class ViewPager extends ViewGroup { ii.offset = offset; if (ii.position == 0) mFirstOffset = offset; } + offset = curItem.offset + curItem.widthFactor + marginOffset; pos = curItem.position + 1; + // Next pages for (int i = curIndex + 1; i < itemCount; i++, pos++) { final ItemInfo ii = mItems.get(i); @@ -1179,8 +1204,6 @@ public class ViewPager extends ViewGroup { ii.offset = offset; offset += ii.widthFactor + marginOffset; } - - mNeedCalculatePageOffsets = false; } /** @@ -1546,34 +1569,47 @@ public class ViewPager extends ViewGroup { // Page views. Do this once we have the right padding offsets from above. for (int i = 0; i < count; i++) { final View child = getChildAt(i); - if (child.getVisibility() != GONE) { - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - ItemInfo ii; - if (!lp.isDecor && (ii = infoForChild(child)) != null) { - int loff = (int) (childWidth * ii.offset); - int childLeft = paddingLeft + loff; - int childTop = paddingTop; - if (lp.needsMeasure) { - // This was added during layout and needs measurement. - // Do it now that we know what we're working with. - lp.needsMeasure = false; - final int widthSpec = MeasureSpec.makeMeasureSpec( - (int) (childWidth * lp.widthFactor), - MeasureSpec.EXACTLY); - final int heightSpec = MeasureSpec.makeMeasureSpec( - (int) (height - paddingTop - paddingBottom), - MeasureSpec.EXACTLY); - child.measure(widthSpec, heightSpec); - } - if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object - + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth() - + "x" + child.getMeasuredHeight()); - child.layout(childLeft, childTop, - childLeft + child.getMeasuredWidth(), - childTop + child.getMeasuredHeight()); - } + if (child.getVisibility() == GONE) { + continue; } + + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (lp.isDecor) { + continue; + } + + final ItemInfo ii = infoForChild(child); + if (ii == null) { + continue; + } + + if (lp.needsMeasure) { + // This was added during layout and needs measurement. + // Do it now that we know what we're working with. + lp.needsMeasure = false; + final int widthSpec = MeasureSpec.makeMeasureSpec( + (int) (childWidth * lp.widthFactor), + MeasureSpec.EXACTLY); + final int heightSpec = MeasureSpec.makeMeasureSpec( + (int) (height - paddingTop - paddingBottom), + MeasureSpec.EXACTLY); + child.measure(widthSpec, heightSpec); + } + + final int childMeasuredWidth = child.getMeasuredWidth(); + final int startOffset = (int) (childWidth * ii.offset); + final int childLeft; + if (isLayoutRtl()) { + childLeft = MAX_SCROLL_X - paddingRight - startOffset - childMeasuredWidth; + } else { + childLeft = paddingLeft + startOffset; + } + + final int childTop = paddingTop; + child.layout(childLeft, childTop, childLeft + childMeasuredWidth, + childTop + child.getMeasuredHeight()); } + mTopPageBounds = paddingTop; mBottomPageBounds = height - paddingBottom; mDecorChildCount = decorCount; @@ -1587,13 +1623,14 @@ public class ViewPager extends ViewGroup { @Override public void computeScroll() { if (!mScroller.isFinished() && mScroller.computeScrollOffset()) { - int oldX = getScrollX(); - int oldY = getScrollY(); - int x = mScroller.getCurrX(); - int y = mScroller.getCurrY(); + final int oldX = getScrollX(); + final int oldY = getScrollY(); + final int x = mScroller.getCurrX(); + final int y = mScroller.getCurrY(); if (oldX != x || oldY != y) { scrollTo(x, y); + if (!pageScrolled(x)) { mScroller.abortAnimation(); scrollTo(0, y); @@ -1609,7 +1646,7 @@ public class ViewPager extends ViewGroup { completeScroll(true); } - private boolean pageScrolled(int xpos) { + private boolean pageScrolled(int scrollX) { if (mItems.size() == 0) { mCalledSuper = false; onPageScrolled(0, 0, 0); @@ -1619,12 +1656,21 @@ public class ViewPager extends ViewGroup { } return false; } - final ItemInfo ii = infoForCurrentScrollPosition(); - final int width = getClientWidth(); + + // Translate to scrollX to scrollStart for RTL. + final int scrollStart; + if (isLayoutRtl()) { + scrollStart = MAX_SCROLL_X - scrollX; + } else { + scrollStart = scrollX; + } + + final ItemInfo ii = infoForFirstVisiblePage(); + final int width = getPaddedWidth(); final int widthWithMargin = width + mPageMargin; final float marginOffset = (float) mPageMargin / width; final int currentPage = ii.position; - final float pageOffset = (((float) xpos / width) - ii.offset) / + final float pageOffset = (((float) scrollStart / width) - ii.offset) / (ii.widthFactor + marginOffset); final int offsetPixels = (int) (pageOffset * widthWithMargin); @@ -1706,7 +1752,7 @@ public class ViewPager extends ViewGroup { if (lp.isDecor) continue; - final float transformPos = (float) (child.getLeft() - scrollX) / getClientWidth(); + final float transformPos = (float) (child.getLeft() - scrollX) / getPaddedWidth(); mPageTransformer.transformPage(child, transformPos); } } @@ -1785,11 +1831,11 @@ public class ViewPager extends ViewGroup { // are dragging. if (action != MotionEvent.ACTION_DOWN) { if (mIsBeingDragged) { - if (DEBUG) Log.v(TAG, "Intercept returning true!"); + if (DEBUG) Log.v(TAG, "Being dragged, intercept returning true!"); return true; } if (mIsUnableToDrag) { - if (DEBUG) Log.v(TAG, "Intercept returning false!"); + if (DEBUG) Log.v(TAG, "Unable to drag, intercept returning false!"); return false; } } @@ -1903,13 +1949,6 @@ public class ViewPager extends ViewGroup { @Override public boolean onTouchEvent(MotionEvent ev) { - if (mFakeDragging) { - // A fake drag is in progress already, ignore this real one - // but still eat the touch events. - // (It is likely that the user is multi-touching the screen.) - return true; - } - if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) { // Don't handle edge touches immediately -- they may actually belong to one of our // descendants. @@ -1978,19 +2017,26 @@ public class ViewPager extends ViewGroup { if (mIsBeingDragged) { final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); - int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId); + final int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId); + mPopulatePending = true; - final int width = getClientWidth(); - final int scrollX = getScrollX(); - final ItemInfo ii = infoForCurrentScrollPosition(); + + final float scrollStart = getScrollStart(); + final float scrolledPages = scrollStart / getPaddedWidth(); + final ItemInfo ii = infoForFirstVisiblePage(); final int currentPage = ii.position; - final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor; - final int activePointerIndex = - ev.findPointerIndex(mActivePointerId); + final float nextPageOffset; + if (isLayoutRtl()) { + nextPageOffset = (ii.offset - scrolledPages) / ii.widthFactor; + } else { + nextPageOffset = (scrolledPages - ii.offset) / ii.widthFactor; + } + + final int activePointerIndex = ev.findPointerIndex(mActivePointerId); final float x = ev.getX(activePointerIndex); final int totalDelta = (int) (x - mInitialMotionX); - int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity, - totalDelta); + final int nextPage = determineTargetPage( + currentPage, nextPageOffset, initialVelocity, totalDelta); setCurrentItemInternal(nextPage, true, true, initialVelocity); mActivePointerId = INVALID_POINTER; @@ -2038,48 +2084,79 @@ public class ViewPager extends ViewGroup { private boolean performDrag(float x) { boolean needsInvalidate = false; + final int width = getPaddedWidth(); final float deltaX = mLastMotionX - x; mLastMotionX = x; - float oldScrollX = getScrollX(); - float scrollX = oldScrollX + deltaX; - final int width = getClientWidth(); + final EdgeEffect startEdge; + final EdgeEffect endEdge; + if (isLayoutRtl()) { + startEdge = mRightEdge; + endEdge = mLeftEdge; + } else { + startEdge = mLeftEdge; + endEdge = mRightEdge; + } - float leftBound = width * mFirstOffset; - float rightBound = width * mLastOffset; - boolean leftAbsolute = true; - boolean rightAbsolute = true; + // Translate scroll to relative coordinates. + final float nextScrollX = getScrollX() + deltaX; + final float scrollStart; + if (isLayoutRtl()) { + scrollStart = MAX_SCROLL_X - nextScrollX; + } else { + scrollStart = nextScrollX; + } - final ItemInfo firstItem = mItems.get(0); - final ItemInfo lastItem = mItems.get(mItems.size() - 1); - if (firstItem.position != 0) { - leftAbsolute = false; - leftBound = firstItem.offset * width; + final float startBound; + final ItemInfo startItem = mItems.get(0); + final boolean startAbsolute = startItem.position == 0; + if (startAbsolute) { + startBound = startItem.offset * width; + } else { + startBound = width * mFirstOffset; } - if (lastItem.position != mAdapter.getCount() - 1) { - rightAbsolute = false; - rightBound = lastItem.offset * width; + + final float endBound; + final ItemInfo endItem = mItems.get(mItems.size() - 1); + final boolean endAbsolute = endItem.position == mAdapter.getCount() - 1; + if (endAbsolute) { + endBound = endItem.offset * width; + } else { + endBound = width * mLastOffset; } - if (scrollX < leftBound) { - if (leftAbsolute) { - float over = leftBound - scrollX; - mLeftEdge.onPull(Math.abs(over) / width); + final float clampedScrollStart; + if (scrollStart < startBound) { + if (startAbsolute) { + final float over = startBound - scrollStart; + startEdge.onPull(Math.abs(over) / width); needsInvalidate = true; } - scrollX = leftBound; - } else if (scrollX > rightBound) { - if (rightAbsolute) { - float over = scrollX - rightBound; - mRightEdge.onPull(Math.abs(over) / width); + clampedScrollStart = startBound; + } else if (scrollStart > endBound) { + if (endAbsolute) { + final float over = scrollStart - endBound; + endEdge.onPull(Math.abs(over) / width); needsInvalidate = true; } - scrollX = rightBound; + clampedScrollStart = endBound; + } else { + clampedScrollStart = scrollStart; } - // Don't lose the rounded component - mLastMotionX += scrollX - (int) scrollX; - scrollTo((int) scrollX, getScrollY()); - pageScrolled((int) scrollX); + + // Translate back to absolute coordinates. + final float targetScrollX; + if (isLayoutRtl()) { + targetScrollX = MAX_SCROLL_X - clampedScrollStart; + } else { + targetScrollX = clampedScrollStart; + } + + // Don't lose the rounded component. + mLastMotionX += targetScrollX - (int) targetScrollX; + + scrollTo((int) targetScrollX, getScrollY()); + pageScrolled((int) targetScrollX); return needsInvalidate; } @@ -2088,19 +2165,23 @@ public class ViewPager extends ViewGroup { * @return Info about the page at the current scroll position. * This can be synthetic for a missing middle page; the 'object' field can be null. */ - private ItemInfo infoForCurrentScrollPosition() { - final int width = getClientWidth(); - final float scrollOffset = width > 0 ? (float) getScrollX() / width : 0; + private ItemInfo infoForFirstVisiblePage() { + final int startOffset = getScrollStart(); + final int width = getPaddedWidth(); + final float scrollOffset = width > 0 ? (float) startOffset / width : 0; final float marginOffset = width > 0 ? (float) mPageMargin / width : 0; + int lastPos = -1; float lastOffset = 0.f; float lastWidth = 0.f; boolean first = true; - ItemInfo lastItem = null; - for (int i = 0; i < mItems.size(); i++) { + + final int N = mItems.size(); + for (int i = 0; i < N; i++) { ItemInfo ii = mItems.get(i); - float offset; + + // Seek to position. if (!first && ii.position != lastPos + 1) { // Create a synthetic item for a missing page. ii = mTempItem; @@ -2109,17 +2190,18 @@ public class ViewPager extends ViewGroup { ii.widthFactor = mAdapter.getPageWidth(ii.position); i--; } - offset = ii.offset; - final float leftBound = offset; - final float rightBound = offset + ii.widthFactor + marginOffset; - if (first || scrollOffset >= leftBound) { - if (scrollOffset < rightBound || i == mItems.size() - 1) { + final float offset = ii.offset; + final float startBound = offset; + if (first || scrollOffset >= startBound) { + final float endBound = offset + ii.widthFactor + marginOffset; + if (scrollOffset < endBound || i == mItems.size() - 1) { return ii; } } else { return lastItem; } + first = false; lastPos = ii.position; lastOffset = offset; @@ -2130,13 +2212,28 @@ public class ViewPager extends ViewGroup { return lastItem; } + private int getScrollStart() { + if (isLayoutRtl()) { + return MAX_SCROLL_X - getScrollX(); + } else { + return getScrollX(); + } + } + + /** + * @param currentPage the position of the page with the first visible starting edge + * @param pageOffset the fraction of the right-hand page that's visible + * @param velocity the velocity of the touch event stream + * @param deltaX the distance of the touch event stream + * @return the position of the target page + */ private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) { int targetPage; if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) { - targetPage = velocity > 0 ? currentPage : currentPage + 1; + targetPage = currentPage - (velocity < 0 ? mLeftIncr : 0); } else { final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f; - targetPage = (int) (currentPage + pageOffset + truncator); + targetPage = (int) (currentPage - mLeftIncr * (pageOffset + truncator)); } if (mItems.size() > 0) { @@ -2144,7 +2241,7 @@ public class ViewPager extends ViewGroup { final ItemInfo lastItem = mItems.get(mItems.size() - 1); // Only let the user target pages we have items for - targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position)); + targetPage = MathUtils.constrain(targetPage, firstItem.position, lastItem.position); } return targetPage; @@ -2205,6 +2302,7 @@ public class ViewPager extends ViewGroup { int itemIndex = 0; ItemInfo ii = mItems.get(0); float offset = ii.offset; + final int itemCount = mItems.size(); final int firstPos = ii.position; final int lastPos = mItems.get(itemCount - 1).position; @@ -2213,156 +2311,39 @@ public class ViewPager extends ViewGroup { ii = mItems.get(++itemIndex); } - float drawAt; + final float itemOffset; + final float widthFactor; if (pos == ii.position) { - drawAt = (ii.offset + ii.widthFactor) * width; - offset = ii.offset + ii.widthFactor + marginOffset; + itemOffset = ii.offset; + widthFactor = ii.widthFactor; + } else { + itemOffset = offset; + widthFactor = mAdapter.getPageWidth(pos); + } + + final float left; + final float scaledOffset = itemOffset * width; + if (isLayoutRtl()) { + left = MAX_SCROLL_X - scaledOffset; } else { - float widthFactor = mAdapter.getPageWidth(pos); - drawAt = (offset + widthFactor) * width; - offset += widthFactor + marginOffset; + left = scaledOffset + widthFactor * width; } - if (drawAt + mPageMargin > scrollX) { - mMarginDrawable.setBounds((int) drawAt, mTopPageBounds, - (int) (drawAt + mPageMargin + 0.5f), mBottomPageBounds); + offset = itemOffset + widthFactor + marginOffset; + + if (left + mPageMargin > scrollX) { + mMarginDrawable.setBounds((int) left, mTopPageBounds, + (int) (left + mPageMargin + 0.5f), mBottomPageBounds); mMarginDrawable.draw(canvas); } - if (drawAt > scrollX + width) { + if (left > scrollX + width) { break; // No more visible, no sense in continuing } } } } - /** - * Start a fake drag of the pager. - * - * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager - * with the touch scrolling of another view, while still letting the ViewPager - * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.) - * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call - * {@link #endFakeDrag()} to complete the fake drag and fling as necessary. - * - * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag - * is already in progress, this method will return false. - * - * @return true if the fake drag began successfully, false if it could not be started. - * - * @see #fakeDragBy(float) - * @see #endFakeDrag() - */ - public boolean beginFakeDrag() { - if (mIsBeingDragged) { - return false; - } - mFakeDragging = true; - setScrollState(SCROLL_STATE_DRAGGING); - mInitialMotionX = mLastMotionX = 0; - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } else { - mVelocityTracker.clear(); - } - final long time = SystemClock.uptimeMillis(); - final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0); - mVelocityTracker.addMovement(ev); - ev.recycle(); - mFakeDragBeginTime = time; - return true; - } - - /** - * End a fake drag of the pager. - * - * @see #beginFakeDrag() - * @see #fakeDragBy(float) - */ - public void endFakeDrag() { - if (!mFakeDragging) { - throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first."); - } - - final VelocityTracker velocityTracker = mVelocityTracker; - velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); - int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId); - mPopulatePending = true; - final int width = getClientWidth(); - final int scrollX = getScrollX(); - final ItemInfo ii = infoForCurrentScrollPosition(); - final int currentPage = ii.position; - final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor; - final int totalDelta = (int) (mLastMotionX - mInitialMotionX); - int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity, - totalDelta); - setCurrentItemInternal(nextPage, true, true, initialVelocity); - endDrag(); - - mFakeDragging = false; - } - - /** - * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first. - * - * @param xOffset Offset in pixels to drag by. - * @see #beginFakeDrag() - * @see #endFakeDrag() - */ - public void fakeDragBy(float xOffset) { - if (!mFakeDragging) { - throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first."); - } - - mLastMotionX += xOffset; - - float oldScrollX = getScrollX(); - float scrollX = oldScrollX - xOffset; - final int width = getClientWidth(); - - float leftBound = width * mFirstOffset; - float rightBound = width * mLastOffset; - - final ItemInfo firstItem = mItems.get(0); - final ItemInfo lastItem = mItems.get(mItems.size() - 1); - if (firstItem.position != 0) { - leftBound = firstItem.offset * width; - } - if (lastItem.position != mAdapter.getCount() - 1) { - rightBound = lastItem.offset * width; - } - - if (scrollX < leftBound) { - scrollX = leftBound; - } else if (scrollX > rightBound) { - scrollX = rightBound; - } - // Don't lose the rounded component - mLastMotionX += scrollX - (int) scrollX; - scrollTo((int) scrollX, getScrollY()); - pageScrolled((int) scrollX); - - // Synthesize an event for the VelocityTracker. - final long time = SystemClock.uptimeMillis(); - final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE, - mLastMotionX, 0, 0); - mVelocityTracker.addMovement(ev); - ev.recycle(); - } - - /** - * Returns true if a fake drag is in progress. - * - * @return true if currently in a fake drag, false otherwise. - * - * @see #beginFakeDrag() - * @see #fakeDragBy(float) - * @see #endFakeDrag() - */ - public boolean isFakeDragging() { - return mFakeDragging; - } - private void onSecondaryPointerUp(MotionEvent ev) { final int pointerIndex = ev.getActionIndex(); final int pointerId = ev.getPointerId(pointerIndex); @@ -2408,7 +2389,7 @@ public class ViewPager extends ViewGroup { return false; } - final int width = getClientWidth(); + final int width = getPaddedWidth(); final int scrollX = getScrollX(); if (direction < 0) { return (scrollX > (int) (width * mFirstOffset)); @@ -2438,12 +2419,11 @@ public class ViewPager extends ViewGroup { final int count = group.getChildCount(); // Count backwards - let topmost views consume scroll distance first. for (int i = count - 1; i >= 0; i--) { - // TODO: Add versioned support here for transformed views. - // This will not work for transformed views in Honeycomb+ + // TODO: Add support for transformed views. final View child = group.getChildAt(i); - if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && - y + scrollY >= child.getTop() && y + scrollY < child.getBottom() && - canScroll(child, true, dx, x + scrollX - child.getLeft(), + if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() + && y + scrollY >= child.getTop() && y + scrollY < child.getBottom() + && canScroll(child, true, dx, x + scrollX - child.getLeft(), y + scrollY - child.getTop())) { return true; } @@ -2582,19 +2562,22 @@ public class ViewPager extends ViewGroup { } boolean pageLeft() { - if (mCurItem > 0) { - setCurrentItem(mCurItem-1, true); - return true; - } - return false; + return setCurrentItemInternal(mCurItem + mLeftIncr, true, false); } boolean pageRight() { - if (mAdapter != null && mCurItem < (mAdapter.getCount()-1)) { - setCurrentItem(mCurItem+1, true); - return true; + return setCurrentItemInternal(mCurItem - mLeftIncr, true, false); + } + + @Override + public void onRtlPropertiesChanged(@ResolvedLayoutDir int layoutDirection) { + super.onRtlPropertiesChanged(layoutDirection); + + if (layoutDirection == LAYOUT_DIRECTION_LTR) { + mLeftIncr = -1; + } else { + mLeftIncr = 1; } - return false; } /** diff --git a/core/java/com/android/server/backup/PreferredActivityBackupHelper.java b/core/java/com/android/server/backup/PreferredActivityBackupHelper.java index 6ac0d89..26f5bf4 100644 --- a/core/java/com/android/server/backup/PreferredActivityBackupHelper.java +++ b/core/java/com/android/server/backup/PreferredActivityBackupHelper.java @@ -17,159 +17,58 @@ package com.android.server.backup; import android.app.AppGlobals; -import android.app.backup.BackupDataInputStream; -import android.app.backup.BackupDataOutput; -import android.app.backup.BackupHelper; -import android.content.Context; +import android.app.backup.BlobBackupHelper; import android.content.pm.IPackageManager; -import android.os.IBinder; -import android.os.ParcelFileDescriptor; -import android.os.ServiceManager; import android.os.UserHandle; import android.util.Slog; -import android.util.Xml; -import com.android.internal.util.FastXmlSerializer; -import com.android.org.bouncycastle.util.Arrays; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlSerializer; - -import java.io.BufferedInputStream; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.EOFException; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; - -public class PreferredActivityBackupHelper implements BackupHelper { +public class PreferredActivityBackupHelper extends BlobBackupHelper { private static final String TAG = "PreferredBackup"; - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; // current schema of the backup state blob - private static final int STATE_VERSION = 1; + private static final int STATE_VERSION = 2; // key under which the preferred-activity state blob is committed to backup private static final String KEY_PREFERRED = "preferred-activity"; - final Context mContext; - - public PreferredActivityBackupHelper(Context context) { - mContext = context; - } - - // The fds passed here are shared among all helpers, so we mustn't close them - private void writeState(ParcelFileDescriptor stateFile, byte[] payload) { - try { - FileOutputStream fos = new FileOutputStream(stateFile.getFileDescriptor()); - - // We explicitly don't close 'out' because we must not close the backing fd. - // The FileOutputStream will not close it implicitly. - @SuppressWarnings("resource") - DataOutputStream out = new DataOutputStream(fos); - - out.writeInt(STATE_VERSION); - if (payload == null) { - out.writeInt(0); - } else { - out.writeInt(payload.length); - out.write(payload); - } - } catch (IOException e) { - Slog.e(TAG, "Unable to write updated state", e); - } - } - - private byte[] readState(ParcelFileDescriptor oldStateFd) { - FileInputStream fis = new FileInputStream(oldStateFd.getFileDescriptor()); - BufferedInputStream bis = new BufferedInputStream(fis); - - @SuppressWarnings("resource") - DataInputStream in = new DataInputStream(bis); - - byte[] oldState = null; - try { - int version = in.readInt(); - if (version == STATE_VERSION) { - int size = in.readInt(); - if (size > 0) { - if (size > 200*1024) { - Slog.w(TAG, "Suspiciously large state blog; ignoring. N=" + size); - } else { - // size looks okay; make the return buffer and fill it - oldState = new byte[size]; - in.read(oldState); - } - } - } else { - Slog.w(TAG, "Prior state from unrecognized version " + version); - } - } catch (EOFException e) { - // Empty file is expected on first backup, so carry on. If the state - // is truncated we just treat it the same way. - oldState = null; - } catch (Exception e) { - Slog.w(TAG, "Error examing prior backup state " + e.getMessage()); - oldState = null; - } - - return oldState; + public PreferredActivityBackupHelper() { + super(STATE_VERSION, KEY_PREFERRED); } @Override - public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data, - ParcelFileDescriptor newState) { - byte[] payload = null; - try { - byte[] oldPayload = readState(oldState); - + protected byte[] getBackupPayload(String key) { + if (KEY_PREFERRED.equals(key)) { + if (DEBUG) { + Slog.v(TAG, "Checking whether to back up"); + } IPackageManager pm = AppGlobals.getPackageManager(); - byte[] newPayload = pm.getPreferredActivityBackup(UserHandle.USER_OWNER); - if (!Arrays.areEqual(oldPayload, newPayload)) { - if (DEBUG) { - Slog.i(TAG, "State has changed => writing new preferred app payload"); - } - data.writeEntityHeader(KEY_PREFERRED, newPayload.length); - data.writeEntityData(newPayload, newPayload.length); - } else { - if (DEBUG) { - Slog.i(TAG, "No change to state => not writing to wire"); - } + try { + return pm.getPreferredActivityBackup(UserHandle.USER_OWNER); + } catch (Exception e) { + Slog.e(TAG, "Unable to store backup payload", e); + // fall through to report null state } - - // Always need to re-record the state, even if nothing changed - payload = newPayload; - } catch (Exception e) { - // On failures we'll wind up committing a zero-size state payload. This is - // a forward-safe situation because we know we commit the entire new payload - // on prior-state mismatch. - Slog.w(TAG, "Unable to record preferred activities", e); - } finally { - writeState(newState, payload); + } else { + Slog.w(TAG, "Unexpected backup key " + key); } + return null; } @Override - public void restoreEntity(BackupDataInputStream data) { - IPackageManager pm = AppGlobals.getPackageManager(); - try { - byte[] payload = new byte[data.size()]; - data.read(payload); + protected void applyRestoredPayload(String key, byte[] payload) { + if (KEY_PREFERRED.equals(key)) { if (DEBUG) { - Slog.i(TAG, "Restoring preferred activities; size=" + payload.length); + Slog.v(TAG, "Restoring"); } - pm.restorePreferredActivities(payload, UserHandle.USER_OWNER); - } catch (Exception e) { - Slog.e(TAG, "Exception reading restore data", e); + IPackageManager pm = AppGlobals.getPackageManager(); + try { + pm.restorePreferredActivities(payload, UserHandle.USER_OWNER); + } catch (Exception e) { + Slog.e(TAG, "Unable to restore", e); + } + } else { + Slog.w(TAG, "Unexpected restore key " + key); } } - - @Override - public void writeNewStateDescription(ParcelFileDescriptor newState) { - writeState(newState, null); - } - } diff --git a/core/java/com/android/server/backup/SystemBackupAgent.java b/core/java/com/android/server/backup/SystemBackupAgent.java index 8e97aa9..a80abce 100644 --- a/core/java/com/android/server/backup/SystemBackupAgent.java +++ b/core/java/com/android/server/backup/SystemBackupAgent.java @@ -94,7 +94,7 @@ public class SystemBackupAgent extends BackupAgentHelper { addHelper(WALLPAPER_HELPER, new WallpaperBackupHelper(this, files, keys)); addHelper(RECENTS_HELPER, new RecentsBackupHelper(this)); addHelper(SYNC_SETTINGS_HELPER, new AccountSyncSettingsBackupHelper(this)); - addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper(this)); + addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper()); addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(this)); super.onBackup(oldState, data, newState); @@ -129,7 +129,8 @@ public class SystemBackupAgent extends BackupAgentHelper { new String[] { WALLPAPER_IMAGE_KEY} )); addHelper(RECENTS_HELPER, new RecentsBackupHelper(this)); addHelper(SYNC_SETTINGS_HELPER, new AccountSyncSettingsBackupHelper(this)); - addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper(this)); + addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper()); + addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(this)); try { super.onRestore(data, appVersionCode, newState); |
