diff options
Diffstat (limited to 'core/java/android')
159 files changed, 7051 insertions, 2158 deletions
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 6957435..480d171 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -203,6 +203,14 @@ public class AccountManager { public static final String KEY_USERDATA = "userdata"; /** + * Bundle key used to supply the last time the credentials of the account + * were authenticated successfully. Time is specified in milliseconds since + * epoch. + */ + public static final String KEY_LAST_AUTHENTICATE_TIME_MILLIS_EPOCH = + "lastAuthenticatedTimeMillisEpoch"; + + /** * Authenticators using 'customTokens' option will also get the UID of the * caller */ @@ -663,6 +671,31 @@ public class AccountManager { } /** + * Informs the system that the account has been authenticated recently. This + * recency may be used by other applications to verify the account. This + * should be called only when the user has entered correct credentials for + * the account. + * <p> + * It is not safe to call this method from the main thread. As such, call it + * from another thread. + * <p> + * This method requires the caller to hold the permission + * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and should be + * called from the account's authenticator. + * + * @param account The {@link Account} to be updated. + */ + public boolean accountAuthenticated(Account account) { + if (account == null) + throw new IllegalArgumentException("account is null"); + try { + return mService.accountAuthenticated(account); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** * Rename the specified {@link Account}. This is equivalent to removing * the existing account and adding a new renamed account with the old * account's user data. @@ -1544,15 +1577,20 @@ public class AccountManager { * with these fields if activity or password was supplied and * the account was successfully verified: * <ul> - * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created + * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account verified * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account * <li> {@link #KEY_BOOLEAN_RESULT} - true to indicate success * </ul> * * If no activity or password was specified, the returned Bundle contains - * only {@link #KEY_INTENT} with the {@link Intent} needed to launch the - * password prompt. If an error occurred, - * {@link AccountManagerFuture#getResult()} throws: + * {@link #KEY_INTENT} with the {@link Intent} needed to launch the + * password prompt. + * + * <p>Also the returning Bundle may contain {@link + * #KEY_LAST_AUTHENTICATE_TIME_MILLIS_EPOCH} indicating the last time the + * credential was validated/created. + * + * If an error occurred,{@link AccountManagerFuture#getResult()} throws: * <ul> * <li> {@link AuthenticatorException} if the authenticator failed to respond * <li> {@link OperationCanceledException} if the operation was canceled for @@ -1625,9 +1663,9 @@ public class AccountManager { * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account * </ul> * - * If no activity was specified, the returned Bundle contains only + * If no activity was specified, the returned Bundle contains * {@link #KEY_INTENT} with the {@link Intent} needed to launch the - * password prompt. If an error occurred, + * password prompt. If an error occurred, * {@link AccountManagerFuture#getResult()} throws: * <ul> * <li> {@link AuthenticatorException} if the authenticator failed to respond diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl index aa41161..04b3c88 100644 --- a/core/java/android/accounts/IAccountManager.aidl +++ b/core/java/android/accounts/IAccountManager.aidl @@ -67,6 +67,7 @@ interface IAccountManager { boolean expectActivityLaunch); void confirmCredentialsAsUser(in IAccountManagerResponse response, in Account account, in Bundle options, boolean expectActivityLaunch, int userId); + boolean accountAuthenticated(in Account account); void getAuthTokenLabel(in IAccountManagerResponse response, String accountType, String authTokenType); diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java index 021194c..81a01ee 100644 --- a/core/java/android/animation/AnimatorInflater.java +++ b/core/java/android/animation/AnimatorInflater.java @@ -947,6 +947,11 @@ public class AnimatorInflater { Keyframe.ofInt(fraction); } + final int resID = a.getResourceId(R.styleable.Keyframe_interpolator, 0); + if (resID > 0) { + final Interpolator interpolator = AnimationUtils.loadInterpolator(res, theme, resID); + keyframe.setInterpolator(interpolator); + } a.recycle(); return keyframe; diff --git a/core/java/android/animation/FloatKeyframeSet.java b/core/java/android/animation/FloatKeyframeSet.java index 56da940..0173079 100644 --- a/core/java/android/animation/FloatKeyframeSet.java +++ b/core/java/android/animation/FloatKeyframeSet.java @@ -118,13 +118,14 @@ class FloatKeyframeSet extends KeyframeSet implements Keyframes.FloatKeyframes { FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(i); if (fraction < nextKeyframe.getFraction()) { final TimeInterpolator interpolator = nextKeyframe.getInterpolator(); - if (interpolator != null) { - fraction = interpolator.getInterpolation(fraction); - } float intervalFraction = (fraction - prevKeyframe.getFraction()) / (nextKeyframe.getFraction() - prevKeyframe.getFraction()); float prevValue = prevKeyframe.getFloatValue(); float nextValue = nextKeyframe.getFloatValue(); + // Apply interpolator on the proportional duration. + if (interpolator != null) { + intervalFraction = interpolator.getInterpolation(intervalFraction); + } return mEvaluator == null ? prevValue + intervalFraction * (nextValue - prevValue) : ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)). diff --git a/core/java/android/animation/IntKeyframeSet.java b/core/java/android/animation/IntKeyframeSet.java index 12a4bf9..73f9af1 100644 --- a/core/java/android/animation/IntKeyframeSet.java +++ b/core/java/android/animation/IntKeyframeSet.java @@ -117,13 +117,14 @@ class IntKeyframeSet extends KeyframeSet implements Keyframes.IntKeyframes { IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(i); if (fraction < nextKeyframe.getFraction()) { final TimeInterpolator interpolator = nextKeyframe.getInterpolator(); - if (interpolator != null) { - fraction = interpolator.getInterpolation(fraction); - } float intervalFraction = (fraction - prevKeyframe.getFraction()) / (nextKeyframe.getFraction() - prevKeyframe.getFraction()); int prevValue = prevKeyframe.getIntValue(); int nextValue = nextKeyframe.getIntValue(); + // Apply interpolator on the proportional duration. + if (interpolator != null) { + intervalFraction = interpolator.getInterpolation(intervalFraction); + } return mEvaluator == null ? prevValue + (int)(intervalFraction * (nextValue - prevValue)) : ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)). diff --git a/core/java/android/animation/KeyframeSet.java b/core/java/android/animation/KeyframeSet.java index c80e162..32edd4d 100644 --- a/core/java/android/animation/KeyframeSet.java +++ b/core/java/android/animation/KeyframeSet.java @@ -201,7 +201,6 @@ class KeyframeSet implements Keyframes { * @return The animated value. */ public Object getValue(float fraction) { - // Special-case optimization for the common case of only two keyframes if (mNumKeyframes == 2) { if (mInterpolator != null) { @@ -238,12 +237,13 @@ class KeyframeSet implements Keyframes { Keyframe nextKeyframe = mKeyframes.get(i); if (fraction < nextKeyframe.getFraction()) { final TimeInterpolator interpolator = nextKeyframe.getInterpolator(); - if (interpolator != null) { - fraction = interpolator.getInterpolation(fraction); - } final float prevFraction = prevKeyframe.getFraction(); float intervalFraction = (fraction - prevFraction) / (nextKeyframe.getFraction() - prevFraction); + // Apply interpolator on the proportional duration. + if (interpolator != null) { + intervalFraction = interpolator.getInterpolation(intervalFraction); + } return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(), nextKeyframe.getValue()); } diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java index 3f71d51..f933373 100644 --- a/core/java/android/animation/ObjectAnimator.java +++ b/core/java/android/animation/ObjectAnimator.java @@ -50,9 +50,11 @@ import java.lang.ref.WeakReference; * value. Alternatively, you can leave the fractions off and the keyframes will be equally * distributed within the total duration. Also, a keyframe with no value will derive its value * from the target object when the animator starts, just like animators with only one - * value specified.</p> + * value specified. In addition, an optional interpolator can be specified. The interpolator will + * be applied on the interval between the keyframe that the interpolator is set on and the previous + * keyframe. When no interpolator is supplied, the default linear interpolator will be used. </p> * - * {@sample development/samples/ApiDemos/res/anim/object_animator_pvh_kf.xml KeyframeResources} + * {@sample development/samples/ApiDemos/res/anim/object_animator_pvh_kf_interpolated.xml KeyframeResources} * * <div class="special reference"> * <h3>Developer Guides</h3> diff --git a/core/java/android/animation/TimeAnimator.java b/core/java/android/animation/TimeAnimator.java index 1738ade..1ba68df 100644 --- a/core/java/android/animation/TimeAnimator.java +++ b/core/java/android/animation/TimeAnimator.java @@ -51,6 +51,7 @@ public class TimeAnimator extends ValueAnimator { public void setCurrentPlayTime(long playTime) { long currentTime = AnimationUtils.currentAnimationTimeMillis(); mStartTime = Math.max(mStartTime, currentTime - playTime); + mStartTimeCommitted = true; // do not allow start time to be compensated for jank animationFrame(currentTime); } diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index 85dc832..2386007 100644 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -20,6 +20,7 @@ import android.annotation.CallSuper; import android.os.Looper; import android.os.Trace; import android.util.AndroidRuntimeException; +import android.util.Log; import android.view.Choreographer; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AnimationUtils; @@ -64,6 +65,8 @@ import java.util.HashMap; */ @SuppressWarnings("unchecked") public class ValueAnimator extends Animator { + private static final String TAG = "ValueAnimator"; + private static final boolean DEBUG = false; /** * Internal constants @@ -85,12 +88,30 @@ public class ValueAnimator extends Animator { * to clone() to make deep copies of them. */ - // The first time that the animation's animateFrame() method is called. This time is used to - // determine elapsed time (and therefore the elapsed fraction) in subsequent calls - // to animateFrame() + /** + * The first time that the animation's animateFrame() method is called. This time is used to + * determine elapsed time (and therefore the elapsed fraction) in subsequent calls + * to animateFrame(). + * + * Whenever mStartTime is set, you must also update mStartTimeCommitted. + */ long mStartTime; /** + * When true, the start time has been firmly committed as a chosen reference point in + * time by which the progress of the animation will be evaluated. When false, the + * start time may be updated when the first animation frame is committed so as + * to compensate for jank that may have occurred between when the start time was + * initialized and when the frame was actually drawn. + * + * This flag is generally set to false during the first frame of the animation + * when the animation playing state transitions from STOPPED to RUNNING or + * resumes after having been paused. This flag is set to true when the start time + * is firmly committed and should not be further compensated for jank. + */ + boolean mStartTimeCommitted; + + /** * Set when setCurrentPlayTime() is called. If negative, animation is not currently seeked * to a value. */ @@ -528,6 +549,7 @@ public class ValueAnimator extends Animator { * value makes it easier to compose statements together that construct and then set the * duration, as in <code>ValueAnimator.ofInt(0, 10).setDuration(500).start()</code>. */ + @Override public ValueAnimator setDuration(long duration) { if (duration < 0) { throw new IllegalArgumentException("Animators cannot have negative duration: " + @@ -547,6 +569,7 @@ public class ValueAnimator extends Animator { * * @return The length of the animation, in milliseconds. */ + @Override public long getDuration() { return mUnscaledDuration; } @@ -608,6 +631,7 @@ public class ValueAnimator extends Animator { long seekTime = (long) (mDuration * fraction); long currentTime = AnimationUtils.currentAnimationTimeMillis(); mStartTime = currentTime - seekTime; + mStartTimeCommitted = true; // do not allow start time to be compensated for jank if (mPlayingState != RUNNING) { mSeekFraction = fraction; mPlayingState = SEEKED; @@ -644,7 +668,7 @@ public class ValueAnimator extends Animator { * @hide */ @SuppressWarnings("unchecked") - protected static class AnimationHandler implements Runnable { + protected static class AnimationHandler { // The per-thread list of all active animations /** @hide */ protected final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>(); @@ -667,6 +691,7 @@ public class ValueAnimator extends Animator { private final Choreographer mChoreographer; private boolean mAnimationScheduled; + private long mLastFrameTime; private AnimationHandler() { mChoreographer = Choreographer.getInstance(); @@ -679,7 +704,9 @@ public class ValueAnimator extends Animator { scheduleAnimation(); } - private void doAnimationFrame(long frameTime) { + void doAnimationFrame(long frameTime) { + mLastFrameTime = frameTime; + // mPendingAnimations holds any animations that have requested to be started // We're going to clear mPendingAnimations, but starting animation may // cause more to be added to the pending list (for example, if one animation @@ -700,6 +727,7 @@ public class ValueAnimator extends Animator { } } } + // Next, process animations currently sitting on the delayed queue, adding // them to the active animations if they are ready int numDelayedAnims = mDelayedAnims.size(); @@ -740,6 +768,9 @@ public class ValueAnimator extends Animator { mEndingAnims.clear(); } + // Schedule final commit for the frame. + mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, mCommit, null); + // If there are still active or delayed animations, schedule a future call to // onAnimate to process the next frame of the animations. if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) { @@ -747,19 +778,37 @@ public class ValueAnimator extends Animator { } } - // Called by the Choreographer. - @Override - public void run() { - mAnimationScheduled = false; - doAnimationFrame(mChoreographer.getFrameTime()); + void commitAnimationFrame(long frameTime) { + final long adjustment = frameTime - mLastFrameTime; + final int numAnims = mAnimations.size(); + for (int i = 0; i < numAnims; ++i) { + mAnimations.get(i).commitAnimationFrame(adjustment); + } } private void scheduleAnimation() { if (!mAnimationScheduled) { - mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null); + mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mAnimate, null); mAnimationScheduled = true; } } + + // Called by the Choreographer. + final Runnable mAnimate = new Runnable() { + @Override + public void run() { + mAnimationScheduled = false; + doAnimationFrame(mChoreographer.getFrameTime()); + } + }; + + // Called by the Choreographer. + final Runnable mCommit = new Runnable() { + @Override + public void run() { + commitAnimationFrame(mChoreographer.getFrameTime()); + } + }; } /** @@ -768,6 +817,7 @@ public class ValueAnimator extends Animator { * * @return the number of milliseconds to delay running the animation */ + @Override public long getStartDelay() { return mUnscaledStartDelay; } @@ -778,6 +828,7 @@ public class ValueAnimator extends Animator { * @param startDelay The amount of the delay, in milliseconds */ + @Override public void setStartDelay(long startDelay) { this.mStartDelay = (long)(startDelay * sDurationScale); mUnscaledStartDelay = startDelay; @@ -1148,6 +1199,7 @@ public class ValueAnimator extends Animator { long currentPlayTime = currentTime - mStartTime; long timeLeft = mDuration - currentPlayTime; mStartTime = currentTime - timeLeft; + mStartTimeCommitted = true; // do not allow start time to be compensated for jank mReversing = !mReversing; } else if (mStarted) { end(); @@ -1254,9 +1306,9 @@ public class ValueAnimator extends Animator { } long deltaTime = currentTime - mDelayStartTime; if (deltaTime > mStartDelay) { - // startDelay ended - start the anim and record the - // mStartTime appropriately - mStartTime = currentTime - (deltaTime - mStartDelay); + // startDelay ended - start the anim and record the mStartTime appropriately + mStartTime = mDelayStartTime + mStartDelay; + mStartTimeCommitted = true; // do not allow start time to be compensated for jank mPlayingState = RUNNING; return true; } @@ -1264,6 +1316,22 @@ public class ValueAnimator extends Animator { } /** + * Applies an adjustment to the animation to compensate for jank between when + * the animation first ran and when the frame was drawn. + */ + void commitAnimationFrame(long adjustment) { + if (!mStartTimeCommitted) { + mStartTimeCommitted = true; + if (mPlayingState == RUNNING && adjustment > 0) { + mStartTime += adjustment; + if (DEBUG) { + Log.d(TAG, "Adjusted start time by " + adjustment + " ms: " + toString()); + } + } + } + } + + /** * This internal function processes a single animation frame for a given animation. The * currentTime parameter is the timing pulse sent by the handler, used to calculate the * elapsed duration, and therefore @@ -1303,6 +1371,8 @@ public class ValueAnimator extends Animator { mCurrentIteration += (int) fraction; fraction = fraction % 1f; mStartTime += mDuration; + // Note: We do not need to update the value of mStartTimeCommitted here + // since we just added a duration offset. } else { done = true; fraction = Math.min(fraction, 1.0f); @@ -1334,6 +1404,7 @@ public class ValueAnimator extends Animator { mStartTime = frameTime - seekTime; mSeekFraction = -1; } + mStartTimeCommitted = false; // allow start time to be compensated for jank } if (mPaused) { if (mPauseTime < 0) { @@ -1345,6 +1416,7 @@ public class ValueAnimator extends Animator { if (mPauseTime > 0) { // Offset by the duration that the animation was paused mStartTime += (frameTime - mPauseTime); + mStartTimeCommitted = false; // allow start time to be compensated for jank } } // The frame time might be before the start time during the first frame of diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index d143f8b..8f125d7 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -2682,6 +2682,47 @@ public class ActivityManager { } /** + * Request that the system start watching for the calling process to exceed a pss + * size as given here. Once called, the system will look for any occassions where it + * sees the associated process with a larger pss size and, when this happens, automatically + * pull a heap dump from it and allow the user to share the data. Note that this request + * continues running even if the process is killed and restarted. To remove the watch, + * use {@link #clearWatchHeapLimit()}. + * + * <p>This API only work if running on a debuggable (userdebug or eng) build.</p> + * + * <p>Callers can optionally implement {@link #ACTION_REPORT_HEAP_LIMIT} to directly + * handle heap limit reports themselves.</p> + * + * @param pssSize The size in bytes to set the limit at. + */ + public void setWatchHeapLimit(long pssSize) { + try { + ActivityManagerNative.getDefault().setDumpHeapDebugLimit(null, 0, pssSize, + mContext.getPackageName()); + } catch (RemoteException e) { + } + } + + /** + * Action an app can implement to handle reports from {@link #setWatchHeapLimit(long)}. + * If your package has an activity handling this action, it will be launched with the + * heap data provided to it the same way as {@link Intent#ACTION_SEND}. Note that to + * match the activty must support this action and a MIME type of "*/*". + */ + public static final String ACTION_REPORT_HEAP_LIMIT = "android.app.action.REPORT_HEAP_LIMIT"; + + /** + * Clear a heap watch limit previously set by {@link #setWatchHeapLimit(long)}. + */ + public void clearWatchHeapLimit() { + try { + ActivityManagerNative.getDefault().setDumpHeapDebugLimit(null, 0, 0, null); + } catch (RemoteException e) { + } + } + + /** * @hide */ public void startLockTaskMode(int taskId) { diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 1484af8..f63d13c 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -2427,8 +2427,10 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM case SET_DUMP_HEAP_DEBUG_LIMIT_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); String procName = data.readString(); + int uid = data.readInt(); long maxMemSize = data.readLong(); - setDumpHeapDebugLimit(procName, maxMemSize); + String reportPackage = data.readString(); + setDumpHeapDebugLimit(procName, uid, maxMemSize, reportPackage); reply.writeNoException(); return true; } @@ -2450,6 +2452,15 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM reply.writeNoException(); return true; } + + case UPDATE_LOCK_TASK_PACKAGES_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int userId = data.readInt(); + String[] packages = data.readStringArray(); + updateLockTaskPackages(userId, packages); + reply.writeNoException(); + return true; + } } return super.onTransact(code, data, reply, flags); @@ -5644,12 +5655,15 @@ class ActivityManagerProxy implements IActivityManager } @Override - public void setDumpHeapDebugLimit(String processName, long maxMemSize) throws RemoteException { + public void setDumpHeapDebugLimit(String processName, int uid, long maxMemSize, + String reportPackage) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeString(processName); + data.writeInt(uid); data.writeLong(maxMemSize); + data.writeString(reportPackage); mRemote.transact(SET_DUMP_HEAP_DEBUG_LIMIT_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); @@ -5682,5 +5696,18 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } + @Override + public void updateLockTaskPackages(int userId, String[] packages) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(userId); + data.writeStringArray(packages); + mRemote.transact(UPDATE_LOCK_TASK_PACKAGES_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); + reply.readException(); + data.recycle(); + reply.recycle(); + } + private IBinder mRemote; } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 4880db1..ed05321 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -94,7 +94,7 @@ import android.view.ViewRootImpl; import android.view.Window; import android.view.WindowManager; import android.view.WindowManagerGlobal; -import android.renderscript.RenderScript; +import android.renderscript.RenderScriptCacheDir; import android.security.AndroidKeyStoreProvider; import com.android.internal.app.IVoiceInteractor; @@ -254,18 +254,21 @@ public final class ActivityThread { } } + static final class AcquiringProviderRecord { + IActivityManager.ContentProviderHolder holder; + boolean acquiring = true; + int requests = 1; + } + // The lock of mProviderMap protects the following variables. - 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<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<Activity, ArrayList<OnActivityPausedListener>> mOnPauseListeners - = new ArrayMap<Activity, ArrayList<OnActivityPausedListener>>(); + = new ArrayMap<>(); final GcIdler mGcIdler = new GcIdler(); boolean mGcIdlerScheduled = false; @@ -345,7 +348,7 @@ public final class ActivityThread { } } - final class ProviderClientRecord { + static final class ProviderClientRecord { final String[] mNames; final IContentProvider mProvider; final ContentProvider mLocalProvider; @@ -3211,7 +3214,7 @@ public final class ActivityThread { if (cv == null) { mThumbnailCanvas = cv = new Canvas(); } - + cv.setBitmap(thumbnail); if (!r.activity.onCreateThumbnail(thumbnail, cv)) { mAvailThumbnailBitmap = thumbnail; @@ -3513,12 +3516,12 @@ public final class ActivityThread { private void handleWindowVisibility(IBinder token, boolean show) { ActivityClientRecord r = mActivities.get(token); - + if (r == null) { Log.w(TAG, "handleWindowVisibility: no activity for token " + token); return; } - + if (!show && !r.stopped) { performStopActivityInner(r, null, show, false); } else if (show && r.stopped) { @@ -3950,10 +3953,10 @@ public final class ActivityThread { } } } - + if (DEBUG_CONFIGURATION) Slog.v(TAG, "Relaunching activity " + tmp.token + ": changedConfig=" + changedConfig); - + // If there was a pending configuration change, execute it first. if (changedConfig != null) { mCurDefaultDisplayDpi = changedConfig.densityDpi; @@ -4151,7 +4154,7 @@ public final class ActivityThread { if (config == null) { return; } - + if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: " + config); @@ -4289,7 +4292,7 @@ public final class ActivityThread { ApplicationPackageManager.handlePackageBroadcast(cmd, packages, hasPkgInfo); } - + final void handleLowMemory() { ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(true, null); @@ -4336,10 +4339,10 @@ public final class ActivityThread { String[] packages = getPackageManager().getPackagesForUid(uid); // If there are several packages in this application we won't - // initialize the graphics disk caches + // initialize the graphics disk caches if (packages != null && packages.length == 1) { HardwareRenderer.setupDiskCache(cacheDir); - RenderScript.setupDiskCache(cacheDir); + RenderScriptCacheDir.setupDiskCache(cacheDir); } } catch (RemoteException e) { // Ignore @@ -4693,22 +4696,57 @@ public final class ActivityThread { public final IContentProvider acquireProvider( Context c, String auth, int userId, boolean stable) { - final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable); + final ProviderKey key = new ProviderKey(auth, userId); + final IContentProvider provider = acquireExistingProvider(c, key, 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 { - holder = ActivityManagerNative.getDefault().getContentProvider( - getApplicationThread(), auth, userId, stable); - } catch (RemoteException ex) { + 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. + try { + holder = ActivityManagerNative.getDefault().getContentProvider( + getApplicationThread(), auth, userId, stable); + } catch (RemoteException ex) { + } + synchronized (r) { + r.holder = holder; + r.acquiring = false; + r.notifyAll(); + } + } else { + synchronized (r) { + while (r.acquiring) { + try { + r.wait(); + } catch (InterruptedException e) { + } + } + holder = r.holder; + } + } + synchronized (mAcquiringProviderMap) { + if (--r.requests == 0) { + mAcquiringProviderMap.remove(key); + } } if (holder == null) { Slog.e(TAG, "Failed to find provider info for " + auth); @@ -4792,8 +4830,12 @@ 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; @@ -4804,7 +4846,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 " + auth + " for user " + userId + Log.i(TAG, "Acquiring provider " + key.authority + " for user " + key.userId + ": existing object's process dead"); handleUnstableProviderDiedLocked(jBinder, true); return null; @@ -5126,18 +5168,12 @@ public final class ActivityThread { if (DEBUG_PROVIDER) { Slog.v(TAG, "installProvider: lost the race, updating ref count"); } - // 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). + // 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). 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( @@ -5231,7 +5267,7 @@ public final class ActivityThread { if (mPendingConfiguration == null || mPendingConfiguration.isOtherSeqNewer(newConfig)) { mPendingConfiguration = newConfig; - + sendMessage(H.CONFIGURATION_CHANGED, newConfig); } } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 4bd2332..381c20c 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -208,8 +208,12 @@ public class AppOpsManager { public static final int OP_ACTIVATE_VPN = 47; /** @hide Access the WallpaperManagerAPI to write wallpapers. */ public static final int OP_WRITE_WALLPAPER = 48; + /** @hide Received the assist structure from an app. */ + public static final int OP_ASSIST_STRUCTURE = 49; + /** @hide Received a screenshot from assist. */ + public static final int OP_ASSIST_SCREENSHOT = 50; /** @hide */ - public static final int _NUM_OP = 49; + public static final int _NUM_OP = 51; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = @@ -288,6 +292,8 @@ public class AppOpsManager { OP_PROJECT_MEDIA, OP_ACTIVATE_VPN, OP_WRITE_WALLPAPER, + OP_ASSIST_STRUCTURE, + OP_ASSIST_SCREENSHOT, }; /** @@ -344,6 +350,8 @@ public class AppOpsManager { null, OPSTR_ACTIVATE_VPN, null, + null, + null, }; /** @@ -400,6 +408,8 @@ public class AppOpsManager { "PROJECT_MEDIA", "ACTIVATE_VPN", "WRITE_WALLPAPER", + "ASSIST_STRUCTURE", + "ASSIST_SCREENSHOT" }; /** @@ -456,6 +466,8 @@ public class AppOpsManager { null, // no permission for projecting media null, // no permission for activating vpn null, // no permission for supporting wallpaper + null, // no permission for receiving assist structure + null, // no permission for receiving assist screenshot }; /** @@ -513,6 +525,8 @@ public class AppOpsManager { null, //PROJECT_MEDIA UserManager.DISALLOW_CONFIG_VPN, // ACTIVATE_VPN UserManager.DISALLOW_WALLPAPER, // WRITE_WALLPAPER + null, // ASSIST_STRUCTURE + null, // ASSIST_SCREENSHOT }; /** @@ -569,6 +583,8 @@ public class AppOpsManager { false, //PROJECT_MEDIA false, //ACTIVATE_VPN false, //WALLPAPER + false, //ASSIST_STRUCTURE + false, //ASSIST_SCREENSHOT }; /** @@ -624,6 +640,8 @@ public class AppOpsManager { AppOpsManager.MODE_IGNORED, // OP_PROJECT_MEDIA AppOpsManager.MODE_IGNORED, // OP_ACTIVATE_VPN AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, }; /** @@ -683,6 +701,8 @@ public class AppOpsManager { false, false, false, + false, + false, }; private static HashMap<String, Integer> sOpStrToOp = new HashMap<String, Integer>(); diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java index 6c2511e..8692336 100644 --- a/core/java/android/app/ApplicationErrorReport.java +++ b/core/java/android/app/ApplicationErrorReport.java @@ -235,10 +235,13 @@ public class ApplicationErrorReport implements Parcelable { dest.writeString(processName); dest.writeLong(time); dest.writeInt(systemApp ? 1 : 0); + dest.writeInt(crashInfo != null ? 1 : 0); switch (type) { case TYPE_CRASH: - crashInfo.writeToParcel(dest, flags); + if (crashInfo != null) { + crashInfo.writeToParcel(dest, flags); + } break; case TYPE_ANR: anrInfo.writeToParcel(dest, flags); @@ -259,10 +262,11 @@ public class ApplicationErrorReport implements Parcelable { processName = in.readString(); time = in.readLong(); systemApp = in.readInt() == 1; + boolean hasCrashInfo = in.readInt() == 1; switch (type) { case TYPE_CRASH: - crashInfo = new CrashInfo(in); + crashInfo = hasCrashInfo ? new CrashInfo(in) : null; anrInfo = null; batteryInfo = null; runningServiceInfo = null; diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 6d74905..ffdc81d 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -36,6 +36,7 @@ import android.content.pm.IPackageManager; import android.content.pm.IPackageMoveObserver; import android.content.pm.IPackageStatsObserver; import android.content.pm.InstrumentationInfo; +import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.KeySet; import android.content.pm.ManifestDigest; import android.content.pm.PackageInfo; @@ -61,19 +62,19 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Process; import android.os.RemoteException; +import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.util.ArrayMap; import android.util.Log; import android.view.Display; -import android.os.SystemProperties; + +import dalvik.system.VMRuntime; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import com.android.internal.util.UserIcons; -import dalvik.system.VMRuntime; - import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; @@ -1309,6 +1310,55 @@ final class ApplicationPackageManager extends PackageManager { } @Override + public void verifyIntentFilter(int id, int verificationCode, List<String> outFailedDomains) { + try { + mPM.verifyIntentFilter(id, verificationCode, outFailedDomains); + } catch (RemoteException e) { + // Should never happen! + } + } + + @Override + public int getIntentVerificationStatus(String packageName, int userId) { + try { + return mPM.getIntentVerificationStatus(packageName, userId); + } catch (RemoteException e) { + // Should never happen! + return PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; + } + } + + @Override + public boolean updateIntentVerificationStatus(String packageName, int status, int userId) { + try { + return mPM.updateIntentVerificationStatus(packageName, status, userId); + } catch (RemoteException e) { + // Should never happen! + return false; + } + } + + @Override + public List<IntentFilterVerificationInfo> getIntentFilterVerifications(String packageName) { + try { + return mPM.getIntentFilterVerifications(packageName); + } catch (RemoteException e) { + // Should never happen! + return null; + } + } + + @Override + public List<IntentFilter> getAllIntentFilters(String packageName) { + try { + return mPM.getAllIntentFilters(packageName); + } catch (RemoteException e) { + // Should never happen! + return null; + } + } + + @Override public void setInstallerPackageName(String targetPackage, String installerPackageName) { try { @@ -1323,7 +1373,17 @@ final class ApplicationPackageManager extends PackageManager { try { mPM.movePackage(packageName, observer, flags); } catch (RemoteException e) { - // Should never happen! + throw e.rethrowAsRuntimeException(); + } + } + + @Override + public void movePackageAndData(String packageName, String volumeUuid, + IPackageMoveObserver observer) { + try { + mPM.movePackageAndData(packageName, volumeUuid, observer); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); } } diff --git a/core/java/android/app/AssistStructure.java b/core/java/android/app/AssistStructure.java index c435ccb..1e159a3 100644 --- a/core/java/android/app/AssistStructure.java +++ b/core/java/android/app/AssistStructure.java @@ -17,24 +17,25 @@ package android.app; import android.content.ComponentName; -import android.content.res.Resources; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Typeface; +import android.os.Binder; import android.os.Bundle; +import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.PooledStringReader; import android.os.PooledStringWriter; +import android.os.RemoteException; +import android.os.SystemClock; import android.text.TextPaint; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.view.ViewAssistStructure; -import android.view.ViewGroup; import android.view.ViewRootImpl; import android.view.WindowManagerGlobal; -import android.widget.Checkable; import java.util.ArrayList; @@ -52,111 +53,49 @@ final public class AssistStructure implements Parcelable { */ public static final String ASSIST_KEY = "android:assist_structure"; - final ComponentName mActivityComponent; + boolean mHaveData; + + ComponentName mActivityComponent; final ArrayList<WindowNode> mWindowNodes = new ArrayList<>(); - ViewAssistStructureImpl mTmpViewAssistStructureImpl = new ViewAssistStructureImpl(); - Bundle mTmpExtras = new Bundle(); + final ArrayList<ViewNodeBuilder> mPendingAsyncChildren = new ArrayList<>(); - final static class ViewAssistStructureImpl extends ViewAssistStructure { - CharSequence mText; - int mTextSelectionStart = -1; - int mTextSelectionEnd = -1; - int mTextColor = ViewNode.TEXT_COLOR_UNDEFINED; - int mTextBackgroundColor = ViewNode.TEXT_COLOR_UNDEFINED; - float mTextSize = 0; - int mTextStyle = 0; - CharSequence mHint; + SendChannel mSendChannel; + IBinder mReceiveChannel; - @Override - public void setText(CharSequence text) { - mText = text; - mTextSelectionStart = mTextSelectionEnd = -1; - } + Rect mTmpRect = new Rect(); - @Override - public void setText(CharSequence text, int selectionStart, int selectionEnd) { - mText = text; - mTextSelectionStart = selectionStart; - mTextSelectionEnd = selectionEnd; - } + static final int TRANSACTION_XFER = Binder.FIRST_CALL_TRANSACTION+1; + static final String DESCRIPTOR = "android.app.AssistStructure"; - @Override - public void setTextPaint(TextPaint paint) { - mTextColor = paint.getColor(); - mTextBackgroundColor = paint.bgColor; - mTextSize = paint.getTextSize(); - mTextStyle = 0; - Typeface tf = paint.getTypeface(); - if (tf != null) { - if (tf.isBold()) { - mTextStyle |= ViewNode.TEXT_STYLE_BOLD; - } - if (tf.isItalic()) { - mTextStyle |= ViewNode.TEXT_STYLE_ITALIC; - } - } - int pflags = paint.getFlags(); - if ((pflags& Paint.FAKE_BOLD_TEXT_FLAG) != 0) { - mTextStyle |= ViewNode.TEXT_STYLE_BOLD; - } - if ((pflags& Paint.UNDERLINE_TEXT_FLAG) != 0) { - mTextStyle |= ViewNode.TEXT_STYLE_UNDERLINE; - } - if ((pflags& Paint.STRIKE_THRU_TEXT_FLAG) != 0) { - mTextStyle |= ViewNode.TEXT_STYLE_STRIKE_THRU; + final class SendChannel extends Binder { + @Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + if (code == TRANSACTION_XFER) { + data.enforceInterface(DESCRIPTOR); + writeContentToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + return true; + } else { + return super.onTransact(code, data, reply, flags); } } - - @Override - public void setHint(CharSequence hint) { - mHint = hint; - } - - @Override - public CharSequence getText() { - return mText; - } - - @Override - public int getTextSelectionStart() { - return mTextSelectionStart; - } - - @Override - public int getTextSelectionEnd() { - return mTextSelectionEnd; - } - - @Override - public CharSequence getHint() { - return mHint; - } } - final static class ViewNodeTextImpl { - final CharSequence mText; - final int mTextSelectionStart; - final int mTextSelectionEnd; + final static class ViewNodeText { + CharSequence mText; + int mTextSelectionStart; + int mTextSelectionEnd; int mTextColor; int mTextBackgroundColor; float mTextSize; int mTextStyle; - final String mHint; + String mHint; - ViewNodeTextImpl(ViewAssistStructureImpl data) { - mText = data.mText; - mTextSelectionStart = data.mTextSelectionStart; - mTextSelectionEnd = data.mTextSelectionEnd; - mTextColor = data.mTextColor; - mTextBackgroundColor = data.mTextBackgroundColor; - mTextSize = data.mTextSize; - mTextStyle = data.mTextStyle; - mHint = data.mHint != null ? data.mHint.toString() : null; + ViewNodeText() { } - ViewNodeTextImpl(Parcel in) { + ViewNodeText(Parcel in) { mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); mTextSelectionStart = in.readInt(); mTextSelectionEnd = in.readInt(); @@ -199,7 +138,9 @@ final public class AssistStructure implements Parcelable { mWidth = rect.width(); mHeight = rect.height(); mTitle = root.getTitle(); - mRoot = new ViewNode(assist, view); + mRoot = new ViewNode(); + ViewNodeBuilder builder = new ViewNodeBuilder(assist, mRoot, false); + view.dispatchProvideAssistStructure(builder); } WindowNode(Parcel in, PooledStringReader preader) { @@ -260,16 +201,16 @@ final public class AssistStructure implements Parcelable { public static final int TEXT_STYLE_UNDERLINE = 1<<2; public static final int TEXT_STYLE_STRIKE_THRU = 1<<3; - final int mId; - final String mIdPackage; - final String mIdType; - final String mIdEntry; - final int mX; - final int mY; - final int mScrollX; - final int mScrollY; - final int mWidth; - final int mHeight; + int mId; + String mIdPackage; + String mIdType; + String mIdEntry; + int mX; + int mY; + int mScrollX; + int mScrollY; + int mWidth; + int mHeight; static final int FLAGS_DISABLED = 0x00000001; static final int FLAGS_VISIBILITY_MASK = View.VISIBLE|View.INVISIBLE|View.GONE; @@ -283,104 +224,17 @@ final public class AssistStructure implements Parcelable { static final int FLAGS_CLICKABLE = 0x00004000; static final int FLAGS_LONG_CLICKABLE = 0x00200000; - final int mFlags; + int mFlags; - final String mClassName; - final CharSequence mContentDescription; + String mClassName; + CharSequence mContentDescription; - final ViewNodeTextImpl mText; - final Bundle mExtras; + ViewNodeText mText; + Bundle mExtras; - final ViewNode[] mChildren; + ViewNode[] mChildren; - ViewNode(AssistStructure assistStructure, View view) { - mId = view.getId(); - if (mId > 0 && (mId&0xff000000) != 0 && (mId&0x00ff0000) != 0 - && (mId&0x0000ffff) != 0) { - String pkg, type, entry; - try { - Resources res = view.getResources(); - entry = res.getResourceEntryName(mId); - type = res.getResourceTypeName(mId); - pkg = res.getResourcePackageName(mId); - } catch (Resources.NotFoundException e) { - entry = type = pkg = null; - } - mIdPackage = pkg; - mIdType = type; - mIdEntry = entry; - } else { - mIdPackage = mIdType = mIdEntry = null; - } - mX = view.getLeft(); - mY = view.getTop(); - mScrollX = view.getScrollX(); - mScrollY = view.getScrollY(); - mWidth = view.getWidth(); - mHeight = view.getHeight(); - int flags = view.getVisibility(); - if (!view.isEnabled()) { - flags |= FLAGS_DISABLED; - } - if (!view.isClickable()) { - flags |= FLAGS_CLICKABLE; - } - if (!view.isFocusable()) { - flags |= FLAGS_FOCUSABLE; - } - if (!view.isFocused()) { - flags |= FLAGS_FOCUSED; - } - if (!view.isAccessibilityFocused()) { - flags |= FLAGS_ACCESSIBILITY_FOCUSED; - } - if (!view.isSelected()) { - flags |= FLAGS_SELECTED; - } - if (!view.isActivated()) { - flags |= FLAGS_ACTIVATED; - } - if (!view.isLongClickable()) { - flags |= FLAGS_LONG_CLICKABLE; - } - if (view instanceof Checkable) { - flags |= FLAGS_CHECKABLE; - if (((Checkable)view).isChecked()) { - flags |= FLAGS_CHECKED; - } - } - mFlags = flags; - mClassName = view.getAccessibilityClassName().toString(); - mContentDescription = view.getContentDescription(); - final ViewAssistStructureImpl viewData = assistStructure.mTmpViewAssistStructureImpl; - final Bundle extras = assistStructure.mTmpExtras; - view.onProvideAssistStructure(viewData, extras); - if (viewData.mText != null || viewData.mHint != null) { - mText = new ViewNodeTextImpl(viewData); - assistStructure.mTmpViewAssistStructureImpl = new ViewAssistStructureImpl(); - } else { - mText = null; - } - if (!extras.isEmpty()) { - mExtras = extras; - assistStructure.mTmpExtras = new Bundle(); - } else { - mExtras = null; - } - if (view instanceof ViewGroup) { - ViewGroup vg = (ViewGroup)view; - final int NCHILDREN = vg.getChildCount(); - if (NCHILDREN > 0) { - mChildren = new ViewNode[NCHILDREN]; - for (int i=0; i<NCHILDREN; i++) { - mChildren[i] = new ViewNode(assistStructure, vg.getChildAt(i)); - } - } else { - mChildren = null; - } - } else { - mChildren = null; - } + ViewNode() { } ViewNode(Parcel in, PooledStringReader preader) { @@ -406,7 +260,7 @@ final public class AssistStructure implements Parcelable { mClassName = preader.readString(); mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); if (in.readInt() != 0) { - mText = new ViewNodeTextImpl(in); + mText = new ViewNodeText(in); } else { mText = null; } @@ -595,7 +449,250 @@ final public class AssistStructure implements Parcelable { } } + static class ViewNodeBuilder extends ViewAssistStructure { + final AssistStructure mAssist; + final ViewNode mNode; + final boolean mAsync; + + ViewNodeBuilder(AssistStructure assist, ViewNode node, boolean async) { + mAssist = assist; + mNode = node; + mAsync = async; + } + + @Override + public void setId(int id, String packageName, String typeName, String entryName) { + mNode.mId = id; + mNode.mIdPackage = packageName; + mNode.mIdType = typeName; + mNode.mIdEntry = entryName; + } + + @Override + public void setDimens(int left, int top, int scrollX, int scrollY, int width, int height) { + mNode.mX = left; + mNode.mY = top; + mNode.mScrollX = scrollX; + mNode.mScrollY = scrollY; + mNode.mWidth = width; + mNode.mHeight = height; + } + + @Override + public void setVisibility(int visibility) { + mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_VISIBILITY_MASK) | visibility; + } + + @Override + public void setEnabled(boolean state) { + mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_DISABLED) + | (state ? 0 : ViewNode.FLAGS_DISABLED); + } + + @Override + public void setClickable(boolean state) { + mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_CLICKABLE) + | (state ? ViewNode.FLAGS_CLICKABLE : 0); + } + + @Override + public void setLongClickable(boolean state) { + mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_LONG_CLICKABLE) + | (state ? ViewNode.FLAGS_LONG_CLICKABLE : 0); + } + + @Override + public void setFocusable(boolean state) { + mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_FOCUSABLE) + | (state ? ViewNode.FLAGS_FOCUSABLE : 0); + } + + @Override + public void setFocused(boolean state) { + mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_FOCUSED) + | (state ? ViewNode.FLAGS_FOCUSED : 0); + } + + @Override + public void setAccessibilityFocused(boolean state) { + mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_ACCESSIBILITY_FOCUSED) + | (state ? ViewNode.FLAGS_ACCESSIBILITY_FOCUSED : 0); + } + + @Override + public void setCheckable(boolean state) { + mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_CHECKABLE) + | (state ? ViewNode.FLAGS_CHECKABLE : 0); + } + + @Override + public void setChecked(boolean state) { + mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_CHECKED) + | (state ? ViewNode.FLAGS_CHECKED : 0); + } + + @Override + public void setSelected(boolean state) { + mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_SELECTED) + | (state ? ViewNode.FLAGS_SELECTED : 0); + } + + @Override + public void setActivated(boolean state) { + mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_ACTIVATED) + | (state ? ViewNode.FLAGS_ACTIVATED : 0); + } + + @Override + public void setClassName(String className) { + mNode.mClassName = className; + } + + @Override + public void setContentDescription(CharSequence contentDescription) { + mNode.mContentDescription = contentDescription; + } + + private final ViewNodeText getNodeText() { + if (mNode.mText != null) { + return mNode.mText; + } + mNode.mText = new ViewNodeText(); + return mNode.mText; + } + + @Override + public void setText(CharSequence text) { + ViewNodeText t = getNodeText(); + t.mText = text; + t.mTextSelectionStart = t.mTextSelectionEnd = -1; + } + + @Override + public void setText(CharSequence text, int selectionStart, int selectionEnd) { + ViewNodeText t = getNodeText(); + t.mText = text; + t.mTextSelectionStart = selectionStart; + t.mTextSelectionEnd = selectionEnd; + } + + @Override + public void setTextPaint(TextPaint paint) { + ViewNodeText t = getNodeText(); + t.mTextColor = paint.getColor(); + t.mTextBackgroundColor = paint.bgColor; + t.mTextSize = paint.getTextSize(); + t.mTextStyle = 0; + Typeface tf = paint.getTypeface(); + if (tf != null) { + if (tf.isBold()) { + t.mTextStyle |= ViewNode.TEXT_STYLE_BOLD; + } + if (tf.isItalic()) { + t.mTextStyle |= ViewNode.TEXT_STYLE_ITALIC; + } + } + int pflags = paint.getFlags(); + if ((pflags& Paint.FAKE_BOLD_TEXT_FLAG) != 0) { + t.mTextStyle |= ViewNode.TEXT_STYLE_BOLD; + } + if ((pflags& Paint.UNDERLINE_TEXT_FLAG) != 0) { + t.mTextStyle |= ViewNode.TEXT_STYLE_UNDERLINE; + } + if ((pflags& Paint.STRIKE_THRU_TEXT_FLAG) != 0) { + t.mTextStyle |= ViewNode.TEXT_STYLE_STRIKE_THRU; + } + } + + @Override + public void setHint(CharSequence hint) { + getNodeText().mHint = hint != null ? hint.toString() : null; + } + + @Override + public CharSequence getText() { + return mNode.mText != null ? mNode.mText.mText : null; + } + + @Override + public int getTextSelectionStart() { + return mNode.mText != null ? mNode.mText.mTextSelectionStart : -1; + } + + @Override + public int getTextSelectionEnd() { + return mNode.mText != null ? mNode.mText.mTextSelectionEnd : -1; + } + + @Override + public CharSequence getHint() { + return mNode.mText != null ? mNode.mText.mHint : null; + } + + @Override + public Bundle editExtras() { + if (mNode.mExtras != null) { + return mNode.mExtras; + } + mNode.mExtras = new Bundle(); + return mNode.mExtras; + } + + @Override + public void clearExtras() { + mNode.mExtras = null; + } + + @Override + public void setChildCount(int num) { + mNode.mChildren = new ViewNode[num]; + } + + @Override + public int getChildCount() { + return mNode.mChildren != null ? mNode.mChildren.length : 0; + } + + @Override + public ViewAssistStructure newChild(int index) { + ViewNode node = new ViewNode(); + mNode.mChildren[index] = node; + return new ViewNodeBuilder(mAssist, node, false); + } + + @Override + public ViewAssistStructure asyncNewChild(int index) { + synchronized (mAssist) { + ViewNode node = new ViewNode(); + mNode.mChildren[index] = node; + ViewNodeBuilder builder = new ViewNodeBuilder(mAssist, node, true); + mAssist.mPendingAsyncChildren.add(builder); + return builder; + } + } + + @Override + public void asyncCommit() { + synchronized (mAssist) { + if (!mAsync) { + throw new IllegalStateException("Child " + this + + " was not created with ViewAssistStructure.asyncNewChild"); + } + if (!mAssist.mPendingAsyncChildren.remove(this)) { + throw new IllegalStateException("Child " + this + " already committed"); + } + mAssist.notifyAll(); + } + } + + @Override + public Rect getTempRect() { + return mAssist.mTmpRect; + } + } + AssistStructure(Activity activity) { + mHaveData = true; mActivityComponent = activity.getComponentName(); ArrayList<ViewRootImpl> views = WindowManagerGlobal.getInstance().getRootViews( activity.getActivityToken()); @@ -606,13 +703,7 @@ final public class AssistStructure implements Parcelable { } AssistStructure(Parcel in) { - PooledStringReader preader = new PooledStringReader(in); - mActivityComponent = ComponentName.readFromParcel(in); - final int N = in.readInt(); - for (int i=0; i<N; i++) { - mWindowNodes.add(new WindowNode(in, preader)); - } - //dump(); + mReceiveChannel = in.readStrongBinder(); } /** @hide */ @@ -689,6 +780,7 @@ final public class AssistStructure implements Parcelable { } public ComponentName getActivityComponent() { + ensureData(); return mActivityComponent; } @@ -696,6 +788,7 @@ final public class AssistStructure implements Parcelable { * Return the number of window contents that have been collected in this assist data. */ public int getWindowNodeCount() { + ensureData(); return mWindowNodes.size(); } @@ -704,6 +797,7 @@ final public class AssistStructure implements Parcelable { * @param index Which window to retrieve, may be 0 to {@link #getWindowNodeCount()}-1. */ public WindowNode getWindowNodeAt(int index) { + ensureData(); return mWindowNodes.get(index); } @@ -711,11 +805,47 @@ final public class AssistStructure implements Parcelable { return 0; } - public void writeToParcel(Parcel out, int flags) { + /** @hide */ + public void ensureData() { + if (mHaveData) { + return; + } + mHaveData = true; + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(DESCRIPTOR); + try { + mReceiveChannel.transact(TRANSACTION_XFER, data, reply, 0); + } catch (RemoteException e) { + Log.w(TAG, "Failure reading AssistStructure data", e); + return; + } + readContentFromParcel(reply); + data.recycle(); + reply.recycle(); + } + + void writeContentToParcel(Parcel out, int flags) { + // First make sure all content has been created. + boolean skipStructure = false; + synchronized (this) { + long endTime = SystemClock.uptimeMillis() + 5000; + long now; + while (mPendingAsyncChildren.size() > 0 && (now=SystemClock.uptimeMillis()) < endTime) { + try { + wait(endTime-now); + } catch (InterruptedException e) { + } + } + if (mPendingAsyncChildren.size() > 0) { + // We waited too long, assume none of the assist structure is valid. + skipStructure = true; + } + } int start = out.dataPosition(); PooledStringWriter pwriter = new PooledStringWriter(out); ComponentName.writeToParcel(mActivityComponent, out); - final int N = mWindowNodes.size(); + final int N = skipStructure ? 0 : mWindowNodes.size(); out.writeInt(N); for (int i=0; i<N; i++) { mWindowNodes.get(i).writeToParcel(out, pwriter); @@ -724,6 +854,30 @@ final public class AssistStructure implements Parcelable { Log.i(TAG, "Flattened assist data: " + (out.dataPosition() - start) + " bytes"); } + void readContentFromParcel(Parcel in) { + PooledStringReader preader = new PooledStringReader(in); + mActivityComponent = ComponentName.readFromParcel(in); + final int N = in.readInt(); + for (int i=0; i<N; i++) { + mWindowNodes.add(new WindowNode(in, preader)); + } + //dump(); + } + + public void writeToParcel(Parcel out, int flags) { + if (mHaveData) { + // This object holds its data. We want to write a send channel that the + // other side can use to retrieve that data. + if (mSendChannel == null) { + mSendChannel = new SendChannel(); + } + out.writeStrongBinder(mSendChannel); + } else { + // This object doesn't hold its data, so just propagate along its receive channel. + out.writeStrongBinder(mReceiveChannel); + } + } + public static final Parcelable.Creator<AssistStructure> CREATOR = new Parcelable.Creator<AssistStructure>() { public AssistStructure createFromParcel(Parcel in) { diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index d794aa3..4a1d6ff 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -482,11 +482,13 @@ public interface IActivityManager extends IInterface { public void systemBackupRestored() throws RemoteException; public void notifyCleartextNetwork(int uid, byte[] firstPacket) throws RemoteException; - public void setDumpHeapDebugLimit(String processName, long maxMemSize) throws RemoteException; + public void setDumpHeapDebugLimit(String processName, int uid, long maxMemSize, + String reportPackage) throws RemoteException; public void dumpHeapFinished(String path) throws RemoteException; public void setVoiceKeepAwake(IVoiceInteractionSession session, boolean keepAwake) throws RemoteException; + public void updateLockTaskPackages(int userId, String[] packages) throws RemoteException; /* * Private non-Binder interfaces @@ -822,4 +824,5 @@ public interface IActivityManager extends IInterface { int SET_DUMP_HEAP_DEBUG_LIMIT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+287; int DUMP_HEAP_FINISHED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+288; int SET_VOICE_KEEP_AWAKE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+289; + int UPDATE_LOCK_TASK_PACKAGES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+290; } diff --git a/core/java/android/app/IBackupAgent.aidl b/core/java/android/app/IBackupAgent.aidl index 451af99..fe8e228 100644 --- a/core/java/android/app/IBackupAgent.aidl +++ b/core/java/android/app/IBackupAgent.aidl @@ -100,6 +100,11 @@ oneway interface IBackupAgent { void doFullBackup(in ParcelFileDescriptor data, int token, IBackupManager callbackBinder); /** + * Estimate how much data a full backup will deliver + */ + void doMeasureFullBackup(int token, IBackupManager callbackBinder); + + /** * Restore a single "file" to the application. The file was typically obtained from * a full-backup dataset. The agent reads 'size' bytes of file content * from the provided file descriptor. diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index b31ce04..e7f8f6d 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -646,6 +646,11 @@ public class Notification implements Parcelable public static final String CATEGORY_STATUS = "status"; /** + * Notification category: user-scheduled reminder. + */ + public static final String CATEGORY_REMINDER = "reminder"; + + /** * One of the predefined notification categories (see the <code>CATEGORY_*</code> constants) * that best describes this Notification. May be used by the system for ranking and filtering. */ diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 59fe490..b3aa6be 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -99,8 +99,8 @@ import android.os.Vibrator; import android.os.storage.StorageManager; import android.print.IPrintManager; import android.print.PrintManager; -import android.service.fingerprint.FingerprintManager; -import android.service.fingerprint.IFingerprintService; +import android.hardware.fingerprint.FingerprintManager; +import android.hardware.fingerprint.IFingerprintService; import android.service.persistentdata.IPersistentDataBlockService; import android.service.persistentdata.PersistentDataBlockManager; import android.telecom.TelecomManager; @@ -402,13 +402,7 @@ final class SystemServiceRegistry { new CachedServiceFetcher<StorageManager>() { @Override public StorageManager createService(ContextImpl ctx) { - try { - return new StorageManager( - ctx.getContentResolver(), ctx.mMainThread.getHandler().getLooper()); - } catch (RemoteException rex) { - Log.e(TAG, "Failed to create StorageManager", rex); - return null; - } + return new StorageManager(ctx, ctx.mMainThread.getHandler().getLooper()); }}); registerService(Context.TELEPHONY_SERVICE, TelephonyManager.class, diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index b0dd70f..a8494fb 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -690,7 +690,7 @@ public final class UiAutomation { * potentially undesirable actions such as calling 911 or posting on public forums etc. * * @param enable whether to run in a "monkey" mode or not. Default is not. - * @see {@link android.app.ActivityManager#isUserAMonkey()} + * @see ActivityManager#isUserAMonkey() */ public void setRunAsMonkey(boolean enable) { synchronized (mLock) { diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java index da7bb05..7acf5f0 100644 --- a/core/java/android/app/VoiceInteractor.java +++ b/core/java/android/app/VoiceInteractor.java @@ -103,9 +103,9 @@ public class VoiceInteractor { request = pullRequest((IVoiceInteractorRequest)args.arg1, true); if (DEBUG) Log.d(TAG, "onCompleteVoice: req=" + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request - + " result=" + args.arg1); + + " result=" + args.arg2); if (request != null) { - ((CompleteVoiceRequest)request).onCompleteResult((Bundle) args.arg1); + ((CompleteVoiceRequest)request).onCompleteResult((Bundle) args.arg2); request.clear(); } break; @@ -297,6 +297,7 @@ public class VoiceInteractor { */ public static final class Option implements Parcelable { final CharSequence mLabel; + final int mIndex; ArrayList<CharSequence> mSynonyms; Bundle mExtras; @@ -308,6 +309,21 @@ public class VoiceInteractor { */ public Option(CharSequence label) { mLabel = label; + mIndex = -1; + } + + /** + * Creates an option that a user can select with their voice by matching the label + * or one of several synonyms. + * @param label The label that will both be matched against what the user speaks + * and displayed visually. + * @param index The location of this option within the overall set of options. + * Can be used to help identify which the option when it is returned from the + * voice interactor. + */ + public Option(CharSequence label, int index) { + mLabel = label; + mIndex = index; } /** @@ -328,6 +344,14 @@ public class VoiceInteractor { return mLabel; } + /** + * Return the index that was supplied in the constructor. + * If the option was constructed without an index, -1 is returned. + */ + public int getIndex() { + return mIndex; + } + public int countSynonyms() { return mSynonyms != null ? mSynonyms.size() : 0; } @@ -356,6 +380,7 @@ public class VoiceInteractor { Option(Parcel in) { mLabel = in.readCharSequence(); + mIndex = in.readInt(); mSynonyms = in.readCharSequenceList(); mExtras = in.readBundle(); } @@ -368,6 +393,7 @@ public class VoiceInteractor { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeCharSequence(mLabel); + dest.writeInt(mIndex); dest.writeCharSequenceList(mSynonyms); dest.writeBundle(mExtras); } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index ea48b61..68f4707 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -175,7 +175,8 @@ public class DevicePolicyManager { * * <p>This component is set as device owner and active admin when device owner provisioning is * started by an NFC message containing an NFC record with MIME type - * {@link #MIME_TYPE_PROVISIONING_NFC_V2}. + * {@link #MIME_TYPE_PROVISIONING_NFC_V2}. For the NFC record, the component name should be + * flattened to a string, via {@link ComponentName#flattenToShortString()}. * * @see DeviceAdminReceiver */ @@ -398,14 +399,16 @@ public class DevicePolicyManager { "android.app.extra.PROVISIONING_SKIP_ENCRYPTION"; /** - * On devices managed by a device owner app, a String representation of a Component name extra - * indicating the component of the application that is temporarily granted device owner - * privileges during device initialization and profile owner privileges during secondary user - * initialization. + * On devices managed by a device owner app, a {@link ComponentName} extra indicating the + * component of the application that is temporarily granted device owner privileges during + * device initialization and profile owner privileges during secondary user initialization. * - * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts device owner - * provisioning via an NFC bump. - * @see ComponentName#unflattenFromString() + * <p> + * It can also be used in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts + * device owner provisioning via an NFC bump. For the NFC record, it should be flattened to a + * string first. + * + * @see ComponentName#flattenToShortString() */ public static final String EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME"; @@ -499,6 +502,20 @@ public class DevicePolicyManager { */ public static final String EXTRA_PROVISIONING_BT_USE_PROXY = "android.app.extra.PROVISIONING_BT_USE_PROXY"; + + /** + * A {@link android.os.Parcelable} extra of type {@link android.os.PersistableBundle} that + * holds data needed by the system to wipe factory reset protection. The data needed to wipe + * the device depend on the installed factory reset protection implementation. For example, + * if an account is needed to unlock a device, this extra may contain data used to + * authenticate that account. + * + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts device owner + * provisioning via an NFC bump. + */ + public static final String EXTRA_PROVISIONING_RESET_PROTECTION_PARAMETERS + = "android.app.extra.PROVISIONING_RESET_PROTECTION_PARAMETERS"; + /** * This MIME type is used for starting the Device Owner provisioning that does not require * provisioning features introduced in Android API level @@ -2636,8 +2653,8 @@ public class DevicePolicyManager { * called by the device owner. * @param initializer Which {@link DeviceAdminReceiver} to make device initializer. * @param initializerName The user-visible name of the device initializer. - * @return whether the package was successfully registered as the device initializer. - * @throws IllegalArgumentException if the package name is null or invalid + * @return whether the component was successfully registered as the device initializer. + * @throws IllegalArgumentException if the componentname is null or invalid * @throws IllegalStateException if the caller is not device owner or the device has * already been provisioned or a device initializer already exists. */ @@ -2707,6 +2724,25 @@ public class DevicePolicyManager { } /** + * @hide + * Gets the device initializer component of the system. + * + * @return the component name of the device initializer. + */ + @SystemApi + public ComponentName getDeviceInitializerComponent() { + if (mService != null) { + try { + return mService.getDeviceInitializerComponent(); + } catch (RemoteException re) { + Log.w(TAG, "Failed to get device initializer"); + } + } + return null; + } + + + /** * Sets the enabled state of the user. A user should be enabled only once it is ready to * be used. * @@ -3162,6 +3198,22 @@ public class DevicePolicyManager { } /** + * Start Quick Contact on the managed profile for the current user, if the policy allows. + * @hide + */ + public void startManagedQuickContact(String actualLookupKey, long actualContactId, + Intent originalIntent) { + if (mService != null) { + try { + mService.startManagedQuickContact( + actualLookupKey, actualContactId, originalIntent); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + + /** * Called by the profile owner of a managed profile so that some intents sent in the managed * profile can also be resolved in the parent, or vice versa. * Only activity intents are supported. diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 9ca52e5..c68311e 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -189,6 +189,7 @@ interface IDevicePolicyManager { void setCrossProfileCallerIdDisabled(in ComponentName who, boolean disabled); boolean getCrossProfileCallerIdDisabled(in ComponentName who); boolean getCrossProfileCallerIdDisabledForUser(int userId); + void startManagedQuickContact(String lookupKey, long contactId, in Intent originalIntent); void setTrustAgentConfiguration(in ComponentName admin, in ComponentName agent, in PersistableBundle args); @@ -209,6 +210,7 @@ interface IDevicePolicyManager { void clearDeviceInitializer(in ComponentName who); boolean setDeviceInitializer(in ComponentName who, in ComponentName initializer, String initializerName); String getDeviceInitializer(); + ComponentName getDeviceInitializerComponent(); void setUserIcon(in ComponentName admin, in Bitmap icon); } diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java index 7f89100..2bf267a 100644 --- a/core/java/android/app/backup/BackupAgent.java +++ b/core/java/android/app/backup/BackupAgent.java @@ -424,10 +424,12 @@ public abstract class BackupAgent extends ContextWrapper { } // And now that we know where it lives, semantically, back it up appropriately - Log.i(TAG, "backupFile() of " + filePath + " => domain=" + domain + // In the measurement case, backupToTar() updates the size in output and returns + // without transmitting any file data. + if (DEBUG) Log.i(TAG, "backupFile() of " + filePath + " => domain=" + domain + " rootpath=" + rootpath); - FullBackup.backupToTar(getPackageName(), domain, null, rootpath, filePath, - output.getData()); + + FullBackup.backupToTar(getPackageName(), domain, null, rootpath, filePath, output); } /** @@ -477,9 +479,8 @@ public abstract class BackupAgent extends ContextWrapper { continue; } - // Finally, back this file up before proceeding - FullBackup.backupToTar(packageName, domain, null, rootPath, filePath, - output.getData()); + // Finally, back this file up (or measure it) before proceeding + FullBackup.backupToTar(packageName, domain, null, rootPath, filePath, output); } } } @@ -640,7 +641,7 @@ public abstract class BackupAgent extends ContextWrapper { Binder.restoreCallingIdentity(ident); try { - callbackBinder.opComplete(token); + callbackBinder.opComplete(token, 0); } catch (RemoteException e) { // we'll time out anyway, so we're safe } @@ -670,7 +671,7 @@ public abstract class BackupAgent extends ContextWrapper { Binder.restoreCallingIdentity(ident); try { - callbackBinder.opComplete(token); + callbackBinder.opComplete(token, 0); } catch (RemoteException e) { // we'll time out anyway, so we're safe } @@ -692,10 +693,10 @@ public abstract class BackupAgent extends ContextWrapper { try { BackupAgent.this.onFullBackup(new FullBackupDataOutput(data)); } catch (IOException ex) { - Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); + Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); throw new RuntimeException(ex); } catch (RuntimeException ex) { - Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); + Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); throw ex; } finally { // ... and then again after, as in the doBackup() case @@ -713,13 +714,37 @@ public abstract class BackupAgent extends ContextWrapper { Binder.restoreCallingIdentity(ident); try { - callbackBinder.opComplete(token); + callbackBinder.opComplete(token, 0); } catch (RemoteException e) { // we'll time out anyway, so we're safe } } } + public void doMeasureFullBackup(int token, IBackupManager callbackBinder) { + // Ensure that we're running with the app's normal permission level + final long ident = Binder.clearCallingIdentity(); + FullBackupDataOutput measureOutput = new FullBackupDataOutput(); + + waitForSharedPrefs(); + try { + BackupAgent.this.onFullBackup(measureOutput); + } catch (IOException ex) { + Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex); + throw new RuntimeException(ex); + } catch (RuntimeException ex) { + Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex); + throw ex; + } finally { + Binder.restoreCallingIdentity(ident); + try { + callbackBinder.opComplete(token, measureOutput.getSize()); + } catch (RemoteException e) { + // timeout, so we're safe + } + } + } + @Override public void doRestoreFile(ParcelFileDescriptor data, long size, int type, String domain, String path, long mode, long mtime, @@ -728,6 +753,7 @@ public abstract class BackupAgent extends ContextWrapper { try { BackupAgent.this.onRestoreFile(data, size, type, domain, path, mode, mtime); } catch (IOException e) { + Log.d(TAG, "onRestoreFile (" + BackupAgent.this.getClass().getName() + ") threw", e); throw new RuntimeException(e); } finally { // Ensure that any side-effect SharedPreferences writes have landed @@ -735,7 +761,7 @@ public abstract class BackupAgent extends ContextWrapper { Binder.restoreCallingIdentity(ident); try { - callbackBinder.opComplete(token); + callbackBinder.opComplete(token, 0); } catch (RemoteException e) { // we'll time out anyway, so we're safe } @@ -747,13 +773,16 @@ public abstract class BackupAgent extends ContextWrapper { long ident = Binder.clearCallingIdentity(); try { BackupAgent.this.onRestoreFinished(); + } catch (Exception e) { + Log.d(TAG, "onRestoreFinished (" + BackupAgent.this.getClass().getName() + ") threw", e); + throw e; } finally { // Ensure that any side-effect SharedPreferences writes have landed waitForSharedPrefs(); Binder.restoreCallingIdentity(ident); try { - callbackBinder.opComplete(token); + callbackBinder.opComplete(token, 0); } catch (RemoteException e) { // we'll time out anyway, so we're safe } diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java index e853540..1131ff9 100644 --- a/core/java/android/app/backup/BackupTransport.java +++ b/core/java/android/app/backup/BackupTransport.java @@ -393,6 +393,26 @@ public class BackupTransport { } /** + * Called after {@link #performFullBackup} to make sure that the transport is willing to + * handle a full-data backup operation of the specified size on the current package. + * If the transport returns anything other than TRANSPORT_OK, the package's backup + * operation will be skipped (and {@link #finishBackup() invoked} with no data for that + * package being passed to {@link #sendBackupData}. + * + * Added in MNC (API 23). + * + * @param size The estimated size of the full-data payload for this app. This includes + * manifest and archive format overhead, but is not guaranteed to be precise. + * @return TRANSPORT_OK if the platform is to proceed with the full-data backup, + * TRANSPORT_PACKAGE_REJECTED if the proposed payload size is too large for + * the transport to handle, or TRANSPORT_ERROR to indicate a fatal error + * condition that means the platform cannot perform a backup at this time. + */ + public int checkFullBackupSize(long size) { + return BackupTransport.TRANSPORT_OK; + } + + /** * Tells the transport to read {@code numBytes} bytes of data from the socket file * descriptor provided in the {@link #performFullBackup(PackageInfo, ParcelFileDescriptor)} * call, and deliver those bytes to the datastore. @@ -444,7 +464,7 @@ public class BackupTransport { * transport level). * * <p>After this method returns zero, the system will then call - * {@link #getNextFullRestorePackage()} to begin the restore process for the next + * {@link #nextRestorePackage()} to begin the restore process for the next * application, and the sequence begins again. * * <p>The transport should always close this socket when returning from this method. @@ -588,6 +608,11 @@ public class BackupTransport { } @Override + public int checkFullBackupSize(long size) { + return BackupTransport.this.checkFullBackupSize(size); + } + + @Override public int sendBackupData(int numBytes) throws RemoteException { return BackupTransport.this.sendBackupData(numBytes); } diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java index e5b47c6..259884e 100644 --- a/core/java/android/app/backup/FullBackup.java +++ b/core/java/android/app/backup/FullBackup.java @@ -58,7 +58,7 @@ public class FullBackup { * @hide */ static public native int backupToTar(String packageName, String domain, - String linkdomain, String rootpath, String path, BackupDataOutput output); + String linkdomain, String rootpath, String path, FullBackupDataOutput output); /** * Copy data from a socket to the given File location on permanent storage. The diff --git a/core/java/android/app/backup/FullBackupDataOutput.java b/core/java/android/app/backup/FullBackupDataOutput.java index 99dab1f..94704b9 100644 --- a/core/java/android/app/backup/FullBackupDataOutput.java +++ b/core/java/android/app/backup/FullBackupDataOutput.java @@ -9,7 +9,14 @@ import android.os.ParcelFileDescriptor; */ public class FullBackupDataOutput { // Currently a name-scoping shim around BackupDataOutput - private BackupDataOutput mData; + private final BackupDataOutput mData; + private long mSize; + + /** @hide - used only in measure operation */ + public FullBackupDataOutput() { + mData = null; + mSize = 0; + } /** @hide */ public FullBackupDataOutput(ParcelFileDescriptor fd) { @@ -18,4 +25,14 @@ public class FullBackupDataOutput { /** @hide */ public BackupDataOutput getData() { return mData; } + + /** @hide - used for measurement pass */ + public void addSize(long size) { + if (size > 0) { + mSize += size; + } + } + + /** @hide - used for measurement pass */ + public long getSize() { return mSize; } } diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl index 41ad936..8f36dc4 100644 --- a/core/java/android/app/backup/IBackupManager.aidl +++ b/core/java/android/app/backup/IBackupManager.aidl @@ -286,11 +286,14 @@ interface IBackupManager { * Notify the backup manager that a BackupAgent has completed the operation * corresponding to the given token. * - * @param token The transaction token passed to a BackupAgent's doBackup() or - * doRestore() method. + * @param token The transaction token passed to the BackupAgent method being + * invoked. + * @param result In the case of a full backup measure operation, the estimated + * total file size that would result from the operation. Unused in all other + * cases. * {@hide} */ - void opComplete(int token); + void opComplete(int token, long result); /** * Make the device's backup and restore machinery (in)active. When it is inactive, diff --git a/core/java/android/app/backup/RecentsBackupHelper.java b/core/java/android/app/backup/RecentsBackupHelper.java index fd69d20..1a64da6 100644 --- a/core/java/android/app/backup/RecentsBackupHelper.java +++ b/core/java/android/app/backup/RecentsBackupHelper.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package android.app.backup; import android.content.Context; diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java index 0122069..8b3fc2e 100644 --- a/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -57,4 +57,41 @@ public abstract class UsageStatsManagerInternal { * Prepares the UsageStatsService for shutdown. */ public abstract void prepareShutdown(); + + /** + * Returns true if the app has not been used for a certain amount of time. How much time? + * Could be hours, could be days, who knows? + * + * @param packageName + * @param userId + * @return + */ + public abstract boolean isAppIdle(String packageName, int userId); + + /** + * Returns the most recent time that the specified package was active for the given user. + * @param packageName The package to search. + * @param userId The user id of the user of interest. + * @return The timestamp of when the package was last used, or -1 if it hasn't been used. + */ + public abstract long getLastPackageAccessTime(String packageName, int userId); + + /** + * Sets up a listener for changes to packages being accessed. + * @param listener A listener within the system process. + */ + public abstract void addAppIdleStateChangeListener( + AppIdleStateChangeListener listener); + + /** + * Removes a listener that was previously added for package usage state changes. + * @param listener The listener within the system process to remove. + */ + public abstract void removeAppIdleStateChangeListener( + AppIdleStateChangeListener listener); + + public interface AppIdleStateChangeListener { + void onAppIdleStateChanged(String packageName, int userId, boolean idle); + } + } diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index be26eac..edb768d 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -18,6 +18,7 @@ package android.bluetooth; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SystemApi; import android.bluetooth.le.BluetoothLeAdvertiser; import android.bluetooth.le.BluetoothLeScanner; import android.bluetooth.le.ScanCallback; @@ -206,6 +207,23 @@ public final class BluetoothAdapter { "android.bluetooth.adapter.action.REQUEST_ENABLE"; /** + * Activity Action: Show a system activity that allows user to enable BLE scans even when + * Bluetooth is turned off.<p> + * + * Notification of result of this activity is posted using + * {@link android.app.Activity#onActivityResult}. The <code>resultCode</code> will be + * {@link android.app.Activity#RESULT_OK} if BLE scan always available setting is turned on or + * {@link android.app.Activity#RESULT_CANCELED} if the user has rejected the request or an + * error occurred. + * + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE = + "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE"; + + /** * Broadcast Action: Indicates the Bluetooth scan mode of the local Adapter * has changed. * <p>Always contains the extra fields {@link #EXTRA_SCAN_MODE} and {@link @@ -916,6 +934,22 @@ public final class BluetoothAdapter { } /** + * Returns {@code true} if BLE scan is always available, {@code false} otherwise. <p> + * + * If this returns {@code true}, application can issue {@link BluetoothLeScanner#startScan} and + * fetch scan results even when Bluetooth is turned off.<p> + * + * To change this setting, use {@link #ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE}. + * + * @hide + */ + @SystemApi + public boolean isBleScanAlwaysAvailable() { + // TODO: implement after Settings UI change. + return false; + } + + /** * Returns whether peripheral mode is supported. * * @hide diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index e9d4e59..9450dce 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -16,6 +16,7 @@ package android.content; +import android.annotation.AttrRes; import android.annotation.CheckResult; import android.annotation.IntDef; import android.annotation.NonNull; @@ -474,8 +475,7 @@ public abstract class Context { * * @see android.content.res.Resources.Theme#obtainStyledAttributes(int[]) */ - public final TypedArray obtainStyledAttributes( - int[] attrs) { + public final TypedArray obtainStyledAttributes(@StyleableRes int[] attrs) { return getTheme().obtainStyledAttributes(attrs); } @@ -487,7 +487,7 @@ public abstract class Context { * @see android.content.res.Resources.Theme#obtainStyledAttributes(int, int[]) */ public final TypedArray obtainStyledAttributes( - @StyleableRes int resid, int[] attrs) throws Resources.NotFoundException { + @StyleRes int resid, @StyleableRes int[] attrs) throws Resources.NotFoundException { return getTheme().obtainStyledAttributes(resid, attrs); } @@ -499,7 +499,7 @@ public abstract class Context { * @see android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int) */ public final TypedArray obtainStyledAttributes( - AttributeSet set, int[] attrs) { + AttributeSet set, @StyleableRes int[] attrs) { return getTheme().obtainStyledAttributes(set, attrs, 0, 0); } @@ -511,7 +511,8 @@ public abstract class Context { * @see android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int) */ public final TypedArray obtainStyledAttributes( - AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) { + AttributeSet set, @StyleableRes int[] attrs, @AttrRes int defStyleAttr, + @StyleRes int defStyleRes) { return getTheme().obtainStyledAttributes( set, attrs, defStyleAttr, defStyleRes); } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 030b770..eea47b7 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1041,6 +1041,18 @@ public class Intent implements Parcelable, Cloneable { */ public static final String ACTION_CALL_PRIVILEGED = "android.intent.action.CALL_PRIVILEGED"; /** + * Activity action: Activate the current SIM card. If SIM cards do not require activation, + * sending this intent is a no-op. + * <p>Input: No data should be specified. get*Extra may have an optional + * {@link #EXTRA_SIM_ACTIVATION_RESPONSE} field containing a PendingIntent through which to + * send the activation result. + * <p>Output: nothing. + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SIM_ACTIVATION_REQUEST = + "android.intent.action.SIM_ACTIVATION_REQUEST"; + /** * Activity Action: Send a message to someone specified by the data. * <p>Input: {@link #getData} is URI describing the target. * <p>Output: nothing. @@ -1911,6 +1923,19 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_PACKAGE_VERIFIED = "android.intent.action.PACKAGE_VERIFIED"; /** + * Broadcast Action: Sent to the system intent filter verifier when an intent filter + * needs to be verified. The data contains the filter data hosts to be verified against. + * <p class="note"> + * This is a protected intent that can only be sent by the system. + * </p> + * + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION"; + + /** * Broadcast Action: Resources for a set of packages (which were * previously unavailable) are currently * available since the media on which they exist is available. @@ -2883,6 +2908,24 @@ public class Intent implements Parcelable, Cloneable { /** {@hide} */ public static final String EXTRA_SETTING_NEW_VALUE = "new_value"; + /** + * Activity Action: Process a piece of text. + * <p>Input: {@link #EXTRA_PROCESS_TEXT} contains the text to be processed. + * {@link #EXTRA_PROCESS_TEXT_READONLY} states if the resulting text will be read-only.</p> + * <p>Output: {@link #EXTRA_PROCESS_TEXT} contains the processed text.</p> + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_PROCESS_TEXT = "android.intent.action.PROCESS_TEXT"; + /** + * The name of the extra used to define the text to be processed. + */ + public static final String EXTRA_PROCESS_TEXT = "android.intent.extra.PROCESS_TEXT"; + /** + * The name of the extra used to define if the processed text will be used as read-only. + */ + public static final String EXTRA_PROCESS_TEXT_READONLY = + "android.intent.extra.PROCESS_TEXT_READONLY"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent categories (see addCategory()). @@ -3607,6 +3650,15 @@ public class Intent implements Parcelable, Cloneable { /** {@hide} */ public static final String EXTRA_REASON = "android.intent.extra.REASON"; + /** + * Optional {@link android.app.PendingIntent} extra used to deliver the result of the SIM + * activation request. + * TODO: Add information about the structure and response data used with the pending intent. + * @hide + */ + public static final String EXTRA_SIM_ACTIVATION_RESPONSE = + "android.intent.extra.SIM_ACTIVATION_RESPONSE"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Intent flags (see mFlags variable). diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java index 1240a23..590d791 100644 --- a/core/java/android/content/IntentFilter.java +++ b/core/java/android/content/IntentFilter.java @@ -16,10 +16,12 @@ package android.content; +import android.content.pm.PackageParser; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; import android.os.PatternMatcher; +import android.text.TextUtils; import android.util.AndroidException; import android.util.Log; import android.util.Printer; @@ -150,6 +152,7 @@ public class IntentFilter implements Parcelable { private static final String CAT_STR = "cat"; private static final String NAME_STR = "name"; private static final String ACTION_STR = "action"; + private static final String AUTO_VERIFY_STR = "autoVerify"; /** * The filter {@link #setPriority} value at which system high-priority @@ -247,6 +250,19 @@ public class IntentFilter implements Parcelable { */ public static final int NO_MATCH_CATEGORY = -4; + /** + * HTTP scheme. + * + * @see #addDataScheme(String) + */ + public static final String SCHEME_HTTP = "http"; + /** + * HTTPS scheme. + * + * @see #addDataScheme(String) + */ + public static final String SCHEME_HTTPS = "https"; + private int mPriority; private final ArrayList<String> mActions; private ArrayList<String> mCategories = null; @@ -257,6 +273,13 @@ public class IntentFilter implements Parcelable { private ArrayList<String> mDataTypes = null; private boolean mHasPartialTypes = false; + private static final int STATE_VERIFY_AUTO = 0x00000001; + private static final int STATE_NEED_VERIFY = 0x00000010; + private static final int STATE_NEED_VERIFY_CHECKED = 0x00000100; + private static final int STATE_VERIFIED = 0x00001000; + + private int mVerifyState; + // These functions are the start of more optimized code for managing // the string sets... not yet implemented. @@ -326,7 +349,7 @@ public class IntentFilter implements Parcelable { public MalformedMimeTypeException(String name) { super(name); } - }; + } /** * Create a new IntentFilter instance with a specified action and MIME @@ -421,6 +444,7 @@ public class IntentFilter implements Parcelable { mDataPaths = new ArrayList<PatternMatcher>(o.mDataPaths); } mHasPartialTypes = o.mHasPartialTypes; + mVerifyState = o.mVerifyState; } /** @@ -452,6 +476,94 @@ public class IntentFilter implements Parcelable { } /** + * Set whether this filter will needs to be automatically verified against its data URIs or not. + * The default is false. + * + * The verification would need to happen only and only if the Intent action is + * {@link android.content.Intent#ACTION_VIEW} and the Intent category is + * {@link android.content.Intent#CATEGORY_BROWSABLE} and the Intent data scheme + * is "http" or "https". + * + * True means that the filter will need to use its data URIs to be verified. + * + * @param autoVerify The new autoVerify value. + * + * @see #getAutoVerify() + * @see #addAction(String) + * @see #getAction(int) + * @see #addCategory(String) + * @see #getCategory(int) + * @see #addDataScheme(String) + * @see #getDataScheme(int) + * + * @hide + */ + public final void setAutoVerify(boolean autoVerify) { + mVerifyState &= ~STATE_VERIFY_AUTO; + if (autoVerify) mVerifyState |= STATE_VERIFY_AUTO; + } + + /** + * Return if this filter will needs to be automatically verified again its data URIs or not. + * + * @return True if the filter will needs to be automatically verified. False otherwise. + * + * @see #setAutoVerify(boolean) + * + * @hide + */ + public final boolean getAutoVerify() { + return ((mVerifyState & STATE_VERIFY_AUTO) == 1); + } + + /** + * Return if this filter needs to be automatically verified again its data URIs or not. + * + * @return True if the filter needs to be automatically verified. False otherwise. + * + * This will check if if the Intent action is {@link android.content.Intent#ACTION_VIEW} and + * the Intent category is {@link android.content.Intent#CATEGORY_BROWSABLE} and the Intent + * data scheme is "http" or "https". + * + * @see #setAutoVerify(boolean) + * + * @hide + */ + public final boolean needsVerification() { + return hasAction(Intent.ACTION_VIEW) && + hasCategory(Intent.CATEGORY_BROWSABLE) && + (hasDataScheme(SCHEME_HTTP) || hasDataScheme(SCHEME_HTTPS)) && + getAutoVerify(); + } + + /** + * Return if this filter has been verified + * + * @return true if the filter has been verified or if autoVerify is false. + * + * @hide + */ + public final boolean isVerified() { + if ((mVerifyState & STATE_NEED_VERIFY_CHECKED) == STATE_NEED_VERIFY_CHECKED) { + return ((mVerifyState & STATE_NEED_VERIFY) == STATE_NEED_VERIFY); + } + return false; + } + + /** + * Set if this filter has been verified + * + * @param verified true if this filter has been verified. False otherwise. + * + * @hide + */ + public void setVerified(boolean verified) { + mVerifyState |= STATE_NEED_VERIFY_CHECKED; + mVerifyState &= ~STATE_VERIFIED; + if (verified) mVerifyState |= STATE_VERIFIED; + } + + /** * Add a new Intent action to match against. If any actions are included * in the filter, then an Intent's action must be one of those values for * it to match. If no actions are included, the Intent action is ignored. @@ -1333,6 +1445,7 @@ public class IntentFilter implements Parcelable { * Write the contents of the IntentFilter as an XML stream. */ public void writeToXml(XmlSerializer serializer) throws IOException { + serializer.attribute(null, AUTO_VERIFY_STR, Boolean.toString(getAutoVerify())); int N = countActions(); for (int i=0; i<N; i++) { serializer.startTag(null, ACTION_STR); @@ -1407,6 +1520,9 @@ public class IntentFilter implements Parcelable { public void readFromXml(XmlPullParser parser) throws XmlPullParserException, IOException { + String autoVerify = parser.getAttributeValue(null, AUTO_VERIFY_STR); + setAutoVerify(TextUtils.isEmpty(autoVerify) ? false : Boolean.getBoolean(autoVerify)); + int outerDepth = parser.getDepth(); int type; while ((type=parser.next()) != XmlPullParser.END_DOCUMENT @@ -1548,6 +1664,11 @@ public class IntentFilter implements Parcelable { sb.append(", mHasPartialTypes="); sb.append(mHasPartialTypes); du.println(sb.toString()); } + { + sb.setLength(0); + sb.append(prefix); sb.append("AutoVerify="); sb.append(getAutoVerify()); + du.println(sb.toString()); + } } public static final Parcelable.Creator<IntentFilter> CREATOR @@ -1614,6 +1735,7 @@ public class IntentFilter implements Parcelable { } dest.writeInt(mPriority); dest.writeInt(mHasPartialTypes ? 1 : 0); + dest.writeInt(getAutoVerify() ? 1 : 0); } /** @@ -1680,6 +1802,7 @@ public class IntentFilter implements Parcelable { } mPriority = source.readInt(); mHasPartialTypes = source.readInt() > 0; + setAutoVerify(source.readInt() > 0); } private final boolean findMimeType(String type) { @@ -1724,4 +1847,27 @@ public class IntentFilter implements Parcelable { return false; } + + /** + * @hide + */ + public ArrayList<String> getHostsList() { + ArrayList<String> result = new ArrayList<>(); + Iterator<IntentFilter.AuthorityEntry> it = authoritiesIterator(); + if (it != null) { + while (it.hasNext()) { + IntentFilter.AuthorityEntry entry = it.next(); + result.add(entry.getHost()); + } + } + return result; + } + + /** + * @hide + */ + public String[] getHosts() { + ArrayList<String> list = getHostsList(); + return list.toArray(new String[list.size()]); + } } diff --git a/core/java/android/content/RestrictionEntry.java b/core/java/android/content/RestrictionEntry.java index 6d79626..342ee38 100644 --- a/core/java/android/content/RestrictionEntry.java +++ b/core/java/android/content/RestrictionEntry.java @@ -20,6 +20,9 @@ import android.annotation.ArrayRes; import android.os.Parcel; import android.os.Parcelable; +import java.util.Arrays; +import java.util.Objects; + /** * Applications can expose restrictions for a restricted user on a * multiuser device. The administrator can configure these restrictions that will then be @@ -33,19 +36,19 @@ import android.os.Parcelable; public class RestrictionEntry implements Parcelable { /** - * A type of restriction. Use this type for information that needs to be transferred across - * but shouldn't be presented to the user in the UI. Stores a single String value. + * Hidden restriction type. Use this type for information that needs to be transferred + * across but shouldn't be presented to the user in the UI. Stores a single String value. */ public static final int TYPE_NULL = 0; /** - * A type of restriction. Use this for storing a boolean value, typically presented as + * Restriction of type "bool". Use this for storing a boolean value, typically presented as * a checkbox in the UI. */ public static final int TYPE_BOOLEAN = 1; /** - * A type of restriction. Use this for storing a string value, typically presented as + * Restriction of type "choice". Use this for storing a string value, typically presented as * a single-select list. Call {@link #setChoiceEntries(String[])} and * {@link #setChoiceValues(String[])} to set the localized list entries to present to the user * and the corresponding values, respectively. @@ -53,7 +56,7 @@ public class RestrictionEntry implements Parcelable { public static final int TYPE_CHOICE = 2; /** - * A type of restriction. Use this for storing a string value, typically presented as + * Internal restriction type. Use this for storing a string value, typically presented as * a single-select list. Call {@link #setChoiceEntries(String[])} and * {@link #setChoiceValues(String[])} to set the localized list entries to present to the user * and the corresponding values, respectively. @@ -64,8 +67,8 @@ public class RestrictionEntry implements Parcelable { public static final int TYPE_CHOICE_LEVEL = 3; /** - * A type of restriction. Use this for presenting a multi-select list where more than one - * entry can be selected, such as for choosing specific titles to white-list. + * Restriction of type "multi-select". Use this for presenting a multi-select list where more + * than one entry can be selected, such as for choosing specific titles to white-list. * Call {@link #setChoiceEntries(String[])} and * {@link #setChoiceValues(String[])} to set the localized list entries to present to the user * and the corresponding values, respectively. @@ -75,18 +78,30 @@ public class RestrictionEntry implements Parcelable { public static final int TYPE_MULTI_SELECT = 4; /** - * A type of restriction. Use this for storing an integer value. The range of values + * Restriction of type "integer". Use this for storing an integer value. The range of values * is from {@link Integer#MIN_VALUE} to {@link Integer#MAX_VALUE}. */ public static final int TYPE_INTEGER = 5; /** - * A type of restriction. Use this for storing a string value. + * Restriction of type "string". Use this for storing a string value. * @see #setSelectedString * @see #getSelectedString */ public static final int TYPE_STRING = 6; + /** + * Restriction of type "bundle". Use this for storing {@link android.os.Bundle bundles} of + * restrictions + */ + public static final int TYPE_BUNDLE = 7; + + /** + * Restriction of type "bundle_array". Use this for storing arrays of + * {@link android.os.Bundle bundles} of restrictions + */ + public static final int TYPE_BUNDLE_ARRAY = 8; + /** The type of restriction. */ private int mType; @@ -100,13 +115,13 @@ public class RestrictionEntry implements Parcelable { private String mDescription; /** The user-visible set of choices used for single-select and multi-select lists. */ - private String [] mChoiceEntries; + private String[] mChoiceEntries; /** The values corresponding to the user-visible choices. The value(s) of this entry will * one or more of these, returned by {@link #getAllSelectedStrings()} and * {@link #getSelectedString()}. */ - private String [] mChoiceValues; + private String[] mChoiceValues; /* The chosen value, whose content depends on the type of the restriction. */ private String mCurrentValue; @@ -115,6 +130,12 @@ public class RestrictionEntry implements Parcelable { private String[] mCurrentValues; /** + * List of nested restrictions. Used by {@link #TYPE_BUNDLE bundle} and + * {@link #TYPE_BUNDLE_ARRAY bundle_array} restrictions. + */ + private RestrictionEntry[] mRestrictions; + + /** * Constructor for specifying the type and key, with no initial value; * * @param type the restriction type. @@ -170,6 +191,35 @@ public class RestrictionEntry implements Parcelable { } /** + * Constructor for {@link #TYPE_BUNDLE}/{@link #TYPE_BUNDLE_ARRAY} type. + * @param key the unique key for this restriction + * @param restrictionEntries array of nested restriction entries. If the entry, being created + * represents a {@link #TYPE_BUNDLE_ARRAY bundle-array}, {@code restrictionEntries} array may + * only contain elements of type {@link #TYPE_BUNDLE bundle}. + * @param isBundleArray true if this restriction represents + * {@link #TYPE_BUNDLE_ARRAY bundle-array} type, otherwise the type will be set to + * {@link #TYPE_BUNDLE bundle}. + */ + public RestrictionEntry(String key, RestrictionEntry[] restrictionEntries, + boolean isBundleArray) { + mKey = key; + if (isBundleArray) { + mType = TYPE_BUNDLE_ARRAY; + if (restrictionEntries != null) { + for (RestrictionEntry restriction : restrictionEntries) { + if (restriction.getType() != TYPE_BUNDLE) { + throw new IllegalArgumentException("bundle_array restriction can only have " + + "nested restriction entries of type bundle"); + } + } + } + } else { + mType = TYPE_BUNDLE; + } + setRestrictions(restrictionEntries); + } + + /** * Sets the type for this restriction. * @param type the type for this restriction. */ @@ -283,6 +333,22 @@ public class RestrictionEntry implements Parcelable { } /** + * Returns array of possible restriction entries that this entry may contain. + */ + public RestrictionEntry[] getRestrictions() { + return mRestrictions; + } + + /** + * Sets an array of possible restriction entries, that this entry may contain. + * <p>This method is only relevant for types {@link #TYPE_BUNDLE} and + * {@link #TYPE_BUNDLE_ARRAY} + */ + public void setRestrictions(RestrictionEntry[] restrictions) { + mRestrictions = restrictions; + } + + /** * Returns the list of possible string values set earlier. * @return the list of possible values. */ @@ -362,27 +428,30 @@ public class RestrictionEntry implements Parcelable { this.mTitle = title; } - private boolean equalArrays(String[] one, String[] other) { - if (one.length != other.length) return false; - for (int i = 0; i < one.length; i++) { - if (!one[i].equals(other[i])) return false; - } - return true; - } - @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof RestrictionEntry)) return false; final RestrictionEntry other = (RestrictionEntry) o; - // Make sure that either currentValue matches or currentValues matches. - return mType == other.mType && mKey.equals(other.mKey) - && - ((mCurrentValues == null && other.mCurrentValues == null - && mCurrentValue != null && mCurrentValue.equals(other.mCurrentValue)) - || - (mCurrentValue == null && other.mCurrentValue == null - && mCurrentValues != null && equalArrays(mCurrentValues, other.mCurrentValues))); + if (mType != other.mType || mKey.equals(other.mKey)) { + return false; + } + if (mCurrentValues == null && other.mCurrentValues == null + && mRestrictions == null && other.mRestrictions == null + && Objects.equals(mCurrentValue, other.mCurrentValue)) { + return true; + } + if (mCurrentValue == null && other.mCurrentValue == null + && mRestrictions == null && other.mRestrictions == null + && Arrays.equals(mCurrentValues, other.mCurrentValues)) { + return true; + } + if (mCurrentValue == null && other.mCurrentValue == null + && mCurrentValue == null && other.mCurrentValue == null + && Arrays.equals(mRestrictions, other.mRestrictions)) { + return true; + } + return false; } @Override @@ -397,28 +466,28 @@ public class RestrictionEntry implements Parcelable { result = 31 * result + value.hashCode(); } } + } else if (mRestrictions != null) { + result = 31 * result + Arrays.hashCode(mRestrictions); } return result; } - private String[] readArray(Parcel in) { - int count = in.readInt(); - String[] values = new String[count]; - for (int i = 0; i < count; i++) { - values[i] = in.readString(); - } - return values; - } - public RestrictionEntry(Parcel in) { mType = in.readInt(); mKey = in.readString(); mTitle = in.readString(); mDescription = in.readString(); - mChoiceEntries = readArray(in); - mChoiceValues = readArray(in); + mChoiceEntries = in.readStringArray(); + mChoiceValues = in.readStringArray(); mCurrentValue = in.readString(); - mCurrentValues = readArray(in); + mCurrentValues = in.readStringArray(); + Parcelable[] parcelables = in.readParcelableArray(null); + if (parcelables != null) { + mRestrictions = new RestrictionEntry[parcelables.length]; + for (int i = 0; i < parcelables.length; i++) { + mRestrictions[i] = (RestrictionEntry) parcelables[i]; + } + } } @Override @@ -426,27 +495,17 @@ public class RestrictionEntry implements Parcelable { return 0; } - private void writeArray(Parcel dest, String[] values) { - if (values == null) { - dest.writeInt(0); - } else { - dest.writeInt(values.length); - for (int i = 0; i < values.length; i++) { - dest.writeString(values[i]); - } - } - } - @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mType); dest.writeString(mKey); dest.writeString(mTitle); dest.writeString(mDescription); - writeArray(dest, mChoiceEntries); - writeArray(dest, mChoiceValues); + dest.writeStringArray(mChoiceEntries); + dest.writeStringArray(mChoiceValues); dest.writeString(mCurrentValue); - writeArray(dest, mCurrentValues); + dest.writeStringArray(mCurrentValues); + dest.writeParcelableArray(mRestrictions, 0); } public static final Creator<RestrictionEntry> CREATOR = new Creator<RestrictionEntry>() { @@ -461,6 +520,16 @@ public class RestrictionEntry implements Parcelable { @Override public String toString() { - return "RestrictionsEntry {type=" + mType + ", key=" + mKey + ", value=" + mCurrentValue + "}"; + return "RestrictionEntry{" + + "mType=" + mType + + ", mKey='" + mKey + '\'' + + ", mTitle='" + mTitle + '\'' + + ", mDescription='" + mDescription + '\'' + + ", mChoiceEntries=" + Arrays.toString(mChoiceEntries) + + ", mChoiceValues=" + Arrays.toString(mChoiceValues) + + ", mCurrentValue='" + mCurrentValue + '\'' + + ", mCurrentValues=" + Arrays.toString(mCurrentValues) + + ", mRestrictions=" + Arrays.toString(mRestrictions) + + '}'; } } diff --git a/core/java/android/content/RestrictionsManager.java b/core/java/android/content/RestrictionsManager.java index 21a6a0d..1fac06e 100644 --- a/core/java/android/content/RestrictionsManager.java +++ b/core/java/android/content/RestrictionsManager.java @@ -32,12 +32,14 @@ import android.util.Log; import android.util.Xml; import com.android.internal.R; +import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -71,12 +73,15 @@ import java.util.List; * android:key="string" * android:title="string resource" * android:restrictionType=["bool" | "string" | "integer" - * | "choice" | "multi-select" | "hidden"] + * | "choice" | "multi-select" | "hidden" + * | "bundle" | "bundle_array"] * android:description="string resource" * android:entries="string-array resource" * android:entryValues="string-array resource" - * android:defaultValue="reference" - * /> + * android:defaultValue="reference" > + * <restriction ... /> + * ... + * </restriction> * <restriction ... /> * ... * </restrictions> @@ -97,6 +102,9 @@ import java.util.List; * administrator controlling the values, if the title is not sufficient.</li> * </ul> * <p> + * Only restrictions of type {@code bundle} and {@code bundle_array} can have one or multiple nested + * restriction elements. + * <p> * In your manifest's <code>application</code> section, add the meta-data tag to point to * the restrictions XML file as shown below: * <pre> @@ -537,9 +545,7 @@ public class RestrictionsManager { XmlResourceParser xml = appInfo.loadXmlMetaData(mContext.getPackageManager(), META_DATA_APP_RESTRICTIONS); - List<RestrictionEntry> restrictions = loadManifestRestrictions(packageName, xml); - - return restrictions; + return loadManifestRestrictions(packageName, xml); } private List<RestrictionEntry> loadManifestRestrictions(String packageName, @@ -550,23 +556,16 @@ public class RestrictionsManager { } catch (NameNotFoundException nnfe) { return null; } - ArrayList<RestrictionEntry> restrictions = new ArrayList<RestrictionEntry>(); + ArrayList<RestrictionEntry> restrictions = new ArrayList<>(); RestrictionEntry restriction; try { int tagType = xml.next(); while (tagType != XmlPullParser.END_DOCUMENT) { if (tagType == XmlPullParser.START_TAG) { - if (xml.getName().equals(TAG_RESTRICTION)) { - AttributeSet attrSet = Xml.asAttributeSet(xml); - if (attrSet != null) { - TypedArray a = appContext.obtainStyledAttributes(attrSet, - com.android.internal.R.styleable.RestrictionEntry); - restriction = loadRestriction(appContext, a); - if (restriction != null) { - restrictions.add(restriction); - } - } + restriction = loadRestrictionElement(appContext, xml); + if (restriction != null) { + restrictions.add(restriction); } } tagType = xml.next(); @@ -582,7 +581,21 @@ public class RestrictionsManager { return restrictions; } - private RestrictionEntry loadRestriction(Context appContext, TypedArray a) { + private RestrictionEntry loadRestrictionElement(Context appContext, XmlResourceParser xml) + throws IOException, XmlPullParserException { + if (xml.getName().equals(TAG_RESTRICTION)) { + AttributeSet attrSet = Xml.asAttributeSet(xml); + if (attrSet != null) { + TypedArray a = appContext.obtainStyledAttributes(attrSet, + com.android.internal.R.styleable.RestrictionEntry); + return loadRestriction(appContext, a, xml); + } + } + return null; + } + + private RestrictionEntry loadRestriction(Context appContext, TypedArray a, XmlResourceParser xml) + throws IOException, XmlPullParserException { String key = a.getString(R.styleable.RestrictionEntry_key); int restrictionType = a.getInt( R.styleable.RestrictionEntry_restrictionType, -1); @@ -633,9 +646,90 @@ public class RestrictionsManager { restriction.setSelectedState( a.getBoolean(R.styleable.RestrictionEntry_defaultValue, false)); break; + case RestrictionEntry.TYPE_BUNDLE: + case RestrictionEntry.TYPE_BUNDLE_ARRAY: + final int outerDepth = xml.getDepth(); + List<RestrictionEntry> restrictionEntries = new ArrayList<>(); + while (XmlUtils.nextElementWithin(xml, outerDepth)) { + RestrictionEntry childEntry = loadRestrictionElement(appContext, xml); + if (childEntry == null) { + Log.w(TAG, "Child entry cannot be loaded for bundle restriction " + key); + } else { + restrictionEntries.add(childEntry); + if (restrictionType == RestrictionEntry.TYPE_BUNDLE_ARRAY + && childEntry.getType() != RestrictionEntry.TYPE_BUNDLE) { + Log.w(TAG, "bundle_array " + key + + " can only contain entries of type bundle"); + } + } + } + restriction.setRestrictions(restrictionEntries.toArray(new RestrictionEntry[ + restrictionEntries.size()])); + break; default: Log.w(TAG, "Unknown restriction type " + restrictionType); } return restriction; } + + /** + * Converts a list of restrictions to the corresponding bundle, using the following mapping: + * <table> + * <tr><th>RestrictionEntry</th><th>Bundle</th></tr> + * <tr><td>{@link RestrictionEntry#TYPE_BOOLEAN}</td><td>{@link Bundle#putBoolean}</td></tr> + * <tr><td>{@link RestrictionEntry#TYPE_CHOICE}, {@link RestrictionEntry#TYPE_CHOICE}</td> + * <td>{@link Bundle#putStringArray}</td></tr> + * <tr><td>{@link RestrictionEntry#TYPE_INTEGER}</td><td>{@link Bundle#putInt}</td></tr> + * <tr><td>{@link RestrictionEntry#TYPE_STRING}</td><td>{@link Bundle#putString}</td></tr> + * <tr><td>{@link RestrictionEntry#TYPE_BUNDLE}</td><td>{@link Bundle#putBundle}</td></tr> + * <tr><td>{@link RestrictionEntry#TYPE_BUNDLE_ARRAY}</td> + * <td>{@link Bundle#putParcelableArray}</td></tr> + * </table> + * @param entries list of restrictions + */ + public static Bundle convertRestrictionsToBundle(List<RestrictionEntry> entries) { + final Bundle bundle = new Bundle(); + for (RestrictionEntry entry : entries) { + addRestrictionToBundle(bundle, entry); + } + return bundle; + } + + private static Bundle addRestrictionToBundle(Bundle bundle, RestrictionEntry entry) { + switch (entry.getType()) { + case RestrictionEntry.TYPE_BOOLEAN: + bundle.putBoolean(entry.getKey(), entry.getSelectedState()); + break; + case RestrictionEntry.TYPE_CHOICE: + case RestrictionEntry.TYPE_CHOICE_LEVEL: + case RestrictionEntry.TYPE_MULTI_SELECT: + bundle.putStringArray(entry.getKey(), entry.getAllSelectedStrings()); + break; + case RestrictionEntry.TYPE_INTEGER: + bundle.putInt(entry.getKey(), entry.getIntValue()); + break; + case RestrictionEntry.TYPE_STRING: + case RestrictionEntry.TYPE_NULL: + bundle.putString(entry.getKey(), entry.getSelectedString()); + break; + case RestrictionEntry.TYPE_BUNDLE: + RestrictionEntry[] restrictions = entry.getRestrictions(); + Bundle childBundle = convertRestrictionsToBundle(Arrays.asList(restrictions)); + bundle.putBundle(entry.getKey(), childBundle); + break; + case RestrictionEntry.TYPE_BUNDLE_ARRAY: + restrictions = entry.getRestrictions(); + Bundle[] bundleArray = new Bundle[restrictions.length]; + for (int i = 0; i < restrictions.length; i++) { + bundleArray[i] = addRestrictionToBundle(new Bundle(), restrictions[i]); + } + bundle.putParcelableArray(entry.getKey(), bundleArray); + break; + default: + throw new IllegalArgumentException( + "Unsupported restrictionEntry type: " + entry.getType()); + } + return bundle; + } + } diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 8f17845..5bdb7bb 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -340,8 +340,6 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * cleartext network traffic, in which case platform components (e.g., HTTP stacks, * {@code WebView}, {@code MediaPlayer}) will refuse app's requests to use cleartext traffic. * Third-party libraries are encouraged to honor this flag as well. - * - * @hide */ public static final int FLAG_USES_CLEARTEXT_TRAFFIC = 1<<27; @@ -379,7 +377,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * {@link #FLAG_LARGE_HEAP}, {@link #FLAG_STOPPED}, * {@link #FLAG_SUPPORTS_RTL}, {@link #FLAG_INSTALLED}, * {@link #FLAG_IS_DATA_ONLY}, {@link #FLAG_IS_GAME}, - * {@link #FLAG_FULL_BACKUP_ONLY}, {@link #FLAG_MULTIARCH}. + * {@link #FLAG_FULL_BACKUP_ONLY}, {@link #FLAG_USES_CLEARTEXT_TRAFFIC}, + * {@link #FLAG_MULTIARCH}. */ public int flags = 0; @@ -422,6 +421,14 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public static final int PRIVATE_FLAG_PRIVILEGED = 1<<3; /** + * Value for {@link #flags}: {@code true} if the application has any IntentFiler with some + * data URI using HTTP or HTTPS with an associated VIEW action. + * + * {@hide} + */ + public static final int PRIVATE_FLAG_HAS_DOMAIN_URLS = 1<<4; + + /** * Private/hidden flags. See {@code PRIVATE_FLAG_...} constants. * {@hide} */ @@ -452,6 +459,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public int largestWidthLimitDp = 0; /** {@hide} */ + public String volumeUuid; + /** {@hide} */ public String scanSourceDir; /** {@hide} */ public String scanPublicSourceDir; @@ -655,7 +664,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } pw.println(prefix + "dataDir=" + dataDir); if (sharedLibraryFiles != null) { - pw.println(prefix + "sharedLibraryFiles=" + sharedLibraryFiles); + pw.println(prefix + "sharedLibraryFiles=" + Arrays.toString(sharedLibraryFiles)); } pw.println(prefix + "enabled=" + enabled + " targetSdkVersion=" + targetSdkVersion + " versionCode=" + versionCode); @@ -719,6 +728,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { requiresSmallestWidthDp = orig.requiresSmallestWidthDp; compatibleWidthLimitDp = orig.compatibleWidthLimitDp; largestWidthLimitDp = orig.largestWidthLimitDp; + volumeUuid = orig.volumeUuid; scanSourceDir = orig.scanSourceDir; scanPublicSourceDir = orig.scanPublicSourceDir; sourceDir = orig.sourceDir; @@ -771,6 +781,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeInt(requiresSmallestWidthDp); dest.writeInt(compatibleWidthLimitDp); dest.writeInt(largestWidthLimitDp); + dest.writeString(volumeUuid); dest.writeString(scanSourceDir); dest.writeString(scanPublicSourceDir); dest.writeString(sourceDir); @@ -822,6 +833,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { requiresSmallestWidthDp = source.readInt(); compatibleWidthLimitDp = source.readInt(); largestWidthLimitDp = source.readInt(); + volumeUuid = source.readString(); scanSourceDir = source.readString(); scanPublicSourceDir = source.readString(); sourceDir = source.readString(); @@ -914,6 +926,20 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { /** * @hide */ + public boolean isSystemApp() { + return (flags & ApplicationInfo.FLAG_SYSTEM) != 0; + } + + /** + * @hide + */ + public boolean isUpdatedSystemApp() { + return (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; + } + + /** + * @hide + */ @Override protected ApplicationInfo getApplicationInfo() { return this; } diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl index ba62cd6..154ff85 100644 --- a/core/java/android/content/pm/IPackageInstaller.aidl +++ b/core/java/android/content/pm/IPackageInstaller.aidl @@ -44,7 +44,8 @@ interface IPackageInstaller { void registerCallback(IPackageInstallerCallback callback, int userId); void unregisterCallback(IPackageInstallerCallback callback); - void uninstall(String packageName, int flags, in IntentSender statusReceiver, int userId); + void uninstall(String packageName, String callerPackageName, int flags, + in IntentSender statusReceiver, int userId); void setPermissionsResult(int sessionId, boolean accepted); } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index c6d97f1..55c990f 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -31,6 +31,7 @@ import android.content.pm.IPackageDeleteObserver2; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageMoveObserver; import android.content.pm.IPackageStatsObserver; +import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.InstrumentationInfo; import android.content.pm.KeySet; import android.content.pm.PackageInfo; @@ -268,6 +269,12 @@ interface IPackageManager { void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage); /** + * Backup/restore support - only the system uid may use these. + */ + byte[] getPreferredActivityBackup(int userId); + void restorePreferredActivities(in byte[] backup, int userId); + + /** * Report the set of 'Home' activity candidates, plus (if any) which of them * is the current "always use this one" setting. */ @@ -425,7 +432,8 @@ interface IPackageManager { PackageCleanItem nextPackageToClean(in PackageCleanItem lastPackage); void movePackage(String packageName, IPackageMoveObserver observer, int flags); - + void movePackageAndData(String packageName, String volumeUuid, IPackageMoveObserver observer); + boolean addPermissionAsync(in PermissionInfo info); boolean setInstallLocation(int loc); @@ -436,6 +444,12 @@ interface IPackageManager { void verifyPendingInstall(int id, int verificationCode); void extendVerificationTimeout(int id, int verificationCodeAtTimeout, long millisecondsToDelay); + void verifyIntentFilter(int id, int verificationCode, in List<String> failedDomains); + int getIntentVerificationStatus(String packageName, int userId); + boolean updateIntentVerificationStatus(String packageName, int status, int userId); + List<IntentFilterVerificationInfo> getIntentFilterVerifications(String packageName); + List<IntentFilter> getAllIntentFilters(String packageName); + VerifierDeviceIdentity getVerifierDeviceIdentity(); boolean isFirstBoot(); diff --git a/core/java/android/content/pm/IntentFilterVerificationInfo.aidl b/core/java/android/content/pm/IntentFilterVerificationInfo.aidl new file mode 100644 index 0000000..00220e5 --- /dev/null +++ b/core/java/android/content/pm/IntentFilterVerificationInfo.aidl @@ -0,0 +1,19 @@ +/* + * 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.content.pm; + +parcelable IntentFilterVerificationInfo; diff --git a/core/java/android/content/pm/IntentFilterVerificationInfo.java b/core/java/android/content/pm/IntentFilterVerificationInfo.java new file mode 100644 index 0000000..28cbaa8 --- /dev/null +++ b/core/java/android/content/pm/IntentFilterVerificationInfo.java @@ -0,0 +1,225 @@ +/* + * 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.content.pm; + +import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; +import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK; +import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS; +import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER; + +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.ArraySet; +import android.util.Log; +import com.android.internal.util.XmlUtils; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.util.ArrayList; + +/** + * The {@link com.android.server.pm.PackageManagerService} maintains some + * {@link IntentFilterVerificationInfo}s for each domain / package / class name per user. + * + * @hide + */ +public final class IntentFilterVerificationInfo implements Parcelable { + private static final String TAG = IntentFilterVerificationInfo.class.getName(); + + private static final String TAG_DOMAIN = "domain"; + private static final String ATTR_DOMAIN_NAME = "name"; + private static final String ATTR_PACKAGE_NAME = "packageName"; + private static final String ATTR_STATUS = "status"; + + private ArrayList<String> mDomains; + private String mPackageName; + private int mMainStatus; + + public IntentFilterVerificationInfo() { + mPackageName = null; + mDomains = new ArrayList<>(); + mMainStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; + } + + public IntentFilterVerificationInfo(String packageName, ArrayList<String> domains) { + mPackageName = packageName; + mDomains = domains; + mMainStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; + } + + public IntentFilterVerificationInfo(XmlPullParser parser) + throws IOException, XmlPullParserException { + readFromXml(parser); + } + + public IntentFilterVerificationInfo(Parcel source) { + readFromParcel(source); + } + + public ArrayList<String> getDomains() { + return mDomains; + } + + public ArraySet<String> getDomainsSet() { + return new ArraySet<>(mDomains); + } + + public String getPackageName() { + return mPackageName; + } + + public int getStatus() { + return mMainStatus; + } + + public void setStatus(int s) { + if (s >= INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED && + s <= INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) { + mMainStatus = s; + } else { + Log.w(TAG, "Trying to set a non supported status: " + s); + } + } + + public String getDomainsString() { + StringBuilder sb = new StringBuilder(); + for (String str : mDomains) { + if (sb.length() > 0) { + sb.append(" "); + } + sb.append(str); + } + return sb.toString(); + } + + String getStringFromXml(XmlPullParser parser, String attribute, String defaultValue) { + String value = parser.getAttributeValue(null, attribute); + if (value == null) { + String msg = "Missing element under " + TAG +": " + attribute + " at " + + parser.getPositionDescription(); + Log.w(TAG, msg); + return defaultValue; + } else { + return value; + } + } + + int getIntFromXml(XmlPullParser parser, String attribute, int defaultValue) { + String value = parser.getAttributeValue(null, attribute); + if (TextUtils.isEmpty(value)) { + String msg = "Missing element under " + TAG +": " + attribute + " at " + + parser.getPositionDescription(); + Log.w(TAG, msg); + return defaultValue; + } else { + return Integer.parseInt(value); + } + } + + public void readFromXml(XmlPullParser parser) throws XmlPullParserException, + IOException { + mPackageName = getStringFromXml(parser, ATTR_PACKAGE_NAME, null); + if (mPackageName == null) { + Log.e(TAG, "Package name cannot be null!"); + } + int status = getIntFromXml(parser, ATTR_STATUS, -1); + if (status == -1) { + Log.e(TAG, "Unknown status value: " + status); + } + mMainStatus = status; + + mDomains = new ArrayList<>(); + int outerDepth = parser.getDepth(); + int type; + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG + || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG + || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals(TAG_DOMAIN)) { + String name = getStringFromXml(parser, ATTR_DOMAIN_NAME, null); + if (!TextUtils.isEmpty(name)) { + mDomains.add(name); + } + } else { + Log.w(TAG, "Unknown tag parsing IntentFilter: " + tagName); + } + XmlUtils.skipCurrentTag(parser); + } + } + + public void writeToXml(XmlSerializer serializer) throws IOException { + serializer.attribute(null, ATTR_PACKAGE_NAME, mPackageName); + serializer.attribute(null, ATTR_STATUS, String.valueOf(mMainStatus)); + for (String str : mDomains) { + serializer.startTag(null, TAG_DOMAIN); + serializer.attribute(null, ATTR_DOMAIN_NAME, str); + serializer.endTag(null, TAG_DOMAIN); + } + } + + public String getStatusString() { + return getStatusStringFromValue(mMainStatus); + } + + public static String getStatusStringFromValue(int val) { + switch (val) { + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK : return "ask"; + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS : return "always"; + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER : return "never"; + default: + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED : return "undefined"; + } + } + + @Override + public int describeContents() { + return 0; + } + + private void readFromParcel(Parcel source) { + mPackageName = source.readString(); + mMainStatus = source.readInt(); + mDomains = new ArrayList<>(); + source.readStringList(mDomains); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mPackageName); + dest.writeInt(mMainStatus); + dest.writeStringList(mDomains); + } + + public static final Creator<IntentFilterVerificationInfo> CREATOR = + new Creator<IntentFilterVerificationInfo>() { + public IntentFilterVerificationInfo createFromParcel(Parcel source) { + return new IntentFilterVerificationInfo(source); + } + public IntentFilterVerificationInfo[] newArray(int size) { + return new IntentFilterVerificationInfo[size]; + } + }; + +} diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 80efd0b..b7ee82d 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -423,7 +423,7 @@ public class PackageInstaller { */ public void uninstall(@NonNull String packageName, @NonNull IntentSender statusReceiver) { try { - mInstaller.uninstall(packageName, 0, statusReceiver, mUserId); + mInstaller.uninstall(packageName, mInstallerPackageName, 0, statusReceiver, mUserId); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } @@ -887,6 +887,8 @@ public class PackageInstaller { public Uri referrerUri; /** {@hide} */ public String abiOverride; + /** {@hide} */ + public String volumeUuid; /** * Construct parameters for a new package install session. @@ -911,6 +913,7 @@ public class PackageInstaller { originatingUri = source.readParcelable(null); referrerUri = source.readParcelable(null); abiOverride = source.readString(); + volumeUuid = source.readString(); } /** @@ -1008,6 +1011,7 @@ public class PackageInstaller { pw.printPair("originatingUri", originatingUri); pw.printPair("referrerUri", referrerUri); pw.printPair("abiOverride", abiOverride); + pw.printPair("volumeUuid", volumeUuid); pw.println(); } @@ -1028,6 +1032,7 @@ public class PackageInstaller { dest.writeParcelable(originatingUri, flags); dest.writeParcelable(referrerUri, flags); dest.writeString(abiOverride); + dest.writeString(volumeUuid); } public static final Parcelable.Creator<SessionParams> diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 59a16da..303b709 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -43,7 +43,9 @@ import android.os.Bundle; import android.os.Environment; import android.os.RemoteException; import android.os.UserHandle; +import android.os.storage.VolumeInfo; import android.util.AndroidException; + import com.android.internal.util.ArrayUtils; import java.io.File; @@ -339,15 +341,17 @@ public abstract class PackageManager { public static final int INSTALL_ALLOW_TEST = 0x00000004; /** - * Flag parameter for {@link #installPackage} to indicate that this - * package has to be installed on the sdcard. + * Flag parameter for {@link #installPackage} to indicate that this package + * must be installed to an ASEC on a {@link VolumeInfo#TYPE_PUBLIC}. + * * @hide */ public static final int INSTALL_EXTERNAL = 0x00000008; /** * Flag parameter for {@link #installPackage} to indicate that this package - * has to be installed on the sdcard. + * must be installed to internal storage. + * * @hide */ public static final int INSTALL_INTERNAL = 0x00000010; @@ -969,6 +973,60 @@ public abstract class PackageManager { public static final int VERIFICATION_REJECT = -1; /** + * Used as the {@code verificationCode} argument for + * {@link PackageManager#verifyIntentFilter} to indicate that the calling + * IntentFilter Verifier confirms that the IntentFilter is verified. + * + * @hide + */ + public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; + + /** + * Used as the {@code verificationCode} argument for + * {@link PackageManager#verifyIntentFilter} to indicate that the calling + * IntentFilter Verifier confirms that the IntentFilter is NOT verified. + * + * @hide + */ + public static final int INTENT_FILTER_VERIFICATION_FAILURE = -1; + + /** + * Internal status code to indicate that an IntentFilter verification result is not specified. + * + * @hide + */ + public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED = 0; + + /** + * Used as the {@code status} argument for {@link PackageManager#updateIntentVerificationStatus} + * to indicate that the User will always be prompted the Intent Disambiguation Dialog if there + * are two or more Intent resolved for the IntentFilter's domain(s). + * + * @hide + */ + public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK = 1; + + /** + * Used as the {@code status} argument for {@link PackageManager#updateIntentVerificationStatus} + * to indicate that the User will never be prompted the Intent Disambiguation Dialog if there + * are two or more resolution of the Intent. The default App for the domain(s) specified in the + * IntentFilter will also ALWAYS be used. + * + * @hide + */ + public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS = 2; + + /** + * Used as the {@code status} argument for {@link PackageManager#updateIntentVerificationStatus} + * to indicate that the User may be prompted the Intent Disambiguation Dialog if there + * are two or more Intent resolved. The default App for the domain(s) specified in the + * IntentFilter will also NEVER be presented to the User. + * + * @hide + */ + public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER = 3; + + /** * Can be used as the {@code millisecondsToDelay} argument for * {@link PackageManager#extendVerificationTimeout}. This is the * maximum time {@code PackageManager} waits for the verification @@ -1600,6 +1658,12 @@ public abstract class PackageManager { @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_GAMEPAD = "android.hardware.gamepad"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * The device has a full implementation of the android.media.midi.* APIs. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_MIDI = "android.software.midi"; /** * Action to external storage service to clean out removed apps. @@ -1674,8 +1738,52 @@ public abstract class PackageManager { = "android.content.pm.extra.VERIFICATION_VERSION_CODE"; /** - * The action used to request that the user approve a grant permissions - * request from the application. + * Extra field name for the ID of a intent filter pending verification. Passed to + * an intent filter verifier and is used to call back to + * {@link PackageManager#verifyIntentFilter(int, int)} + * + * @hide + */ + public static final String EXTRA_INTENT_FILTER_VERIFICATION_ID + = "android.content.pm.extra.INTENT_FILTER_VERIFICATION_ID"; + + /** + * Extra field name for the scheme used for an intent filter pending verification. Passed to + * an intent filter verifier and is used to construct the URI to verify against. + * + * Usually this is "https" + * + * @hide + */ + public static final String EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME + = "android.content.pm.extra.INTENT_FILTER_VERIFICATION_URI_SCHEME"; + + /** + * Extra field name for the host names to be used for an intent filter pending verification. + * Passed to an intent filter verifier and is used to construct the URI to verify the + * intent filter. + * + * This is a space delimited list of hosts. + * + * @hide + */ + public static final String EXTRA_INTENT_FILTER_VERIFICATION_HOSTS + = "android.content.pm.extra.INTENT_FILTER_VERIFICATION_HOSTS"; + + /** + * Extra field name for the package name to be used for an intent filter pending verification. + * Passed to an intent filter verifier and is used to check the verification responses coming + * from the hosts. Each host response will need to include the package name of APK containing + * the intent filter. + * + * @hide + */ + public static final String EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME + = "android.content.pm.extra.INTENT_FILTER_VERIFICATION_PACKAGE_NAME"; + + /** + * The action used to request that the user approve a permission request + * from the application. * * @hide */ @@ -3455,6 +3563,101 @@ public abstract class PackageManager { int verificationCodeAtTimeout, long millisecondsToDelay); /** + * Allows a package listening to the + * {@link Intent#ACTION_INTENT_FILTER_NEEDS_VERIFICATION intent filter verification + * broadcast} to respond to the package manager. The response must include + * the {@code verificationCode} which is one of + * {@link PackageManager#INTENT_FILTER_VERIFICATION_SUCCESS} or + * {@link PackageManager#INTENT_FILTER_VERIFICATION_FAILURE}. + * + * @param verificationId pending package identifier as passed via the + * {@link PackageManager#EXTRA_VERIFICATION_ID} Intent extra. + * @param verificationCode either {@link PackageManager#INTENT_FILTER_VERIFICATION_SUCCESS} + * or {@link PackageManager#INTENT_FILTER_VERIFICATION_FAILURE}. + * @param outFailedDomains a list of failed domains if the verificationCode is + * {@link PackageManager#INTENT_FILTER_VERIFICATION_FAILURE}, otherwise null; + * @throws SecurityException if the caller does not have the + * INTENT_FILTER_VERIFICATION_AGENT permission. + * + * @hide + */ + public abstract void verifyIntentFilter(int verificationId, int verificationCode, + List<String> outFailedDomains); + + /** + * Get the status of a Domain Verification Result for an IntentFilter. This is + * related to the {@link android.content.IntentFilter#setAutoVerify(boolean)} and + * {@link android.content.IntentFilter#getAutoVerify()} + * + * This is used by the ResolverActivity to change the status depending on what the User select + * in the Disambiguation Dialog and also used by the Settings App for changing the default App + * for a domain. + * + * @param packageName The package name of the Activity associated with the IntentFilter. + * @param userId The user id. + * + * @return The status to set to. This can be + * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK} or + * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS} or + * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER} or + * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED} + * + * @hide + */ + public abstract int getIntentVerificationStatus(String packageName, int userId); + + /** + * Allow to change the status of a Intent Verification status for all IntentFilter of an App. + * This is related to the {@link android.content.IntentFilter#setAutoVerify(boolean)} and + * {@link android.content.IntentFilter#getAutoVerify()} + * + * This is used by the ResolverActivity to change the status depending on what the User select + * in the Disambiguation Dialog and also used by the Settings App for changing the default App + * for a domain. + * + * @param packageName The package name of the Activity associated with the IntentFilter. + * @param status The status to set to. This can be + * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK} or + * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS} or + * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER} + * @param userId The user id. + * + * @return true if the status has been set. False otherwise. + * + * @hide + */ + public abstract boolean updateIntentVerificationStatus(String packageName, int status, + int userId); + + /** + * Get the list of IntentFilterVerificationInfo for a specific package and User. + * + * @param packageName the package name. When this parameter is set to a non null value, + * the results will be filtered by the package name provided. + * Otherwise, there will be no filtering and it will return a list + * corresponding for all packages + * + * @return a list of IntentFilterVerificationInfo for a specific package. + * + * @hide + */ + public abstract List<IntentFilterVerificationInfo> getIntentFilterVerifications( + String packageName); + + /** + * Get the list of IntentFilter for a specific package. + * + * @param packageName the package name. This parameter is set to a non null value, + * the list will contain all the IntentFilter for that package. + * Otherwise, the list will be empty. + * + * @return a list of IntentFilter for a specific package. + * + * @hide + */ + public abstract List<IntentFilter> getAllIntentFilters(String packageName); + + /** * Change the installer associated with a given package. There are limitations * on how the installer package can be changed; in particular: * <ul> @@ -3916,8 +4119,12 @@ public abstract class PackageManager { * * @hide */ - public abstract void movePackage( - String packageName, IPackageMoveObserver observer, int flags); + @Deprecated + public abstract void movePackage(String packageName, IPackageMoveObserver observer, int flags); + + /** {@hide} */ + public abstract void movePackageAndData(String packageName, String volumeUuid, + IPackageMoveObserver observer); /** * Returns the device identity that verifiers can use to associate their scheme to a particular diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 212cf6d..c1e6a4d 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -604,7 +604,7 @@ public class PackageParser { public final static int PARSE_MUST_BE_APK = 1<<2; public final static int PARSE_IGNORE_PROCESSES = 1<<3; public final static int PARSE_FORWARD_LOCK = 1<<4; - public final static int PARSE_ON_SDCARD = 1<<5; + public final static int PARSE_EXTERNAL_STORAGE = 1<<5; public final static int PARSE_IS_SYSTEM_DIR = 1<<6; public final static int PARSE_IS_PRIVILEGED = 1<<7; public final static int PARSE_COLLECT_CERTIFICATES = 1<<8; @@ -1408,7 +1408,7 @@ public class PackageParser { } /* Set the global "on SD card" flag */ - if ((flags & PARSE_ON_SDCARD) != 0) { + if ((flags & PARSE_EXTERNAL_STORAGE) != 0) { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_EXTERNAL_STORAGE; } @@ -2750,15 +2750,51 @@ public class PackageParser { } } - addSharedLibrariesForBackwardCompatibility(owner); + modifySharedLibrariesForBackwardCompatibility(owner); + + if (hasDomainURLs(owner)) { + owner.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS; + } else { + owner.applicationInfo.privateFlags &= ~ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS; + } return true; } - private static void addSharedLibrariesForBackwardCompatibility(Package owner) { - if (owner.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1) { - owner.usesLibraries = ArrayUtils.add(owner.usesLibraries, "org.apache.http.legacy"); + private static void modifySharedLibrariesForBackwardCompatibility(Package owner) { + // "org.apache.http.legacy" is now a part of the boot classpath so it doesn't need + // to be an explicit dependency. + // + // A future change will remove this library from the boot classpath, at which point + // all apps that target SDK 21 and earlier will have it automatically added to their + // dependency lists. + owner.usesLibraries = ArrayUtils.remove(owner.usesLibraries, "org.apache.http.legacy"); + owner.usesOptionalLibraries = ArrayUtils.remove(owner.usesOptionalLibraries, + "org.apache.http.legacy"); + } + + /** + * Check if one of the IntentFilter as an action VIEW and a HTTP/HTTPS data URI + */ + private static boolean hasDomainURLs(Package pkg) { + if (pkg == null || pkg.activities == null) return false; + final ArrayList<Activity> activities = pkg.activities; + final int countActivities = activities.size(); + for (int n=0; n<countActivities; n++) { + Activity activity = activities.get(n); + ArrayList<ActivityIntentInfo> filters = activity.intents; + if (filters == null) continue; + final int countFilters = filters.size(); + for (int m=0; m<countFilters; m++) { + ActivityIntentInfo aii = filters.get(m); + if (!aii.hasAction(Intent.ACTION_VIEW)) continue; + if (aii.hasDataScheme(IntentFilter.SCHEME_HTTP) || + aii.hasDataScheme(IntentFilter.SCHEME_HTTPS)) { + return true; + } + } } + return false; } /** @@ -3149,7 +3185,7 @@ public class PackageParser { if (parser.getName().equals("intent-filter")) { ActivityIntentInfo intent = new ActivityIntentInfo(a); - if (!parseIntent(res, parser, attrs, true, intent, outError)) { + if (!parseIntent(res, parser, attrs, true, true, intent, outError)) { return null; } if (intent.countActions() == 0) { @@ -3161,7 +3197,7 @@ public class PackageParser { } } else if (!receiver && parser.getName().equals("preferred")) { ActivityIntentInfo intent = new ActivityIntentInfo(a); - if (!parseIntent(res, parser, attrs, false, intent, outError)) { + if (!parseIntent(res, parser, attrs, false, false, intent, outError)) { return null; } if (intent.countActions() == 0) { @@ -3341,7 +3377,7 @@ public class PackageParser { if (parser.getName().equals("intent-filter")) { ActivityIntentInfo intent = new ActivityIntentInfo(a); - if (!parseIntent(res, parser, attrs, true, intent, outError)) { + if (!parseIntent(res, parser, attrs, true, true, intent, outError)) { return null; } if (intent.countActions() == 0) { @@ -3521,7 +3557,7 @@ public class PackageParser { if (parser.getName().equals("intent-filter")) { ProviderIntentInfo intent = new ProviderIntentInfo(outInfo); - if (!parseIntent(res, parser, attrs, true, intent, outError)) { + if (!parseIntent(res, parser, attrs, true, false, intent, outError)) { return false; } outInfo.intents.add(intent); @@ -3780,7 +3816,7 @@ public class PackageParser { if (parser.getName().equals("intent-filter")) { ServiceIntentInfo intent = new ServiceIntentInfo(s); - if (!parseIntent(res, parser, attrs, true, intent, outError)) { + if (!parseIntent(res, parser, attrs, true, false, intent, outError)) { return null; } @@ -3981,7 +4017,7 @@ public class PackageParser { = "http://schemas.android.com/apk/res/android"; private boolean parseIntent(Resources res, XmlPullParser parser, AttributeSet attrs, - boolean allowGlobs, IntentInfo outInfo, String[] outError) + boolean allowGlobs, boolean allowAutoVerify, IntentInfo outInfo, String[] outError) throws XmlPullParserException, IOException { TypedArray sa = res.obtainAttributes(attrs, @@ -4006,6 +4042,12 @@ public class PackageParser { outInfo.banner = sa.getResourceId( com.android.internal.R.styleable.AndroidManifestIntentFilter_banner, 0); + if (allowAutoVerify) { + outInfo.setAutoVerify(sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestIntentFilter_autoVerify, + false)); + } + sa.recycle(); int outerDepth = parser.getDepth(); @@ -4399,6 +4441,20 @@ public class PackageParser { return applicationInfo.isForwardLocked(); } + /** + * @hide + */ + public boolean isSystemApp() { + return applicationInfo.isSystemApp(); + } + + /** + * @hide + */ + public boolean isUpdatedSystemApp() { + return applicationInfo.isUpdatedSystemApp(); + } + public String toString() { return "Package{" + Integer.toHexString(System.identityHashCode(this)) diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java index a9c7be3..92b8055 100644 --- a/core/java/android/content/pm/PackageUserState.java +++ b/core/java/android/content/pm/PackageUserState.java @@ -37,10 +37,14 @@ public class PackageUserState { public ArraySet<String> disabledComponents; public ArraySet<String> enabledComponents; + public int domainVerificationStatus; + public PackageUserState() { installed = true; hidden = false; enabled = COMPONENT_ENABLED_STATE_DEFAULT; + domainVerificationStatus = + PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; } public PackageUserState(PackageUserState o) { @@ -51,9 +55,10 @@ public class PackageUserState { hidden = o.hidden; lastDisableAppCaller = o.lastDisableAppCaller; disabledComponents = o.disabledComponents != null - ? new ArraySet<String>(o.disabledComponents) : null; + ? new ArraySet<>(o.disabledComponents) : null; enabledComponents = o.enabledComponents != null - ? new ArraySet<String>(o.enabledComponents) : null; + ? new ArraySet<>(o.enabledComponents) : null; blockUninstall = o.blockUninstall; + domainVerificationStatus = o.domainVerificationStatus; } } diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java index fe3aec9..7b141f0 100644 --- a/core/java/android/content/pm/ResolveInfo.java +++ b/core/java/android/content/pm/ResolveInfo.java @@ -143,6 +143,11 @@ public class ResolveInfo implements Parcelable { */ public boolean system; + /** + * @hide Does the associated IntentFilter needs verification ? + */ + public boolean filterNeedsVerification; + private ComponentInfo getComponentInfo() { if (activityInfo != null) return activityInfo; if (serviceInfo != null) return serviceInfo; @@ -283,6 +288,7 @@ public class ResolveInfo implements Parcelable { resolvePackageName = orig.resolvePackageName; system = orig.system; targetUserId = orig.targetUserId; + filterNeedsVerification = orig.filterNeedsVerification; } public String toString() { @@ -344,6 +350,7 @@ public class ResolveInfo implements Parcelable { dest.writeInt(targetUserId); dest.writeInt(system ? 1 : 0); dest.writeInt(noResourceId ? 1 : 0); + dest.writeInt(filterNeedsVerification ? 1 : 0); } public static final Creator<ResolveInfo> CREATOR @@ -389,6 +396,7 @@ public class ResolveInfo implements Parcelable { targetUserId = source.readInt(); system = source.readInt() != 0; noResourceId = source.readInt() != 0; + filterNeedsVerification = source.readInt() != 0; } public static class DisplayNameComparator diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java index 7d8dff3..fdafb04 100644 --- a/core/java/android/content/res/ColorStateList.java +++ b/core/java/android/content/res/ColorStateList.java @@ -71,10 +71,15 @@ import java.util.Arrays; */ public class ColorStateList implements Parcelable { private static final String TAG = "ColorStateList"; + private static final int DEFAULT_COLOR = Color.RED; private static final int[][] EMPTY = new int[][] { new int[0] }; - private static final SparseArray<WeakReference<ColorStateList>> sCache = - new SparseArray<WeakReference<ColorStateList>>(); + + /** Thread-safe cache of single-color ColorStateLists. */ + private static final SparseArray<WeakReference<ColorStateList>> sCache = new SparseArray<>(); + + /** Lazily-created factory for this color state list. */ + private ColorStateListFactory mFactory; private int[][] mThemeAttrs; private int mChangingConfigurations; @@ -125,7 +130,7 @@ public class ColorStateList implements Parcelable { } final ColorStateList csl = new ColorStateList(EMPTY, new int[] { color }); - sCache.put(color, new WeakReference<ColorStateList>(csl)); + sCache.put(color, new WeakReference<>(csl)); return csl; } } @@ -141,11 +146,13 @@ public class ColorStateList implements Parcelable { */ private ColorStateList(ColorStateList orig) { if (orig != null) { + mChangingConfigurations = orig.mChangingConfigurations; mStateSpecs = orig.mStateSpecs; mDefaultColor = orig.mDefaultColor; mIsOpaque = orig.mIsOpaque; - // Deep copy, this may change due to theming. + // Deep copy, these may change due to applyTheme(). + mThemeAttrs = orig.mThemeAttrs.clone(); mColors = orig.mColors.clone(); } } @@ -329,6 +336,7 @@ public class ColorStateList implements Parcelable { * attributes. * * @return whether a theme can be applied to this color state list + * @hide only for resource preloading */ public boolean canApplyTheme() { return mThemeAttrs != null; @@ -336,10 +344,15 @@ public class ColorStateList implements Parcelable { /** * Applies a theme to this color state list. + * <p> + * <strong>Note:</strong> Applying a theme may affect the changing + * configuration parameters of this color state list. After calling this + * method, any dependent configurations must be updated by obtaining the + * new configuration mask from {@link #getChangingConfigurations()}. * * @param t the theme to apply */ - public void applyTheme(Theme t) { + private void applyTheme(Theme t) { if (mThemeAttrs == null) { return; } @@ -376,6 +389,38 @@ public class ColorStateList implements Parcelable { onColorsChanged(); } + /** + * Returns an appropriately themed color state list. + * + * @param t the theme to apply + * @return a copy of the color state list with the theme applied, or the + * color state list itself if there were no unresolved theme + * attributes + * @hide only for resource preloading + */ + public ColorStateList obtainForTheme(Theme t) { + if (t == null || !canApplyTheme()) { + return this; + } + + final ColorStateList clone = new ColorStateList(this); + clone.applyTheme(t); + return clone; + } + + /** + * Returns a mask of the configuration parameters for which this color + * state list may change, requiring that it be re-created. + * + * @return a mask of the changing configuration parameters, as defined by + * {@link android.content.pm.ActivityInfo} + * + * @see android.content.pm.ActivityInfo + */ + public int getChangingConfigurations() { + return mChangingConfigurations; + } + private int modulateColorAlpha(int baseColor, float alphaMod) { if (alphaMod == 1.0f) { return baseColor; @@ -383,8 +428,7 @@ public class ColorStateList implements Parcelable { final int baseAlpha = Color.alpha(baseColor); final int alpha = MathUtils.constrain((int) (baseAlpha * alphaMod + 0.5f), 0, 255); - final int color = (baseColor & 0xFFFFFF) | (alpha << 24); - return color; + return (baseColor & 0xFFFFFF) | (alpha << 24); } /** @@ -534,14 +578,18 @@ public class ColorStateList implements Parcelable { } /** - * @return A factory that can create new instances of this ColorStateList. + * @return a factory that can create new instances of this ColorStateList + * @hide only for resource preloading */ - ColorStateListFactory getFactory() { - return new ColorStateListFactory(this); + public ConstantState<ColorStateList> getConstantState() { + if (mFactory != null) { + mFactory = new ColorStateListFactory(this); + } + return mFactory; } - static class ColorStateListFactory extends ConstantState<ColorStateList> { - final ColorStateList mSrc; + private static class ColorStateListFactory extends ConstantState<ColorStateList> { + private final ColorStateList mSrc; public ColorStateListFactory(ColorStateList src) { mSrc = src; @@ -559,13 +607,7 @@ public class ColorStateList implements Parcelable { @Override public ColorStateList newInstance(Resources res, Theme theme) { - if (theme == null || !mSrc.canApplyTheme()) { - return mSrc; - } - - final ColorStateList clone = new ColorStateList(mSrc); - clone.applyTheme(theme); - return clone; + return mSrc.obtainForTheme(theme); } } diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 14af584..bc6d4ce 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -1284,14 +1284,12 @@ public final class Configuration implements Parcelable, Comparable<Configuration /** * Set the locale. This is the preferred way for setting up the locale (instead of using the - * direct accessor). This will also set the userLocale and layout direction according to - * the locale. + * direct accessor). This will also set the layout direction according to the locale. * * @param loc The locale. Can be null. */ public void setLocale(Locale loc) { locale = loc; - userSetLocale = true; setLayoutDirection(locale); } @@ -1314,7 +1312,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration * {@link View#LAYOUT_DIRECTION_LTR}. If not null will set it to the layout direction * corresponding to the Locale. * - * @see {@link View#LAYOUT_DIRECTION_LTR} and {@link View#LAYOUT_DIRECTION_RTL} + * @see View#LAYOUT_DIRECTION_LTR + * @see View#LAYOUT_DIRECTION_RTL */ public void setLayoutDirection(Locale locale) { // There is a "1" difference between the configuration values for diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 95ad57e..334d180 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -16,7 +16,10 @@ package android.content.res; +import android.annotation.AttrRes; import android.annotation.ColorInt; +import android.annotation.StyleRes; +import android.annotation.StyleableRes; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; @@ -41,7 +44,6 @@ import android.annotation.RawRes; import android.annotation.StringRes; import android.annotation.XmlRes; import android.content.pm.ActivityInfo; -import android.content.res.ColorStateList.ColorStateListFactory; import android.graphics.Movie; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; @@ -111,12 +113,15 @@ public class Resources { // single-threaded, and after that these are immutable. private static final LongSparseArray<ConstantState>[] sPreloadedDrawables; private static final LongSparseArray<ConstantState> sPreloadedColorDrawables - = new LongSparseArray<ConstantState>(); - private static final LongSparseArray<ColorStateListFactory> sPreloadedColorStateLists - = new LongSparseArray<ColorStateListFactory>(); + = new LongSparseArray<>(); + private static final LongSparseArray<android.content.res.ConstantState<ColorStateList>> + sPreloadedColorStateLists = new LongSparseArray<>(); + + private static final String CACHE_NOT_THEMED = ""; + private static final String CACHE_NULL_THEME = "null_theme"; // Pool of TypedArrays targeted to this Resources object. - final SynchronizedPool<TypedArray> mTypedArrayPool = new SynchronizedPool<TypedArray>(5); + final SynchronizedPool<TypedArray> mTypedArrayPool = new SynchronizedPool<>(5); // Used by BridgeResources in layoutlib static Resources mSystem = null; @@ -128,21 +133,19 @@ public class Resources { private final Object mAccessLock = new Object(); private final Configuration mTmpConfig = new Configuration(); private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> mDrawableCache = - new ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>>(); + new ArrayMap<>(); private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> mColorDrawableCache = - new ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>>(); + new ArrayMap<>(); private final ConfigurationBoundResourceCache<ColorStateList> mColorStateListCache = - new ConfigurationBoundResourceCache<ColorStateList>(this); + new ConfigurationBoundResourceCache<>(this); private final ConfigurationBoundResourceCache<Animator> mAnimatorCache = - new ConfigurationBoundResourceCache<Animator>(this); + new ConfigurationBoundResourceCache<>(this); private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache = - new ConfigurationBoundResourceCache<StateListAnimator>(this); + new ConfigurationBoundResourceCache<>(this); private TypedValue mTmpValue = new TypedValue(); private boolean mPreloading; - private TypedArray mCachedStyledAttributes = null; - private int mLastCachedXmlBlockIndex = -1; private final int[] mCachedXmlBlockIds = { 0, 0, 0, 0 }; private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[4]; @@ -157,8 +160,8 @@ public class Resources { static { sPreloadedDrawables = new LongSparseArray[2]; - sPreloadedDrawables[0] = new LongSparseArray<ConstantState>(); - sPreloadedDrawables[1] = new LongSparseArray<ConstantState>(); + sPreloadedDrawables[0] = new LongSparseArray<>(); + sPreloadedDrawables[1] = new LongSparseArray<>(); } /** @@ -1475,7 +1478,7 @@ public class Resources { * @see #obtainStyledAttributes(int, int[]) * @see #obtainStyledAttributes(AttributeSet, int[], int, int) */ - public TypedArray obtainStyledAttributes(int[] attrs) { + public TypedArray obtainStyledAttributes(@StyleableRes int[] attrs) { final int len = attrs.length; final TypedArray array = TypedArray.obtain(Resources.this, len); array.mTheme = this; @@ -1503,7 +1506,8 @@ public class Resources { * @see #obtainStyledAttributes(int[]) * @see #obtainStyledAttributes(AttributeSet, int[], int, int) */ - public TypedArray obtainStyledAttributes(int resid, int[] attrs) throws NotFoundException { + public TypedArray obtainStyledAttributes(@StyleRes int resid, @StyleableRes int[] attrs) + throws NotFoundException { final int len = attrs.length; final TypedArray array = TypedArray.obtain(Resources.this, len); array.mTheme = this; @@ -1586,7 +1590,7 @@ public class Resources { * @see #obtainStyledAttributes(int, int[]) */ public TypedArray obtainStyledAttributes(AttributeSet set, - int[] attrs, int defStyleAttr, int defStyleRes) { + @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { final int len = attrs.length; final TypedArray array = TypedArray.obtain(Resources.this, len); @@ -1876,7 +1880,7 @@ public class Resources { // the framework. mCompatibilityInfo.applyToDisplayMetrics(mMetrics); - int configChanges = calcConfigChanges(config); + final int configChanges = calcConfigChanges(config); if (mConfiguration.locale == null) { mConfiguration.locale = Locale.getDefault(); mConfiguration.setLayoutDirection(mConfiguration.locale); @@ -1891,7 +1895,8 @@ public class Resources { if (mConfiguration.locale != null) { locale = adjustLanguageTag(mConfiguration.locale.toLanguageTag()); } - int width, height; + + final int width, height; if (mMetrics.widthPixels >= mMetrics.heightPixels) { width = mMetrics.widthPixels; height = mMetrics.heightPixels; @@ -1901,12 +1906,15 @@ public class Resources { //noinspection SuspiciousNameCombination height = mMetrics.widthPixels; } - int keyboardHidden = mConfiguration.keyboardHidden; - if (keyboardHidden == Configuration.KEYBOARDHIDDEN_NO - && mConfiguration.hardKeyboardHidden - == Configuration.HARDKEYBOARDHIDDEN_YES) { + + final int keyboardHidden; + if (mConfiguration.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO + && mConfiguration.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) { keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT; + } else { + keyboardHidden = mConfiguration.keyboardHidden; } + mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc, locale, mConfiguration.orientation, mConfiguration.touchscreen, @@ -2436,8 +2444,8 @@ public class Resources { } } - // Next, check preloaded drawables. These are unthemed but may have - // themeable attributes. + // Next, check preloaded drawables. These may contain unresolved theme + // attributes. final ConstantState cs; if (isColorDrawable) { cs = sPreloadedColorDrawables.get(key); @@ -2445,42 +2453,49 @@ public class Resources { cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key); } - final Drawable dr; + Drawable dr; if (cs != null) { - final Drawable clonedDr = cs.newDrawable(this); - if (theme != null) { - dr = clonedDr.mutate(); - dr.applyTheme(theme); - dr.clearMutated(); - } else { - dr = clonedDr; - } + dr = cs.newDrawable(this); } else if (isColorDrawable) { dr = new ColorDrawable(value.data); } else { - dr = loadDrawableForCookie(value, id, theme); + dr = loadDrawableForCookie(value, id, null); + } + + // Determine if the drawable has unresolved theme attributes. If it + // does, we'll need to apply a theme and store it in a theme-specific + // cache. + final String cacheKey; + if (!dr.canApplyTheme()) { + cacheKey = CACHE_NOT_THEMED; + } else if (theme == null) { + cacheKey = CACHE_NULL_THEME; + } else { + cacheKey = theme.getKey(); + dr = dr.mutate(); + dr.applyTheme(theme); + dr.clearMutated(); } // If we were able to obtain a drawable, store it in the appropriate - // cache (either preload or themed). + // cache: preload, not themed, null theme, or theme-specific. if (dr != null) { dr.setChangingConfigurations(value.changingConfigurations); - cacheDrawable(value, theme, isColorDrawable, caches, key, dr); + cacheDrawable(value, isColorDrawable, caches, cacheKey, key, dr); } return dr; } - private void cacheDrawable(TypedValue value, Theme theme, boolean isColorDrawable, + private void cacheDrawable(TypedValue value, boolean isColorDrawable, ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches, - long key, Drawable dr) { + String cacheKey, long key, Drawable dr) { final ConstantState cs = dr.getConstantState(); if (cs == null) { return; } if (mPreloading) { - // Preloaded drawables never have a theme, but may be themeable. final int changingConfigs = cs.getChangingConfigurations(); if (isColorDrawable) { if (verifyPreloadConfig(changingConfigs, 0, value.resourceId, "drawable")) { @@ -2502,16 +2517,15 @@ public class Resources { } } else { synchronized (mAccessLock) { - final String themeKey = theme == null ? "" : theme.mKey; - LongSparseArray<WeakReference<ConstantState>> themedCache = caches.get(themeKey); + LongSparseArray<WeakReference<ConstantState>> themedCache = caches.get(cacheKey); if (themedCache == null) { // Clean out the caches before we add more. This shouldn't // happen very often. pruneCaches(caches); - themedCache = new LongSparseArray<WeakReference<ConstantState>>(1); - caches.put(themeKey, themedCache); + themedCache = new LongSparseArray<>(1); + caches.put(cacheKey, themedCache); } - themedCache.put(key, new WeakReference<ConstantState>(cs)); + themedCache.put(key, new WeakReference<>(cs)); } } } @@ -2607,46 +2621,47 @@ public class Resources { ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches, long key, Theme theme) { synchronized (mAccessLock) { - final String themeKey = theme != null ? theme.mKey : ""; - final LongSparseArray<WeakReference<ConstantState>> themedCache = caches.get(themeKey); - if (themedCache != null) { - final Drawable themedDrawable = getCachedDrawableLocked(themedCache, key); - if (themedDrawable != null) { - return themedDrawable; - } + // First search theme-agnostic cache. + final Drawable unthemedDrawable = getCachedDrawableLocked( + caches, key, CACHE_NOT_THEMED); + if (unthemedDrawable != null) { + return unthemedDrawable; } - // No cached drawable, we'll need to create a new one. - return null; + // Next search theme-specific cache. + final String themeKey = theme != null ? theme.getKey() : CACHE_NULL_THEME; + return getCachedDrawableLocked(caches, key, themeKey); } } + private Drawable getCachedDrawableLocked( + ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches, + long key, String themeKey) { + final LongSparseArray<WeakReference<ConstantState>> cache = caches.get(themeKey); + if (cache != null) { + final ConstantState entry = getConstantStateLocked(cache, key); + if (entry != null) { + return entry.newDrawable(this); + } + } + return null; + } + private ConstantState getConstantStateLocked( LongSparseArray<WeakReference<ConstantState>> drawableCache, long key) { final WeakReference<ConstantState> wr = drawableCache.get(key); - if (wr != null) { // we have the key + if (wr != null) { final ConstantState entry = wr.get(); if (entry != null) { - //Log.i(TAG, "Returning cached drawable @ #" + - // Integer.toHexString(((Integer)key).intValue()) - // + " in " + this + ": " + entry); return entry; - } else { // our entry has been purged + } else { + // Our entry has been purged. drawableCache.delete(key); } } return null; } - private Drawable getCachedDrawableLocked( - LongSparseArray<WeakReference<ConstantState>> drawableCache, long key) { - final ConstantState entry = getConstantStateLocked(drawableCache, key); - if (entry != null) { - return entry.newDrawable(this); - } - return null; - } - @Nullable ColorStateList loadColorStateList(TypedValue value, int id, Theme theme) throws NotFoundException { @@ -2665,7 +2680,8 @@ public class Resources { // Handle inline color definitions. if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { - final ColorStateListFactory factory = sPreloadedColorStateLists.get(key); + final android.content.res.ConstantState<ColorStateList> factory = + sPreloadedColorStateLists.get(key); if (factory != null) { return factory.newInstance(); } @@ -2675,7 +2691,7 @@ public class Resources { if (mPreloading) { if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId, "color")) { - sPreloadedColorStateLists.put(key, csl.getFactory()); + sPreloadedColorStateLists.put(key, csl.getConstantState()); } } @@ -2689,7 +2705,8 @@ public class Resources { return csl; } - final ColorStateListFactory factory = sPreloadedColorStateLists.get(key); + final android.content.res.ConstantState<ColorStateList> factory = + sPreloadedColorStateLists.get(key); if (factory != null) { csl = factory.newInstance(this, theme); } @@ -2702,10 +2719,10 @@ public class Resources { if (mPreloading) { if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId, "color")) { - sPreloadedColorStateLists.put(key, csl.getFactory()); + sPreloadedColorStateLists.put(key, csl.getConstantState()); } } else { - cache.put(key, theme, csl.getFactory()); + cache.put(key, theme, csl.getConstantState()); } } @@ -2830,15 +2847,6 @@ public class Resources { + Integer.toHexString(id)); } - /*package*/ void recycleCachedStyledAttributes(TypedArray attrs) { - synchronized (mAccessLock) { - final TypedArray cached = mCachedStyledAttributes; - if (cached == null || cached.mData.length < attrs.mData.length) { - mCachedStyledAttributes = attrs; - } - } - } - /** * Obtains styled attributes from the theme, if available, or unstyled * resources if the theme is null. diff --git a/core/java/android/database/DatabaseErrorHandler.java b/core/java/android/database/DatabaseErrorHandler.java index f0c5452..55ad921 100644 --- a/core/java/android/database/DatabaseErrorHandler.java +++ b/core/java/android/database/DatabaseErrorHandler.java @@ -19,13 +19,12 @@ package android.database; import android.database.sqlite.SQLiteDatabase; /** - * An interface to let the apps define the actions to take when the following errors are detected - * database corruption + * An interface to let apps define an action to take when database corruption is detected. */ public interface DatabaseErrorHandler { /** - * defines the method to be invoked when database corruption is detected. + * The method invoked when database corruption is detected. * @param dbObj the {@link SQLiteDatabase} object representing the database on which corruption * is detected. */ diff --git a/core/java/android/database/DefaultDatabaseErrorHandler.java b/core/java/android/database/DefaultDatabaseErrorHandler.java index b234e34..7fa2b40 100755 --- a/core/java/android/database/DefaultDatabaseErrorHandler.java +++ b/core/java/android/database/DefaultDatabaseErrorHandler.java @@ -24,7 +24,7 @@ import android.util.Log; import android.util.Pair; /** - * Default class used to define the actions to take when the database corruption is reported + * Default class used to define the action to take when database corruption is reported * by sqlite. * <p> * An application can specify an implementation of {@link DatabaseErrorHandler} on the @@ -38,7 +38,7 @@ import android.util.Pair; * The specified {@link DatabaseErrorHandler} is used to handle database corruption errors, if they * occur. * <p> - * If null is specified for DatabaeErrorHandler param in the above calls, then this class is used + * If null is specified for the DatabaseErrorHandler param in the above calls, this class is used * as the default {@link DatabaseErrorHandler}. */ public final class DefaultDatabaseErrorHandler implements DatabaseErrorHandler { diff --git a/core/java/android/hardware/ICameraService.aidl b/core/java/android/hardware/ICameraService.aidl index d5dfaf6..9bc2f46 100644 --- a/core/java/android/hardware/ICameraService.aidl +++ b/core/java/android/hardware/ICameraService.aidl @@ -75,4 +75,11 @@ interface ICameraService out BinderHolder device); int setTorchMode(String CameraId, boolean enabled, IBinder clientBinder); + + /** + * Notify the camera service of a system event. Should only be called from system_server. + * + * Callers require the android.permission.CAMERA_SEND_SYSTEM_EVENTS permission. + */ + oneway void notifySystemEvent(int eventId, int arg0); } diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java index a6c3ea4..88fa339 100644 --- a/core/java/android/hardware/SystemSensorManager.java +++ b/core/java/android/hardware/SystemSensorManager.java @@ -54,11 +54,13 @@ public class SystemSensorManager extends SensorManager { // Looper associated with the context in which this instance was created. private final Looper mMainLooper; private final int mTargetSdkLevel; + private final String mPackageName; /** {@hide} */ public SystemSensorManager(Context context, Looper mainLooper) { mMainLooper = mainLooper; mTargetSdkLevel = context.getApplicationInfo().targetSdkVersion; + mPackageName = context.getPackageName(); synchronized(sSensorModuleLock) { if (!sSensorModuleInitialized) { sSensorModuleInitialized = true; @@ -117,14 +119,14 @@ public class SystemSensorManager extends SensorManager { if (queue == null) { Looper looper = (handler != null) ? handler.getLooper() : mMainLooper; queue = new SensorEventQueue(listener, looper, this); - if (!queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs, reservedFlags)) { + if (!queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs)) { queue.dispose(); return false; } mSensorListeners.put(listener, queue); return true; } else { - return queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs, reservedFlags); + return queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs); } } } @@ -165,14 +167,14 @@ public class SystemSensorManager extends SensorManager { TriggerEventQueue queue = mTriggerListeners.get(listener); if (queue == null) { queue = new TriggerEventQueue(listener, mMainLooper, this); - if (!queue.addSensor(sensor, 0, 0, 0)) { + if (!queue.addSensor(sensor, 0, 0)) { queue.dispose(); return false; } mTriggerListeners.put(listener, queue); return true; } else { - return queue.addSensor(sensor, 0, 0, 0); + return queue.addSensor(sensor, 0, 0); } } } @@ -223,9 +225,9 @@ public class SystemSensorManager extends SensorManager { */ private static abstract class BaseEventQueue { private native long nativeInitBaseEventQueue(BaseEventQueue eventQ, MessageQueue msgQ, - float[] scratch); + float[] scratch, String packageName); private static native int nativeEnableSensor(long eventQ, int handle, int rateUs, - int maxBatchReportLatencyUs, int reservedFlags); + int maxBatchReportLatencyUs); private static native int nativeDisableSensor(long eventQ, int handle); private static native void nativeDestroySensorEventQueue(long eventQ); private static native int nativeFlushSensor(long eventQ); @@ -238,7 +240,8 @@ public class SystemSensorManager extends SensorManager { protected final SystemSensorManager mManager; BaseEventQueue(Looper looper, SystemSensorManager manager) { - nSensorEventQueue = nativeInitBaseEventQueue(this, looper.getQueue(), mScratch); + nSensorEventQueue = nativeInitBaseEventQueue(this, looper.getQueue(), mScratch, + manager.mPackageName); mCloseGuard.open("dispose"); mManager = manager; } @@ -248,7 +251,7 @@ public class SystemSensorManager extends SensorManager { } public boolean addSensor( - Sensor sensor, int delayUs, int maxBatchReportLatencyUs, int reservedFlags) { + Sensor sensor, int delayUs, int maxBatchReportLatencyUs) { // Check if already present. int handle = sensor.getHandle(); if (mActiveSensors.get(handle)) return false; @@ -256,10 +259,10 @@ public class SystemSensorManager extends SensorManager { // Get ready to receive events before calling enable. mActiveSensors.put(handle, true); addSensorEvent(sensor); - if (enableSensor(sensor, delayUs, maxBatchReportLatencyUs, reservedFlags) != 0) { + if (enableSensor(sensor, delayUs, maxBatchReportLatencyUs) != 0) { // Try continuous mode if batching fails. if (maxBatchReportLatencyUs == 0 || - maxBatchReportLatencyUs > 0 && enableSensor(sensor, delayUs, 0, 0) != 0) { + maxBatchReportLatencyUs > 0 && enableSensor(sensor, delayUs, 0) != 0) { removeSensor(sensor, false); return false; } @@ -328,11 +331,11 @@ public class SystemSensorManager extends SensorManager { } private int enableSensor( - Sensor sensor, int rateUs, int maxBatchReportLatencyUs, int reservedFlags) { + Sensor sensor, int rateUs, int maxBatchReportLatencyUs) { if (nSensorEventQueue == 0) throw new NullPointerException(); if (sensor == null) throw new NullPointerException(); return nativeEnableSensor(nSensorEventQueue, sensor.getHandle(), rateUs, - maxBatchReportLatencyUs, reservedFlags); + maxBatchReportLatencyUs); } private int disableSensor(Sensor sensor) { diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index a0217c2..2f5e402 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -939,6 +939,129 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<Integer>("android.lens.facing", int.class); /** + * <p>The orientation of the camera relative to the sensor + * coordinate system.</p> + * <p>The four coefficients that describe the quarternion + * rotation from the Android sensor coordinate system to a + * camera-aligned coordinate system where the X-axis is + * aligned with the long side of the image sensor, the Y-axis + * is aligned with the short side of the image sensor, and + * the Z-axis is aligned with the optical axis of the sensor.</p> + * <p>To convert from the quarternion coefficients <code>(x,y,z,w)</code> + * to the axis of rotation <code>(a_x, a_y, a_z)</code> and rotation + * amount <code>theta</code>, the following formulas can be used:</p> + * <pre><code> theta = 2 * acos(w) + * a_x = x / sin(theta/2) + * a_y = y / sin(theta/2) + * a_z = z / sin(theta/2) + * </code></pre> + * <p>To create a 3x3 rotation matrix that applies the rotation + * defined by this quarternion, the following matrix can be + * used:</p> + * <pre><code>R = [ 1 - 2y^2 - 2z^2, 2xy - 2zw, 2xz + 2yw, + * 2xy + 2zw, 1 - 2x^2 - 2z^2, 2yz - 2xw, + * 2xz - 2yw, 2yz + 2xw, 1 - 2x^2 - 2y^2 ] + * </code></pre> + * <p>This matrix can then be used to apply the rotation to a + * column vector point with</p> + * <p><code>p' = Rp</code></p> + * <p>where <code>p</code> is in the device sensor coordinate system, and + * <code>p'</code> is in the camera-oriented coordinate system.</p> + * <p><b>Units</b>: + * Quarternion coefficients</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + */ + @PublicKey + public static final Key<float[]> LENS_POSE_ROTATION = + new Key<float[]>("android.lens.poseRotation", float[].class); + + /** + * <p>Position of the camera optical center.</p> + * <p>As measured in the device sensor coordinate system, the + * position of the camera device's optical center, as a + * three-dimensional vector <code>(x,y,z)</code>.</p> + * <p>To transform a world position to a camera-device centered + * coordinate system, the position must be translated by this + * vector and then rotated by {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}.</p> + * <p><b>Units</b>: Meters</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#LENS_POSE_ROTATION + */ + @PublicKey + public static final Key<float[]> LENS_POSE_TRANSLATION = + new Key<float[]>("android.lens.poseTranslation", float[].class); + + /** + * <p>The parameters for this camera device's intrinsic + * calibration.</p> + * <p>The five calibration parameters that describe the + * transform from camera-centric 3D coordinates to sensor + * pixel coordinates:</p> + * <pre><code>[f_x, f_y, c_x, c_y, s] + * </code></pre> + * <p>Where <code>f_x</code> and <code>f_y</code> are the horizontal and vertical + * focal lengths, <code>[c_x, c_y]</code> is the position of the optical + * axis, and <code>s</code> is a skew parameter for the sensor plane not + * being aligned with the lens plane.</p> + * <p>These are typically used within a transformation matrix K:</p> + * <pre><code>K = [ f_x, s, c_x, + * 0, f_y, c_y, + * 0 0, 1 ] + * </code></pre> + * <p>which can then be combined with the camera pose rotation + * <code>R</code> and translation <code>t</code> ({@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} and + * {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}, respective) to calculate the + * complete transform from world coordinates to pixel + * coordinates:</p> + * <pre><code>P = [ K 0 * [ R t + * 0 1 ] 0 1 ] + * </code></pre> + * <p>and with <code>p_w</code> being a point in the world coordinate system + * and <code>p_s</code> being a point in the camera active pixel array + * coordinate system, and with the mapping including the + * homogeneous division by z:</p> + * <pre><code> p_h = (x_h, y_h, z_h) = P p_w + * p_s = p_h / z_h + * </code></pre> + * <p>so <code>[x_s, y_s]</code> is the pixel coordinates of the world + * point, <code>z_s = 1</code>, and <code>w_s</code> is a measurement of disparity + * (depth) in pixel coordinates.</p> + * <p><b>Units</b>: + * Pixels in the android.sensor.activeArraySize coordinate + * system.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#LENS_POSE_ROTATION + * @see CameraCharacteristics#LENS_POSE_TRANSLATION + */ + @PublicKey + public static final Key<float[]> LENS_INTRINSIC_CALIBRATION = + new Key<float[]>("android.lens.intrinsicCalibration", float[].class); + + /** + * <p>The correction coefficients to correct for this camera device's + * radial lens distortion.</p> + * <p>Three cofficients <code>[kappa_1, kappa_2, kappa_3]</code> that + * can be used to correct the lens's radial geometric + * distortion with the mapping equations:</p> + * <pre><code> x_c = x_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 ) + * y_c = y_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 ) + * </code></pre> + * <p>where <code>[x_i, y_i]</code> are normalized coordinates with <code>(0,0)</code> + * at the lens optical center, and <code>[-1, 1]</code> are the edges of + * the active pixel array; and where <code>[x_c, y_c]</code> are the + * corrected normalized coordinates with radial distortion + * removed; and <code>r^2 = x_i^2 + y_i^2</code>.</p> + * <p><b>Units</b>: + * Coefficients for a 6th-degree even radial polynomial.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + */ + @PublicKey + public static final Key<float[]> LENS_RADIAL_DISTORTION = + new Key<float[]>("android.lens.radialDistortion", float[].class); + + /** * <p>List of noise reduction modes for {@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode} that are supported * by this camera device.</p> * <p>Full-capability camera devices will always support OFF and FAST.</p> @@ -991,7 +1114,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <ul> * <li>Processed (but stalling): any non-RAW format with a stallDurations > 0. * Typically JPEG format (ImageFormat#JPEG).</li> - * <li>Raw formats: ImageFormat#RAW_SENSOR, ImageFormat#RAW10 and ImageFormat#RAW_OPAQUE.</li> + * <li>Raw formats: ImageFormat#RAW_SENSOR, ImageFormat#RAW10, ImageFormat#RAW12, + * and ImageFormat#RAW_OPAQUE.</li> * <li>Processed (but not-stalling): any non-RAW format without a stall duration. * Typically ImageFormat#YUV_420_888, ImageFormat#NV21, ImageFormat#YV12.</li> * </ul> @@ -1023,6 +1147,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <ul> * <li>ImageFormat#RAW_SENSOR</li> * <li>ImageFormat#RAW10</li> + * <li>ImageFormat#RAW12</li> * <li>Opaque <code>RAW</code></li> * </ul> * <p>LEGACY mode devices ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} <code>==</code> LEGACY) @@ -2352,8 +2477,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>Camera devices that support the MANUAL_POST_PROCESSING capability will always contain * at least one of below mode combinations:</p> * <ul> - * <li>CONTRAST_CURVE and FAST</li> - * <li>GAMMA_VALUE, PRESET_CURVE, and FAST</li> + * <li>CONTRAST_CURVE, FAST and HIGH_QUALITY</li> + * <li>GAMMA_VALUE, PRESET_CURVE, FAST and HIGH_QUALITY</li> * </ul> * <p>This includes all FULL level devices.</p> * <p><b>Range of valid values:</b><br> diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 7f901c8..85c574a 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -852,8 +852,8 @@ public abstract class CameraMetadata<TKey> { /** * <p>Color correction processing operates at improved - * quality but reduced capture rate (relative to sensor raw - * output).</p> + * quality but the capture rate might be reduced (relative to sensor + * raw output rate)</p> * <p>Advanced white balance adjustments above and beyond * the specified white balance pipeline may be applied.</p> * <p>If AWB is enabled with <code>{@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode} != OFF</code>, then @@ -883,8 +883,8 @@ public abstract class CameraMetadata<TKey> { public static final int COLOR_CORRECTION_ABERRATION_MODE_FAST = 1; /** - * <p>Aberration correction operates at improved quality but reduced - * capture rate (relative to sensor raw output).</p> + * <p>Aberration correction operates at improved quality but the capture rate might be + * reduced (relative to sensor raw output rate)</p> * @see CaptureRequest#COLOR_CORRECTION_ABERRATION_MODE */ public static final int COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY = 2; @@ -1797,7 +1797,7 @@ public abstract class CameraMetadata<TKey> { public static final int EDGE_MODE_FAST = 1; /** - * <p>Apply high-quality edge enhancement, at a cost of reducing output frame rate.</p> + * <p>Apply high-quality edge enhancement, at a cost of possibly reduced output frame rate.</p> * @see CaptureRequest#EDGE_MODE */ public static final int EDGE_MODE_HIGH_QUALITY = 2; @@ -1852,7 +1852,7 @@ public abstract class CameraMetadata<TKey> { /** * <p>High-quality hot pixel correction is applied, at a cost - * of reducing frame rate relative to sensor raw output.</p> + * of possibly reduced frame rate relative to sensor raw output.</p> * <p>The hotpixel map may be returned in {@link CaptureResult#STATISTICS_HOT_PIXEL_MAP android.statistics.hotPixelMap}.</p> * * @see CaptureResult#STATISTICS_HOT_PIXEL_MAP @@ -1894,8 +1894,8 @@ public abstract class CameraMetadata<TKey> { public static final int NOISE_REDUCTION_MODE_FAST = 1; /** - * <p>High-quality noise reduction is applied, at the cost of reducing frame rate - * relative to sensor output.</p> + * <p>High-quality noise reduction is applied, at the cost of possibly reduced frame + * rate relative to sensor output.</p> * @see CaptureRequest#NOISE_REDUCTION_MODE */ public static final int NOISE_REDUCTION_MODE_HIGH_QUALITY = 2; @@ -2032,7 +2032,7 @@ public abstract class CameraMetadata<TKey> { /** * <p>Apply high-quality lens shading correction, at the - * cost of reduced frame rate.</p> + * cost of possibly reduced frame rate.</p> * @see CaptureRequest#SHADING_MODE */ public static final int SHADING_MODE_HIGH_QUALITY = 2; @@ -2105,7 +2105,7 @@ public abstract class CameraMetadata<TKey> { /** * <p>High-quality gamma mapping and color enhancement will be applied, at - * the cost of reduced frame rate compared to raw sensor output.</p> + * the cost of possibly reduced frame rate compared to raw sensor output.</p> * @see CaptureRequest#TONEMAP_MODE */ public static final int TONEMAP_MODE_HIGH_QUALITY = 2; diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 7569ea5..b8fb8e7 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -1541,7 +1541,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * to the camera, that the JPEG picture needs to be rotated by, to be viewed * upright.</p> * <p>Camera devices may either encode this value into the JPEG EXIF header, or - * rotate the image data to match this orientation.</p> + * rotate the image data to match this orientation. When the image data is rotated, + * the thumbnail data will also be rotated.</p> * <p>Note that this orientation is relative to the orientation of the camera sensor, given * by {@link CameraCharacteristics#SENSOR_ORIENTATION android.sensor.orientation}.</p> * <p>To translate from the device orientation given by the Android sensor APIs, the following diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index b84dc2e..d8f92e5 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -2230,7 +2230,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * to the camera, that the JPEG picture needs to be rotated by, to be viewed * upright.</p> * <p>Camera devices may either encode this value into the JPEG EXIF header, or - * rotate the image data to match this orientation.</p> + * rotate the image data to match this orientation. When the image data is rotated, + * the thumbnail data will also be rotated.</p> * <p>Note that this orientation is relative to the orientation of the camera sensor, given * by {@link CameraCharacteristics#SENSOR_ORIENTATION android.sensor.orientation}.</p> * <p>To translate from the device orientation given by the Android sensor APIs, the following @@ -2531,6 +2532,129 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { new Key<Integer>("android.lens.state", int.class); /** + * <p>The orientation of the camera relative to the sensor + * coordinate system.</p> + * <p>The four coefficients that describe the quarternion + * rotation from the Android sensor coordinate system to a + * camera-aligned coordinate system where the X-axis is + * aligned with the long side of the image sensor, the Y-axis + * is aligned with the short side of the image sensor, and + * the Z-axis is aligned with the optical axis of the sensor.</p> + * <p>To convert from the quarternion coefficients <code>(x,y,z,w)</code> + * to the axis of rotation <code>(a_x, a_y, a_z)</code> and rotation + * amount <code>theta</code>, the following formulas can be used:</p> + * <pre><code> theta = 2 * acos(w) + * a_x = x / sin(theta/2) + * a_y = y / sin(theta/2) + * a_z = z / sin(theta/2) + * </code></pre> + * <p>To create a 3x3 rotation matrix that applies the rotation + * defined by this quarternion, the following matrix can be + * used:</p> + * <pre><code>R = [ 1 - 2y^2 - 2z^2, 2xy - 2zw, 2xz + 2yw, + * 2xy + 2zw, 1 - 2x^2 - 2z^2, 2yz - 2xw, + * 2xz - 2yw, 2yz + 2xw, 1 - 2x^2 - 2y^2 ] + * </code></pre> + * <p>This matrix can then be used to apply the rotation to a + * column vector point with</p> + * <p><code>p' = Rp</code></p> + * <p>where <code>p</code> is in the device sensor coordinate system, and + * <code>p'</code> is in the camera-oriented coordinate system.</p> + * <p><b>Units</b>: + * Quarternion coefficients</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + */ + @PublicKey + public static final Key<float[]> LENS_POSE_ROTATION = + new Key<float[]>("android.lens.poseRotation", float[].class); + + /** + * <p>Position of the camera optical center.</p> + * <p>As measured in the device sensor coordinate system, the + * position of the camera device's optical center, as a + * three-dimensional vector <code>(x,y,z)</code>.</p> + * <p>To transform a world position to a camera-device centered + * coordinate system, the position must be translated by this + * vector and then rotated by {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}.</p> + * <p><b>Units</b>: Meters</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#LENS_POSE_ROTATION + */ + @PublicKey + public static final Key<float[]> LENS_POSE_TRANSLATION = + new Key<float[]>("android.lens.poseTranslation", float[].class); + + /** + * <p>The parameters for this camera device's intrinsic + * calibration.</p> + * <p>The five calibration parameters that describe the + * transform from camera-centric 3D coordinates to sensor + * pixel coordinates:</p> + * <pre><code>[f_x, f_y, c_x, c_y, s] + * </code></pre> + * <p>Where <code>f_x</code> and <code>f_y</code> are the horizontal and vertical + * focal lengths, <code>[c_x, c_y]</code> is the position of the optical + * axis, and <code>s</code> is a skew parameter for the sensor plane not + * being aligned with the lens plane.</p> + * <p>These are typically used within a transformation matrix K:</p> + * <pre><code>K = [ f_x, s, c_x, + * 0, f_y, c_y, + * 0 0, 1 ] + * </code></pre> + * <p>which can then be combined with the camera pose rotation + * <code>R</code> and translation <code>t</code> ({@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} and + * {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}, respective) to calculate the + * complete transform from world coordinates to pixel + * coordinates:</p> + * <pre><code>P = [ K 0 * [ R t + * 0 1 ] 0 1 ] + * </code></pre> + * <p>and with <code>p_w</code> being a point in the world coordinate system + * and <code>p_s</code> being a point in the camera active pixel array + * coordinate system, and with the mapping including the + * homogeneous division by z:</p> + * <pre><code> p_h = (x_h, y_h, z_h) = P p_w + * p_s = p_h / z_h + * </code></pre> + * <p>so <code>[x_s, y_s]</code> is the pixel coordinates of the world + * point, <code>z_s = 1</code>, and <code>w_s</code> is a measurement of disparity + * (depth) in pixel coordinates.</p> + * <p><b>Units</b>: + * Pixels in the android.sensor.activeArraySize coordinate + * system.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#LENS_POSE_ROTATION + * @see CameraCharacteristics#LENS_POSE_TRANSLATION + */ + @PublicKey + public static final Key<float[]> LENS_INTRINSIC_CALIBRATION = + new Key<float[]>("android.lens.intrinsicCalibration", float[].class); + + /** + * <p>The correction coefficients to correct for this camera device's + * radial lens distortion.</p> + * <p>Three cofficients <code>[kappa_1, kappa_2, kappa_3]</code> that + * can be used to correct the lens's radial geometric + * distortion with the mapping equations:</p> + * <pre><code> x_c = x_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 ) + * y_c = y_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 ) + * </code></pre> + * <p>where <code>[x_i, y_i]</code> are normalized coordinates with <code>(0,0)</code> + * at the lens optical center, and <code>[-1, 1]</code> are the edges of + * the active pixel array; and where <code>[x_c, y_c]</code> are the + * corrected normalized coordinates with radial distortion + * removed; and <code>r^2 = x_i^2 + y_i^2</code>.</p> + * <p><b>Units</b>: + * Coefficients for a 6th-degree even radial polynomial.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + */ + @PublicKey + public static final Key<float[]> LENS_RADIAL_DISTORTION = + new Key<float[]>("android.lens.radialDistortion", float[].class); + + /** * <p>Mode of operation for the noise reduction algorithm.</p> * <p>The noise reduction algorithm attempts to improve image quality by removing * excessive noise added by the capture process, especially in dark conditions.</p> diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index f47ce79..e926a98 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -830,11 +830,19 @@ public class CameraMetadataNative implements Parcelable { CameraCharacteristics.SCALER_AVAILABLE_MIN_FRAME_DURATIONS); StreamConfigurationDuration[] stallDurations = getBase( CameraCharacteristics.SCALER_AVAILABLE_STALL_DURATIONS); + StreamConfiguration[] depthConfigurations = getBase( + CameraCharacteristics.DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS); + StreamConfigurationDuration[] depthMinFrameDurations = getBase( + CameraCharacteristics.DEPTH_AVAILABLE_DEPTH_MIN_FRAME_DURATIONS); + StreamConfigurationDuration[] depthStallDurations = getBase( + CameraCharacteristics.DEPTH_AVAILABLE_DEPTH_STALL_DURATIONS); HighSpeedVideoConfiguration[] highSpeedVideoConfigurations = getBase( CameraCharacteristics.CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS); return new StreamConfigurationMap( - configurations, minFrameDurations, stallDurations, highSpeedVideoConfigurations); + configurations, minFrameDurations, stallDurations, + depthConfigurations, depthMinFrameDurations, depthStallDurations, + highSpeedVideoConfigurations); } private <T> Integer getMaxRegions(Key<T> key) { diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java index 0a4ed39..7aa9787 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -18,6 +18,7 @@ package android.hardware.camera2.params; import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.utils.HashCodeHelpers; import android.util.Log; import android.view.Surface; import android.os.Parcel; @@ -159,6 +160,35 @@ public final class OutputConfiguration implements Parcelable { mSurface.writeToParcel(dest, flags); } + /** + * Check if this {@link OutputConfiguration} is equal to another {@link OutputConfiguration}. + * + * <p>Two output configurations are only equal if and only if the underlying surface and + * all other configuration parameters are equal. </p> + * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } else if (this == obj) { + return true; + } else if (obj instanceof OutputConfiguration) { + final OutputConfiguration other = (OutputConfiguration) obj; + return (mSurface == other.mSurface && mRotation == other.mRotation); + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return HashCodeHelpers.hashCode(mSurface.hashCode(), mRotation); + } + private static final String TAG = "OutputConfiguration"; private final Surface mSurface; private final int mRotation; diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java index f5304f8..b418971 100644 --- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java +++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java @@ -89,11 +89,28 @@ public final class StreamConfigurationMap { StreamConfiguration[] configurations, StreamConfigurationDuration[] minFrameDurations, StreamConfigurationDuration[] stallDurations, + StreamConfiguration[] depthConfigurations, + StreamConfigurationDuration[] depthMinFrameDurations, + StreamConfigurationDuration[] depthStallDurations, HighSpeedVideoConfiguration[] highSpeedVideoConfigurations) { mConfigurations = checkArrayElementsNotNull(configurations, "configurations"); mMinFrameDurations = checkArrayElementsNotNull(minFrameDurations, "minFrameDurations"); mStallDurations = checkArrayElementsNotNull(stallDurations, "stallDurations"); + + if (depthConfigurations == null) { + mDepthConfigurations = new StreamConfiguration[0]; + mDepthMinFrameDurations = new StreamConfigurationDuration[0]; + mDepthStallDurations = new StreamConfigurationDuration[0]; + } else { + mDepthConfigurations = checkArrayElementsNotNull(depthConfigurations, + "depthConfigurations"); + mDepthMinFrameDurations = checkArrayElementsNotNull(depthMinFrameDurations, + "depthMinFrameDurations"); + mDepthStallDurations = checkArrayElementsNotNull(depthStallDurations, + "depthStallDurations"); + } + if (highSpeedVideoConfigurations == null) { mHighSpeedVideoConfigurations = new HighSpeedVideoConfiguration[0]; } else { @@ -110,9 +127,24 @@ public final class StreamConfigurationMap { if (count == null) { count = 0; } - count = count + 1; - map.put(config.getFormat(), count); + map.put(config.getFormat(), count + 1); + } + + // For each depth format, track how many sizes there are available to configure + for (StreamConfiguration config : mDepthConfigurations) { + if (!config.isOutput()) { + // Ignoring input depth configs + continue; + } + + Integer count = mDepthOutputFormats.get(config.getFormat()); + + if (count == null) { + count = 0; + } + + mDepthOutputFormats.put(config.getFormat(), count + 1); } if (!mOutputFormats.containsKey(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED)) { @@ -214,8 +246,13 @@ public final class StreamConfigurationMap { public boolean isOutputSupportedFor(int format) { checkArgumentFormat(format); - format = imageFormatToInternal(format); - return getFormatsMap(/*output*/true).containsKey(format); + int internalFormat = imageFormatToInternal(format); + int dataspace = imageFormatToDataspace(format); + if (dataspace == HAL_DATASPACE_DEPTH) { + return mDepthOutputFormats.containsKey(internalFormat); + } else { + return getFormatsMap(/*output*/true).containsKey(internalFormat); + } } /** @@ -386,7 +423,8 @@ public final class StreamConfigurationMap { return null; } - return getInternalFormatSizes(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED, /*output*/true); + return getInternalFormatSizes(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED, + HAL_DATASPACE_UNKNOWN,/*output*/true); } /** @@ -599,7 +637,10 @@ public final class StreamConfigurationMap { checkNotNull(size, "size must not be null"); checkArgumentFormatSupported(format, /*output*/true); - return getInternalFormatDuration(imageFormatToInternal(format), size, DURATION_MIN_FRAME); + return getInternalFormatDuration(imageFormatToInternal(format), + imageFormatToDataspace(format), + size, + DURATION_MIN_FRAME); } /** @@ -652,6 +693,7 @@ public final class StreamConfigurationMap { } return getInternalFormatDuration(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED, + HAL_DATASPACE_UNKNOWN, size, DURATION_MIN_FRAME); } @@ -741,7 +783,9 @@ public final class StreamConfigurationMap { checkArgumentFormatSupported(format, /*output*/true); return getInternalFormatDuration(imageFormatToInternal(format), - size, DURATION_STALL); + imageFormatToDataspace(format), + size, + DURATION_STALL); } /** @@ -778,7 +822,7 @@ public final class StreamConfigurationMap { } return getInternalFormatDuration(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED, - size, DURATION_STALL); + HAL_DATASPACE_UNKNOWN, size, DURATION_STALL); } /** @@ -857,6 +901,7 @@ public final class StreamConfigurationMap { case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED: case HAL_PIXEL_FORMAT_BLOB: case HAL_PIXEL_FORMAT_RAW_OPAQUE: + case HAL_PIXEL_FORMAT_Y16: return format; case ImageFormat.JPEG: throw new IllegalArgumentException( @@ -896,8 +941,8 @@ public final class StreamConfigurationMap { } /** - * Convert a public-visible {@code ImageFormat} into an internal format - * compatible with {@code graphics.h}. + * Convert an internal format compatible with {@code graphics.h} into public-visible + * {@code ImageFormat}. This assumes the dataspace of the format is not HAL_DATASPACE_DEPTH. * * <p>In particular these formats are converted: * <ul> @@ -911,7 +956,8 @@ public final class StreamConfigurationMap { * * <p>All other formats are returned as-is, no further invalid check is performed.</p> * - * <p>This function is the dual of {@link #imageFormatToInternal}.</p> + * <p>This function is the dual of {@link #imageFormatToInternal} for dataspaces other than + * HAL_DATASPACE_DEPTH.</p> * * @param format image format from {@link ImageFormat} or {@link PixelFormat} * @return the converted image formats @@ -940,6 +986,55 @@ public final class StreamConfigurationMap { } /** + * Convert an internal format compatible with {@code graphics.h} into public-visible + * {@code ImageFormat}. This assumes the dataspace of the format is HAL_DATASPACE_DEPTH. + * + * <p>In particular these formats are converted: + * <ul> + * <li>HAL_PIXEL_FORMAT_BLOB => ImageFormat.DEPTH_POINT_CLOUD + * <li>HAL_PIXEL_FORMAT_Y16 => ImageFormat.DEPTH16 + * </ul> + * </p> + * + * <p>Passing in an implementation-defined format which has no public equivalent will fail; + * as will passing in a public format which has a different internal format equivalent. + * See {@link #checkArgumentFormat} for more details about a legal public format.</p> + * + * <p>All other formats are returned as-is, no further invalid check is performed.</p> + * + * <p>This function is the dual of {@link #imageFormatToInternal} for formats associated with + * HAL_DATASPACE_DEPTH.</p> + * + * @param format image format from {@link ImageFormat} or {@link PixelFormat} + * @return the converted image formats + * + * @throws IllegalArgumentException + * if {@code format} is {@code HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED} or + * {@link ImageFormat#JPEG} + * + * @see ImageFormat + * @see PixelFormat + * @see #checkArgumentFormat + */ + static int depthFormatToPublic(int format) { + switch (format) { + case HAL_PIXEL_FORMAT_BLOB: + return ImageFormat.DEPTH_POINT_CLOUD; + case HAL_PIXEL_FORMAT_Y16: + return ImageFormat.DEPTH16; + case ImageFormat.JPEG: + throw new IllegalArgumentException( + "ImageFormat.JPEG is an unknown internal format"); + case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED: + throw new IllegalArgumentException( + "IMPLEMENTATION_DEFINED must not leak to public API"); + default: + throw new IllegalArgumentException( + "Unknown DATASPACE_DEPTH format " + format); + } + } + + /** * Convert image formats from internal to public formats (in-place). * * @param formats an array of image formats @@ -966,6 +1061,8 @@ public final class StreamConfigurationMap { * <p>In particular these formats are converted: * <ul> * <li>ImageFormat.JPEG => HAL_PIXEL_FORMAT_BLOB + * <li>ImageFormat.DEPTH_POINT_CLOUD => HAL_PIXEL_FORMAT_BLOB + * <li>ImageFormat.DEPTH16 => HAL_PIXEL_FORMAT_Y16 * </ul> * </p> * @@ -989,7 +1086,10 @@ public final class StreamConfigurationMap { static int imageFormatToInternal(int format) { switch (format) { case ImageFormat.JPEG: + case ImageFormat.DEPTH_POINT_CLOUD: return HAL_PIXEL_FORMAT_BLOB; + case ImageFormat.DEPTH16: + return HAL_PIXEL_FORMAT_Y16; case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED: throw new IllegalArgumentException( "IMPLEMENTATION_DEFINED is not allowed via public API"); @@ -999,6 +1099,48 @@ public final class StreamConfigurationMap { } /** + * Convert a public format compatible with {@code ImageFormat} to an internal dataspace + * from {@code graphics.h}. + * + * <p>In particular these formats are converted: + * <ul> + * <li>ImageFormat.JPEG => HAL_DATASPACE_JFIF + * <li>ImageFormat.DEPTH_POINT_CLOUD => HAL_DATASPACE_DEPTH + * <li>ImageFormat.DEPTH16 => HAL_DATASPACE_DEPTH + * <li>others => HAL_DATASPACE_UNKNOWN + * </ul> + * </p> + * + * <p>Passing in an implementation-defined format here will fail (it's not a public format); + * as will passing in an internal format which has a different public format equivalent. + * See {@link #checkArgumentFormat} for more details about a legal public format.</p> + * + * <p>All other formats are returned as-is, no invalid check is performed.</p> + * + * <p>This function is the dual of {@link #imageFormatToPublic}.</p> + * + * @param format public image format from {@link ImageFormat} or {@link PixelFormat} + * @return the converted image formats + * + * @see ImageFormat + * @see PixelFormat + * + * @throws IllegalArgumentException + * if {@code format} was {@code HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED} + */ + static int imageFormatToDataspace(int format) { + switch (format) { + case ImageFormat.JPEG: + return HAL_DATASPACE_JFIF; + case ImageFormat.DEPTH_POINT_CLOUD: + case ImageFormat.DEPTH16: + return HAL_DATASPACE_DEPTH; + default: + return HAL_DATASPACE_UNKNOWN; + } + } + + /** * Convert image formats from public to internal formats (in-place). * * @param formats an array of image formats @@ -1027,13 +1169,16 @@ public final class StreamConfigurationMap { return null; } - format = imageFormatToInternal(format); + int internalFormat = imageFormatToInternal(format); + int dataspace = imageFormatToDataspace(format); - return getInternalFormatSizes(format, output); + return getInternalFormatSizes(internalFormat, dataspace, output); } - private Size[] getInternalFormatSizes(int format, boolean output) { - HashMap<Integer, Integer> formatsMap = getFormatsMap(output); + private Size[] getInternalFormatSizes(int format, int dataspace, boolean output) { + + HashMap<Integer, Integer> formatsMap = + (dataspace == HAL_DATASPACE_DEPTH) ? mDepthOutputFormats : getFormatsMap(output); Integer sizesCount = formatsMap.get(format); if (sizesCount == null) { @@ -1044,7 +1189,11 @@ public final class StreamConfigurationMap { Size[] sizes = new Size[len]; int sizeIndex = 0; - for (StreamConfiguration config : mConfigurations) { + StreamConfiguration[] configurations = + (dataspace == HAL_DATASPACE_DEPTH) ? mDepthConfigurations : mConfigurations; + + + for (StreamConfiguration config : configurations) { if (config.getFormat() == format && config.isOutput() == output) { sizes[sizeIndex++] = config.getSize(); } @@ -1067,15 +1216,19 @@ public final class StreamConfigurationMap { for (int format : getFormatsMap(output).keySet()) { if (format != HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED && format != HAL_PIXEL_FORMAT_RAW_OPAQUE) { - formats[i++] = format; + formats[i++] = imageFormatToPublic(format); + } + } + if (output) { + for (int format : mDepthOutputFormats.keySet()) { + formats[i++] = depthFormatToPublic(format); } } - if (formats.length != i) { throw new AssertionError("Too few formats " + i + ", expected " + formats.length); } - return imageFormatToPublic(formats); + return formats; } /** Get the format -> size count map for either output or input formats */ @@ -1083,14 +1236,14 @@ public final class StreamConfigurationMap { return output ? mOutputFormats : mInputFormats; } - private long getInternalFormatDuration(int format, Size size, int duration) { + private long getInternalFormatDuration(int format, int dataspace, Size size, int duration) { // assume format is already checked, since its internal - if (!arrayContains(getInternalFormatSizes(format, /*output*/true), size)) { + if (!arrayContains(getInternalFormatSizes(format, dataspace, /*output*/true), size)) { throw new IllegalArgumentException("size was not supported"); } - StreamConfigurationDuration[] durations = getDurations(duration); + StreamConfigurationDuration[] durations = getDurations(duration, dataspace); for (StreamConfigurationDuration configurationDuration : durations) { if (configurationDuration.getFormat() == format && @@ -1109,12 +1262,14 @@ public final class StreamConfigurationMap { * @see #DURATION_MIN_FRAME * @see #DURATION_STALL * */ - private StreamConfigurationDuration[] getDurations(int duration) { + private StreamConfigurationDuration[] getDurations(int duration, int dataspace) { switch (duration) { case DURATION_MIN_FRAME: - return mMinFrameDurations; + return (dataspace == HAL_DATASPACE_DEPTH) ? + mDepthMinFrameDurations : mMinFrameDurations; case DURATION_STALL: - return mStallDurations; + return (dataspace == HAL_DATASPACE_DEPTH) ? + mDepthStallDurations : mStallDurations; default: throw new IllegalArgumentException("duration was invalid"); } @@ -1131,6 +1286,9 @@ public final class StreamConfigurationMap { if (formatsMap.containsKey(HAL_PIXEL_FORMAT_RAW_OPAQUE)) { size -= 1; } + if (output) { + size += mDepthOutputFormats.size(); + } return size; } @@ -1153,10 +1311,14 @@ public final class StreamConfigurationMap { private static final int HAL_PIXEL_FORMAT_BLOB = 0x21; private static final int HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED = 0x22; private static final int HAL_PIXEL_FORMAT_RAW_OPAQUE = 0x24; + private static final int HAL_PIXEL_FORMAT_Y16 = 0x20363159; + + private static final int HAL_DATASPACE_UNKNOWN = 0x0; + private static final int HAL_DATASPACE_JFIF = 0x101; + private static final int HAL_DATASPACE_DEPTH = 0x1000; /** - * @see #getDurations(int) - * @see #getDurationDefault(int) + * @see #getDurations(int, int) */ private static final int DURATION_MIN_FRAME = 0; private static final int DURATION_STALL = 1; @@ -1164,6 +1326,11 @@ public final class StreamConfigurationMap { private final StreamConfiguration[] mConfigurations; private final StreamConfigurationDuration[] mMinFrameDurations; private final StreamConfigurationDuration[] mStallDurations; + + private final StreamConfiguration[] mDepthConfigurations; + private final StreamConfigurationDuration[] mDepthMinFrameDurations; + private final StreamConfigurationDuration[] mDepthStallDurations; + private final HighSpeedVideoConfiguration[] mHighSpeedVideoConfigurations; /** ImageFormat -> num output sizes mapping */ @@ -1172,6 +1339,9 @@ public final class StreamConfigurationMap { /** ImageFormat -> num input sizes mapping */ private final HashMap</*ImageFormat*/Integer, /*Count*/Integer> mInputFormats = new HashMap<Integer, Integer>(); + /** ImageFormat -> num depth output sizes mapping */ + private final HashMap</*ImageFormat*/Integer, /*Count*/Integer> mDepthOutputFormats = + new HashMap<Integer, Integer>(); /** High speed video Size -> FPS range count mapping*/ private final HashMap</*HighSpeedVideoSize*/Size, /*Count*/Integer> mHighSpeedVideoSizeMap = new HashMap<Size, Integer>(); diff --git a/core/java/android/service/fingerprint/Fingerprint.aidl b/core/java/android/hardware/fingerprint/Fingerprint.aidl index c9fd989..4743354 100644 --- a/core/java/android/service/fingerprint/Fingerprint.aidl +++ b/core/java/android/hardware/fingerprint/Fingerprint.aidl @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.service.fingerprint; +package android.hardware.fingerprint; // @hide parcelable Fingerprint; diff --git a/core/java/android/service/fingerprint/Fingerprint.java b/core/java/android/hardware/fingerprint/Fingerprint.java index 37552eb..c307634 100644 --- a/core/java/android/service/fingerprint/Fingerprint.java +++ b/core/java/android/hardware/fingerprint/Fingerprint.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.service.fingerprint; +package android.hardware.fingerprint; import android.os.Parcel; import android.os.Parcelable; diff --git a/core/java/android/service/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index bb90e40..e3572a2 100644 --- a/core/java/android/service/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.service.fingerprint; +package android.hardware.fingerprint; import android.app.ActivityManagerNative; import android.content.ContentResolver; @@ -28,7 +28,7 @@ import android.os.Parcelable; import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; -import android.service.fingerprint.FingerprintManager.EnrollmentCallback; +import android.hardware.fingerprint.FingerprintManager.EnrollmentCallback; import android.util.Log; import android.util.Slog; diff --git a/core/java/android/service/fingerprint/FingerprintUtils.java b/core/java/android/hardware/fingerprint/FingerprintUtils.java index 62acbb9..ae3d4a4 100644 --- a/core/java/android/service/fingerprint/FingerprintUtils.java +++ b/core/java/android/hardware/fingerprint/FingerprintUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.service.fingerprint; +package android.hardware.fingerprint; import android.content.ContentResolver; import android.provider.Settings; diff --git a/core/java/android/service/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index e5d3ad4..c5a45e2 100644 --- a/core/java/android/service/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.service.fingerprint; +package android.hardware.fingerprint; import android.os.Bundle; -import android.service.fingerprint.IFingerprintServiceReceiver; -import android.service.fingerprint.Fingerprint; +import android.hardware.fingerprint.IFingerprintServiceReceiver; +import android.hardware.fingerprint.Fingerprint; import java.util.List; /** diff --git a/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl index f025064..e82395f 100644 --- a/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.service.fingerprint; +package android.hardware.fingerprint; import android.os.Bundle; import android.os.UserHandle; diff --git a/core/java/android/hardware/hdmi/HdmiTvClient.java b/core/java/android/hardware/hdmi/HdmiTvClient.java index a94c1da..a336e5c 100644 --- a/core/java/android/hardware/hdmi/HdmiTvClient.java +++ b/core/java/android/hardware/hdmi/HdmiTvClient.java @@ -168,7 +168,22 @@ public final class HdmiTvClient extends HdmiClient { } /** - * Sets system audio volume + * Sets system audio mode. + * + * @param enabled set to {@code true} to enable the mode; otherwise {@code false} + * @param callback callback to get the result with + * @throws {@link IllegalArgumentException} if the {@code callback} is null + */ + public void setSystemAudioMode(boolean enabled, SelectCallback callback) { + try { + mService.setSystemAudioMode(enabled, getCallbackWrapper(callback)); + } catch (RemoteException e) { + Log.e(TAG, "failed to set system audio mode:", e); + } + } + + /** + * Sets system audio volume. * * @param oldIndex current volume index * @param newIndex volume index to be set @@ -183,7 +198,7 @@ public final class HdmiTvClient extends HdmiClient { } /** - * Sets system audio mute status + * Sets system audio mute status. * * @param mute {@code true} if muted; otherwise, {@code false} */ @@ -196,7 +211,7 @@ public final class HdmiTvClient extends HdmiClient { } /** - * Sets record listener + * Sets record listener. * * @param listener */ diff --git a/core/java/android/inputmethodservice/ExtractEditLayout.java b/core/java/android/inputmethodservice/ExtractEditLayout.java index 5696839..e902443 100644 --- a/core/java/android/inputmethodservice/ExtractEditLayout.java +++ b/core/java/android/inputmethodservice/ExtractEditLayout.java @@ -163,6 +163,8 @@ public class ExtractEditLayout extends LinearLayout { mCallback.onDestroyActionMode(this); mCallback = null; + mMenu.close(); + mExtractActionButton.setVisibility(VISIBLE); mEditButton.setVisibility(INVISIBLE); diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index a0e2bf8..3abccbc 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -40,6 +40,7 @@ import android.telephony.SubscriptionManager; import android.util.ArrayMap; import android.util.Log; +import com.android.internal.net.VpnConfig; import com.android.internal.telephony.ITelephony; import com.android.internal.telephony.PhoneConstants; import com.android.internal.util.Protocol; @@ -2447,6 +2448,38 @@ public class ConnectivityManager { } /** + * Resets all connectivity manager settings back to factory defaults. + * @hide + */ + public void factoryReset() { + // Turn airplane mode off + setAirplaneMode(false); + + // Untether + for (String tether : getTetheredIfaces()) { + untether(tether); + } + + // Turn VPN off + try { + VpnConfig vpnConfig = mService.getVpnConfig(); + if (vpnConfig != null) { + if (vpnConfig.legacy) { + mService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN); + } else { + // Prevent this app from initiating VPN connections in the future without + // user intervention. + mService.setVpnPackageAuthorization(false); + + mService.prepareVpn(vpnConfig.user, VpnConfig.LEGACY_VPN); + } + } + } catch (RemoteException e) { + // Well, we tried + } + } + + /** * Binds the current process to {@code network}. All Sockets created in the future * (and not explicitly bound via a bound SocketFactory from * {@link Network#getSocketFactory() Network.getSocketFactory()}) will be bound to diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index a8e7757..a7ffee9 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -180,6 +180,33 @@ public class NetworkPolicyManager { } /** + * Resets network policy settings back to factory defaults. + * + * @hide + */ + public void factoryReset(String subscriber) { + // Turn mobile data limit off + NetworkPolicy[] policies = getNetworkPolicies(); + NetworkTemplate template = NetworkTemplate.buildTemplateMobileAll(subscriber); + for (NetworkPolicy policy : policies) { + if (policy.template.equals(template)) { + policy.limitBytes = NetworkPolicy.LIMIT_DISABLED; + policy.inferred = false; + policy.clearSnooze(); + } + } + setNetworkPolicies(policies); + + // Turn restrict background data off + setRestrictBackground(false); + + // Remove app's "restrict background data" flag + for (int uid : getUidsWithPolicy(POLICY_REJECT_METERED_BACKGROUND)) { + setUidPolicy(uid, NetworkPolicyManager.POLICY_NONE); + } + } + + /** * Compute the last cycle boundary for the given {@link NetworkPolicy}. For * example, if cycle day is 20th, and today is June 15th, it will return May * 20th. When cycle day doesn't exist in current month, it snaps to the 1st diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index 0766253..77d7e0c 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -50,12 +50,19 @@ public class NetworkStats implements Parcelable { public static final int UID_ALL = -1; /** {@link #tag} value matching any tag. */ public static final int TAG_ALL = -1; - /** {@link #set} value when all sets combined. */ + /** {@link #set} value when all sets combined, not including debug sets. */ public static final int SET_ALL = -1; /** {@link #set} value where background data is accounted. */ public static final int SET_DEFAULT = 0; /** {@link #set} value where foreground data is accounted. */ public static final int SET_FOREGROUND = 1; + /** All {@link #set} value greater than SET_DEBUG_START are debug {@link #set} values. */ + public static final int SET_DEBUG_START = 1000; + /** Debug {@link #set} value when the VPN stats are moved in. */ + public static final int SET_DBG_VPN_IN = 1001; + /** Debug {@link #set} value when the VPN stats are moved out of a vpn UID. */ + public static final int SET_DBG_VPN_OUT = 1002; + /** {@link #tag} value for total data across all tags. */ public static final int TAG_NONE = 0; @@ -729,6 +736,10 @@ public class NetworkStats implements Parcelable { return "DEFAULT"; case SET_FOREGROUND: return "FOREGROUND"; + case SET_DBG_VPN_IN: + return "DBG_VPN_IN"; + case SET_DBG_VPN_OUT: + return "DBG_VPN_OUT"; default: return "UNKNOWN"; } @@ -745,12 +756,27 @@ public class NetworkStats implements Parcelable { return "def"; case SET_FOREGROUND: return "fg"; + case SET_DBG_VPN_IN: + return "vpnin"; + case SET_DBG_VPN_OUT: + return "vpnout"; default: return "unk"; } } /** + * @return true if the querySet matches the dataSet. + */ + public static boolean setMatches(int querySet, int dataSet) { + if (querySet == dataSet) { + return true; + } + // SET_ALL matches all non-debugging sets. + return querySet == SET_ALL && dataSet < SET_DEBUG_START; + } + + /** * Return text description of {@link #tag} value. */ public static String tagToString(int tag) { @@ -843,6 +869,9 @@ public class NetworkStats implements Parcelable { if (recycle.uid == UID_ALL) { throw new IllegalStateException( "Cannot adjust VPN accounting on an iface aggregated NetworkStats."); + } if (recycle.set == SET_DBG_VPN_IN || recycle.set == SET_DBG_VPN_OUT) { + throw new IllegalStateException( + "Cannot adjust VPN accounting on a NetworkStats containing SET_DBG_VPN_*"); } if (recycle.uid == tunUid && recycle.tag == TAG_NONE @@ -906,6 +935,9 @@ public class NetworkStats implements Parcelable { combineValues(tmpEntry); if (tag[i] == TAG_NONE) { moved.add(tmpEntry); + // Add debug info + tmpEntry.set = SET_DBG_VPN_IN; + combineValues(tmpEntry); } } } @@ -913,6 +945,13 @@ public class NetworkStats implements Parcelable { } private void deductTrafficFromVpnApp(int tunUid, String underlyingIface, Entry moved) { + // Add debug info + moved.uid = tunUid; + moved.set = SET_DBG_VPN_OUT; + moved.tag = TAG_NONE; + moved.iface = underlyingIface; + combineValues(moved); + // Caveat: if the vpn software uses tag, the total tagged traffic may be greater than // the TAG_NONE traffic. int idxVpnBackground = findIndex(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE); diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java index 3f18519..ff3de2b 100644 --- a/core/java/android/net/TrafficStats.java +++ b/core/java/android/net/TrafficStats.java @@ -51,6 +51,8 @@ public class TrafficStats { public static final long MB_IN_BYTES = KB_IN_BYTES * 1024; /** @hide */ public static final long GB_IN_BYTES = MB_IN_BYTES * 1024; + /** @hide */ + public static final long TB_IN_BYTES = GB_IN_BYTES * 1024; /** * Special UID value used when collecting {@link NetworkStatsHistory} for diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index bf3d9aa..f305b2a 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -366,7 +366,6 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { public String toSafeString() { String scheme = getScheme(); String ssp = getSchemeSpecificPart(); - String authority = null; if (scheme != null) { if (scheme.equalsIgnoreCase("tel") || scheme.equalsIgnoreCase("sip") || scheme.equalsIgnoreCase("sms") || scheme.equalsIgnoreCase("smsto") @@ -385,9 +384,11 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { } } return builder.toString(); - } else if (scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https")) { - ssp = null; - authority = "//" + getAuthority() + "/..."; + } else if (scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https") + || scheme.equalsIgnoreCase("ftp")) { + ssp = "//" + ((getHost() != null) ? getHost() : "") + + ((getPort() != -1) ? (":" + getPort()) : "") + + "/..."; } } // Not a sensitive scheme, but let's still be conservative about @@ -401,9 +402,6 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { if (ssp != null) { builder.append(ssp); } - if (authority != null) { - builder.append(authority); - } return builder.toString(); } diff --git a/core/java/android/net/WifiKey.java b/core/java/android/net/WifiKey.java index 71df2f9..99de99e 100644 --- a/core/java/android/net/WifiKey.java +++ b/core/java/android/net/WifiKey.java @@ -33,7 +33,8 @@ import java.util.regex.Pattern; public class WifiKey implements Parcelable { // Patterns used for validation. - private static final Pattern SSID_PATTERN = Pattern.compile("(\".*\")|(0x[\\p{XDigit}]+)"); + private static final Pattern SSID_PATTERN = Pattern.compile("(\".*\")|(0x[\\p{XDigit}]+)", + Pattern.DOTALL); private static final Pattern BSSID_PATTERN = Pattern.compile("([\\p{XDigit}]{2}:){5}[\\p{XDigit}]{2}"); diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 1a06e0a..3051926 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -269,6 +269,15 @@ public abstract class BatteryStats implements Parcelable { public abstract long getTotalTimeLocked(long elapsedRealtimeUs, int which); /** + * Returns the total time in microseconds associated with this Timer since the + * 'mark' was last set. + * + * @param elapsedRealtimeUs current elapsed realtime of system in microseconds + * @return a time in microseconds + */ + public abstract long getTimeSinceMarkLocked(long elapsedRealtimeUs); + + /** * Temporary for debugging. */ public abstract void logState(Printer pw, String prefix); @@ -332,7 +341,17 @@ public abstract class BatteryStats implements Parcelable { * @return a Map from Strings to Uid.Pkg objects. */ public abstract ArrayMap<String, ? extends Pkg> getPackageStats(); - + + /** + * Returns the time in milliseconds that this app kept the WiFi controller in the + * specified state <code>type</code>. + * @param type one of {@link #CONTROLLER_IDLE_TIME}, {@link #CONTROLLER_RX_TIME}, or + * {@link #CONTROLLER_TX_TIME}. + * @param which one of {@link #STATS_CURRENT}, {@link #STATS_SINCE_CHARGED}, or + * {@link #STATS_SINCE_UNPLUGGED}. + */ + public abstract long getWifiControllerActivity(int type, int which); + /** * {@hide} */ @@ -1914,7 +1933,6 @@ public abstract class BatteryStats implements Parcelable { public static final int NETWORK_MOBILE_TX_DATA = 1; public static final int NETWORK_WIFI_RX_DATA = 2; public static final int NETWORK_WIFI_TX_DATA = 3; - public static final int NUM_NETWORK_ACTIVITY_TYPES = NETWORK_WIFI_TX_DATA + 1; public abstract long getNetworkActivityBytes(int type, int which); @@ -1923,10 +1941,25 @@ public abstract class BatteryStats implements Parcelable { public static final int CONTROLLER_IDLE_TIME = 0; public static final int CONTROLLER_RX_TIME = 1; public static final int CONTROLLER_TX_TIME = 2; - public static final int CONTROLLER_ENERGY = 3; - public static final int NUM_CONTROLLER_ACTIVITY_TYPES = CONTROLLER_ENERGY + 1; + public static final int CONTROLLER_POWER_DRAIN = 3; + public static final int NUM_CONTROLLER_ACTIVITY_TYPES = CONTROLLER_POWER_DRAIN + 1; + /** + * For {@link #CONTROLLER_IDLE_TIME}, {@link #CONTROLLER_RX_TIME}, and + * {@link #CONTROLLER_TX_TIME}, returns the time spent (in milliseconds) in the + * respective state. + * For {@link #CONTROLLER_POWER_DRAIN}, returns the power used by the controller in + * milli-ampere-milliseconds (mAms). + */ public abstract long getBluetoothControllerActivity(int type, int which); + + /** + * For {@link #CONTROLLER_IDLE_TIME}, {@link #CONTROLLER_RX_TIME}, and + * {@link #CONTROLLER_TX_TIME}, returns the time spent (in milliseconds) in the + * respective state. + * For {@link #CONTROLLER_POWER_DRAIN}, returns the power used by the controller in + * milli-ampere-milliseconds (mAms). + */ public abstract long getWifiControllerActivity(int type, int which); /** @@ -2618,7 +2651,7 @@ public abstract class BatteryStats implements Parcelable { label = "???"; } dumpLine(pw, uid, category, POWER_USE_ITEM_DATA, label, - BatteryStatsHelper.makemAh(bs.value)); + BatteryStatsHelper.makemAh(bs.totalPowerMah)); } } @@ -3233,9 +3266,9 @@ public abstract class BatteryStats implements Parcelable { if (!didOne) sb.append(" (no activity)"); pw.println(sb.toString()); - final long wifiIdleTimeMs = getBluetoothControllerActivity(CONTROLLER_IDLE_TIME, which); - final long wifiRxTimeMs = getBluetoothControllerActivity(CONTROLLER_RX_TIME, which); - final long wifiTxTimeMs = getBluetoothControllerActivity(CONTROLLER_TX_TIME, which); + final long wifiIdleTimeMs = getWifiControllerActivity(CONTROLLER_IDLE_TIME, which); + final long wifiRxTimeMs = getWifiControllerActivity(CONTROLLER_RX_TIME, which); + final long wifiTxTimeMs = getWifiControllerActivity(CONTROLLER_TX_TIME, which); final long wifiTotalTimeMs = wifiIdleTimeMs + wifiRxTimeMs + wifiTxTimeMs; sb.setLength(0); @@ -3264,6 +3297,13 @@ public abstract class BatteryStats implements Parcelable { sb.setLength(0); sb.append(prefix); + sb.append(" WiFi Energy use: ").append(BatteryStatsHelper.makemAh( + getWifiControllerActivity(CONTROLLER_POWER_DRAIN, which) / (double)(1000*60*60))); + sb.append(" mAh"); + pw.println(sb.toString()); + + sb.setLength(0); + sb.append(prefix); sb.append(" Bluetooth on: "); formatTimeMs(sb, bluetoothOnTime / 1000); sb.append("("); sb.append(formatRatioLocked(bluetoothOnTime, whichBatteryRealtime)); sb.append(")"); @@ -3376,48 +3416,48 @@ public abstract class BatteryStats implements Parcelable { final BatterySipper bs = sippers.get(i); switch (bs.drainType) { case IDLE: - pw.print(prefix); pw.print(" Idle: "); printmAh(pw, bs.value); + pw.print(prefix); pw.print(" Idle: "); printmAh(pw, bs.totalPowerMah); pw.println(); break; case CELL: - pw.print(prefix); pw.print(" Cell standby: "); printmAh(pw, bs.value); + pw.print(prefix); pw.print(" Cell standby: "); printmAh(pw, bs.totalPowerMah); pw.println(); break; case PHONE: - pw.print(prefix); pw.print(" Phone calls: "); printmAh(pw, bs.value); + pw.print(prefix); pw.print(" Phone calls: "); printmAh(pw, bs.totalPowerMah); pw.println(); break; case WIFI: - pw.print(prefix); pw.print(" Wifi: "); printmAh(pw, bs.value); + pw.print(prefix); pw.print(" Wifi: "); printmAh(pw, bs.totalPowerMah); pw.println(); break; case BLUETOOTH: - pw.print(prefix); pw.print(" Bluetooth: "); printmAh(pw, bs.value); + pw.print(prefix); pw.print(" Bluetooth: "); printmAh(pw, bs.totalPowerMah); pw.println(); break; case SCREEN: - pw.print(prefix); pw.print(" Screen: "); printmAh(pw, bs.value); + pw.print(prefix); pw.print(" Screen: "); printmAh(pw, bs.totalPowerMah); pw.println(); break; case FLASHLIGHT: - pw.print(prefix); pw.print(" Flashlight: "); printmAh(pw, bs.value); + pw.print(prefix); pw.print(" Flashlight: "); printmAh(pw, bs.totalPowerMah); pw.println(); break; case APP: pw.print(prefix); pw.print(" Uid "); UserHandle.formatUid(pw, bs.uidObj.getUid()); - pw.print(": "); printmAh(pw, bs.value); pw.println(); + pw.print(": "); printmAh(pw, bs.totalPowerMah); pw.println(); break; case USER: pw.print(prefix); pw.print(" User "); pw.print(bs.userId); - pw.print(": "); printmAh(pw, bs.value); pw.println(); + pw.print(": "); printmAh(pw, bs.totalPowerMah); pw.println(); break; case UNACCOUNTED: - pw.print(prefix); pw.print(" Unaccounted: "); printmAh(pw, bs.value); + pw.print(prefix); pw.print(" Unaccounted: "); printmAh(pw, bs.totalPowerMah); pw.println(); break; case OVERCOUNTED: - pw.print(prefix); pw.print(" Over-counted: "); printmAh(pw, bs.value); + pw.print(prefix); pw.print(" Over-counted: "); printmAh(pw, bs.totalPowerMah); pw.println(); break; } @@ -4415,7 +4455,7 @@ public abstract class BatteryStats implements Parcelable { if (!checkin) { pw.println(header); } - String[] lineArgs = new String[4]; + String[] lineArgs = new String[5]; for (int i=0; i<count; i++) { long duration = steps.getDurationAt(i); int level = steps.getLevelAt(i); @@ -4430,7 +4470,7 @@ public abstract class BatteryStats implements Parcelable { case Display.STATE_ON: lineArgs[2] = "s+"; break; case Display.STATE_DOZE: lineArgs[2] = "sd"; break; case Display.STATE_DOZE_SUSPEND: lineArgs[2] = "sds"; break; - default: lineArgs[1] = "?"; break; + default: lineArgs[2] = "?"; break; } } else { lineArgs[2] = ""; @@ -4441,9 +4481,9 @@ public abstract class BatteryStats implements Parcelable { lineArgs[3] = ""; } if ((modMode&STEP_LEVEL_MODE_DEVICE_IDLE) == 0) { - lineArgs[3] = (initMode&STEP_LEVEL_MODE_DEVICE_IDLE) != 0 ? "i+" : "i-"; + lineArgs[4] = (initMode&STEP_LEVEL_MODE_DEVICE_IDLE) != 0 ? "i+" : "i-"; } else { - lineArgs[3] = ""; + lineArgs[4] = ""; } dumpLine(pw, 0 /* uid */, "i" /* category */, header, (Object[])lineArgs); } else { @@ -4459,7 +4499,7 @@ public abstract class BatteryStats implements Parcelable { case Display.STATE_ON: pw.print("screen-on"); break; case Display.STATE_DOZE: pw.print("screen-doze"); break; case Display.STATE_DOZE_SUSPEND: pw.print("screen-doze-suspend"); break; - default: lineArgs[1] = "screen-?"; break; + default: pw.print("screen-?"); break; } haveModes = true; } diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index 975bfc2..2eb97f1 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -18,16 +18,11 @@ package android.os; import android.app.admin.DevicePolicyManager; import android.content.Context; -import android.os.storage.IMountService; +import android.os.storage.StorageManager; import android.os.storage.StorageVolume; -import android.text.TextUtils; import android.util.Log; -import com.google.android.collect.Lists; - import java.io.File; -import java.io.IOException; -import java.util.ArrayList; /** * Provides access to environment variables. @@ -36,11 +31,9 @@ public class Environment { private static final String TAG = "Environment"; private static final String ENV_EXTERNAL_STORAGE = "EXTERNAL_STORAGE"; - private static final String ENV_EMULATED_STORAGE_SOURCE = "EMULATED_STORAGE_SOURCE"; - private static final String ENV_EMULATED_STORAGE_TARGET = "EMULATED_STORAGE_TARGET"; - private static final String ENV_MEDIA_STORAGE = "MEDIA_STORAGE"; - private static final String ENV_SECONDARY_STORAGE = "SECONDARY_STORAGE"; private static final String ENV_ANDROID_ROOT = "ANDROID_ROOT"; + private static final String ENV_ANDROID_DATA = "ANDROID_DATA"; + private static final String ENV_ANDROID_STORAGE = "ANDROID_STORAGE"; private static final String ENV_OEM_ROOT = "OEM_ROOT"; private static final String ENV_VENDOR_ROOT = "VENDOR_ROOT"; @@ -57,12 +50,10 @@ public class Environment { public static final String DIRECTORY_ANDROID = DIR_ANDROID; private static final File DIR_ANDROID_ROOT = getDirectory(ENV_ANDROID_ROOT, "/system"); + private static final File DIR_ANDROID_DATA = getDirectory(ENV_ANDROID_DATA, "/data"); + private static final File DIR_ANDROID_STORAGE = getDirectory(ENV_ANDROID_STORAGE, "/storage"); private static final File DIR_OEM_ROOT = getDirectory(ENV_OEM_ROOT, "/oem"); private static final File DIR_VENDOR_ROOT = getDirectory(ENV_VENDOR_ROOT, "/vendor"); - private static final File DIR_MEDIA_STORAGE = getDirectory(ENV_MEDIA_STORAGE, "/data/media"); - - private static final String CANONCIAL_EMULATED_STORAGE_TARGET = getCanonicalPathOrNull( - ENV_EMULATED_STORAGE_TARGET); private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled"; @@ -81,73 +72,24 @@ public class Environment { /** {@hide} */ public static class UserEnvironment { - // TODO: generalize further to create package-specific environment - - /** External storage dirs, as visible to vold */ - private final File[] mExternalDirsForVold; - /** External storage dirs, as visible to apps */ - private final File[] mExternalDirsForApp; - /** Primary emulated storage dir for direct access */ - private final File mEmulatedDirForDirect; + private final int mUserId; public UserEnvironment(int userId) { - // See storage config details at http://source.android.com/tech/storage/ - String rawExternalStorage = System.getenv(ENV_EXTERNAL_STORAGE); - String rawEmulatedSource = System.getenv(ENV_EMULATED_STORAGE_SOURCE); - String rawEmulatedTarget = System.getenv(ENV_EMULATED_STORAGE_TARGET); - - String rawMediaStorage = System.getenv(ENV_MEDIA_STORAGE); - if (TextUtils.isEmpty(rawMediaStorage)) { - rawMediaStorage = "/data/media"; - } - - ArrayList<File> externalForVold = Lists.newArrayList(); - ArrayList<File> externalForApp = Lists.newArrayList(); - - if (!TextUtils.isEmpty(rawEmulatedTarget)) { - // Device has emulated storage; external storage paths should have - // userId burned into them. - final String rawUserId = Integer.toString(userId); - final File emulatedSourceBase = new File(rawEmulatedSource); - final File emulatedTargetBase = new File(rawEmulatedTarget); - final File mediaBase = new File(rawMediaStorage); - - // /storage/emulated/0 - externalForVold.add(buildPath(emulatedSourceBase, rawUserId)); - externalForApp.add(buildPath(emulatedTargetBase, rawUserId)); - // /data/media/0 - mEmulatedDirForDirect = buildPath(mediaBase, rawUserId); - - } else { - // Device has physical external storage; use plain paths. - if (TextUtils.isEmpty(rawExternalStorage)) { - Log.w(TAG, "EXTERNAL_STORAGE undefined; falling back to default"); - rawExternalStorage = "/storage/sdcard0"; - } - - // /storage/sdcard0 - externalForVold.add(new File(rawExternalStorage)); - externalForApp.add(new File(rawExternalStorage)); - // /data/media - mEmulatedDirForDirect = new File(rawMediaStorage); - } + mUserId = userId; + } - // Splice in any secondary storage paths, but only for owner - final String rawSecondaryStorage = System.getenv(ENV_SECONDARY_STORAGE); - if (!TextUtils.isEmpty(rawSecondaryStorage) && userId == UserHandle.USER_OWNER) { - for (String secondaryPath : rawSecondaryStorage.split(":")) { - externalForVold.add(new File(secondaryPath)); - externalForApp.add(new File(secondaryPath)); - } + public File[] getExternalDirs() { + final StorageVolume[] volumes = StorageManager.getVolumeList(mUserId); + final File[] files = new File[volumes.length]; + for (int i = 0; i < volumes.length; i++) { + files[i] = volumes[i].getPathFile(); } - - mExternalDirsForVold = externalForVold.toArray(new File[externalForVold.size()]); - mExternalDirsForApp = externalForApp.toArray(new File[externalForApp.size()]); + return files; } @Deprecated public File getExternalStorageDirectory() { - return mExternalDirsForApp[0]; + return getExternalDirs()[0]; } @Deprecated @@ -155,60 +97,36 @@ public class Environment { return buildExternalStoragePublicDirs(type)[0]; } - public File[] getExternalDirsForVold() { - return mExternalDirsForVold; - } - - public File[] getExternalDirsForApp() { - return mExternalDirsForApp; - } - - public File getMediaDir() { - return mEmulatedDirForDirect; - } - public File[] buildExternalStoragePublicDirs(String type) { - return buildPaths(mExternalDirsForApp, type); + return buildPaths(getExternalDirs(), type); } public File[] buildExternalStorageAndroidDataDirs() { - return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA); + return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA); } public File[] buildExternalStorageAndroidObbDirs() { - return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_OBB); + return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_OBB); } public File[] buildExternalStorageAppDataDirs(String packageName) { - return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName); - } - - public File[] buildExternalStorageAppDataDirsForVold(String packageName) { - return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_DATA, packageName); + return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA, packageName); } public File[] buildExternalStorageAppMediaDirs(String packageName) { - return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_MEDIA, packageName); - } - - public File[] buildExternalStorageAppMediaDirsForVold(String packageName) { - return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_MEDIA, packageName); + return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_MEDIA, packageName); } public File[] buildExternalStorageAppObbDirs(String packageName) { - return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_OBB, packageName); - } - - public File[] buildExternalStorageAppObbDirsForVold(String packageName) { - return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_OBB, packageName); + return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_OBB, packageName); } public File[] buildExternalStorageAppFilesDirs(String packageName) { - return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName, DIR_FILES); + return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA, packageName, DIR_FILES); } public File[] buildExternalStorageAppCacheDirs(String packageName) { - return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName, DIR_CACHE); + return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA, packageName, DIR_CACHE); } } @@ -220,6 +138,11 @@ public class Environment { return DIR_ANDROID_ROOT; } + /** {@hide} */ + public static File getStorageDirectory() { + return DIR_ANDROID_STORAGE; + } + /** * Return root directory of the "oem" partition holding OEM customizations, * if any. If present, the partition is mounted read-only. @@ -270,17 +193,6 @@ public class Environment { } /** - * Return directory used for internal media storage, which is protected by - * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}. - * - * @hide - */ - public static File getMediaStorageDirectory() { - throwIfUserRequired(); - return sCurrentUser.getMediaDir(); - } - - /** * Return the system directory for a user. This is for use by system services to store * files relating to the user. This directory will be automatically deleted when the user * is removed. @@ -389,7 +301,7 @@ public class Environment { */ public static File getExternalStorageDirectory() { throwIfUserRequired(); - return sCurrentUser.getExternalDirsForApp()[0]; + return sCurrentUser.getExternalDirs()[0]; } /** {@hide} */ @@ -402,18 +314,6 @@ public class Environment { return buildPath(getLegacyExternalStorageDirectory(), DIR_ANDROID, DIR_OBB); } - /** {@hide} */ - public static File getEmulatedStorageSource(int userId) { - // /mnt/shell/emulated/0 - return new File(System.getenv(ENV_EMULATED_STORAGE_SOURCE), String.valueOf(userId)); - } - - /** {@hide} */ - public static File getEmulatedStorageObbSource() { - // /mnt/shell/emulated/obb - return new File(System.getenv(ENV_EMULATED_STORAGE_SOURCE), DIR_OBB); - } - /** * Standard directory in which to place any audio files that should be * in the regular list of music for the user. @@ -683,6 +583,13 @@ public class Environment { public static final String MEDIA_UNMOUNTABLE = "unmountable"; /** + * Storage state if the media is in the process of being ejected. + * + * @see #getExternalStorageState(File) + */ + public static final String MEDIA_EJECTING = "ejecting"; + + /** * Returns the current state of the primary "external" storage device. * * @see #getExternalStorageDirectory() @@ -693,7 +600,7 @@ public class Environment { * {@link #MEDIA_BAD_REMOVAL}, or {@link #MEDIA_UNMOUNTABLE}. */ public static String getExternalStorageState() { - final File externalDir = sCurrentUser.getExternalDirsForApp()[0]; + final File externalDir = sCurrentUser.getExternalDirs()[0]; return getExternalStorageState(externalDir); } @@ -716,17 +623,12 @@ public class Environment { * {@link #MEDIA_BAD_REMOVAL}, or {@link #MEDIA_UNMOUNTABLE}. */ public static String getExternalStorageState(File path) { - final StorageVolume volume = getStorageVolume(path); + final StorageVolume volume = StorageManager.getStorageVolume(path, UserHandle.myUserId()); if (volume != null) { - final IMountService mountService = IMountService.Stub.asInterface( - ServiceManager.getService("mount")); - try { - return mountService.getVolumeState(volume.getPath()); - } catch (RemoteException e) { - } + return volume.getState(); + } else { + return MEDIA_UNKNOWN; } - - return Environment.MEDIA_UNKNOWN; } /** @@ -738,7 +640,7 @@ public class Environment { */ public static boolean isExternalStorageRemovable() { if (isStorageDisabled()) return false; - final File externalDir = sCurrentUser.getExternalDirsForApp()[0]; + final File externalDir = sCurrentUser.getExternalDirs()[0]; return isExternalStorageRemovable(externalDir); } @@ -753,7 +655,7 @@ public class Environment { * device. */ public static boolean isExternalStorageRemovable(File path) { - final StorageVolume volume = getStorageVolume(path); + final StorageVolume volume = StorageManager.getStorageVolume(path, UserHandle.myUserId()); if (volume != null) { return volume.isRemovable(); } else { @@ -771,7 +673,7 @@ public class Environment { */ public static boolean isExternalStorageEmulated() { if (isStorageDisabled()) return false; - final File externalDir = sCurrentUser.getExternalDirsForApp()[0]; + final File externalDir = sCurrentUser.getExternalDirs()[0]; return isExternalStorageEmulated(externalDir); } @@ -784,7 +686,7 @@ public class Environment { * device. */ public static boolean isExternalStorageEmulated(File path) { - final StorageVolume volume = getStorageVolume(path); + final StorageVolume volume = StorageManager.getStorageVolume(path, UserHandle.myUserId()); if (volume != null) { return volume.isEmulated(); } else { @@ -797,19 +699,6 @@ public class Environment { return path == null ? new File(defaultPath) : new File(path); } - private static String getCanonicalPathOrNull(String variableName) { - String path = System.getenv(variableName); - if (path == null) { - return null; - } - try { - return new File(path).getCanonicalPath(); - } catch (IOException e) { - Log.w(TAG, "Unable to resolve canonical path for " + path); - return null; - } - } - /** {@hide} */ public static void setUserRequired(boolean userRequired) { sUserRequired = userRequired; @@ -856,28 +745,6 @@ public class Environment { return SystemProperties.getBoolean("config.disable_storage", false); } - private static StorageVolume getStorageVolume(File path) { - try { - path = path.getCanonicalFile(); - } catch (IOException e) { - return null; - } - - try { - final IMountService mountService = IMountService.Stub.asInterface( - ServiceManager.getService("mount")); - final StorageVolume[] volumes = mountService.getVolumeList(); - for (StorageVolume volume : volumes) { - if (FileUtils.contains(volume.getPathFile(), path)) { - return volume; - } - } - } catch (RemoteException e) { - } - - return null; - } - /** * If the given path exists on emulated external storage, return the * translated backing path hosted on internal storage. This bypasses any @@ -891,26 +758,7 @@ public class Environment { * @hide */ public static File maybeTranslateEmulatedPathToInternal(File path) { - // Fast return if not emulated, or missing variables - if (!Environment.isExternalStorageEmulated() - || CANONCIAL_EMULATED_STORAGE_TARGET == null) { - return path; - } - - try { - final String rawPath = path.getCanonicalPath(); - if (rawPath.startsWith(CANONCIAL_EMULATED_STORAGE_TARGET)) { - final File internalPath = new File(DIR_MEDIA_STORAGE, - rawPath.substring(CANONCIAL_EMULATED_STORAGE_TARGET.length())); - if (internalPath.exists()) { - return internalPath; - } - } - } catch (IOException e) { - Log.w(TAG, "Failed to resolve canonical path for " + path); - } - - // Unable to translate to internal path; use original + // TODO: bring back this optimization return path; } } diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index 0a724a1..b302f95 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -369,6 +369,23 @@ public class FileUtils { * {@link File#getCanonicalFile()} to avoid symlink or path traversal * attacks. */ + public static boolean contains(File[] dirs, File file) { + for (File dir : dirs) { + if (contains(dir, file)) { + return true; + } + } + return false; + } + + /** + * Test if a file lives under the given directory, either as a direct child + * or a distant grandchild. + * <p> + * Both files <em>must</em> have been resolved using + * {@link File#getCanonicalFile()} to avoid symlink or path traversal + * attacks. + */ public static boolean contains(File dir, File file) { if (file == null) return false; diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 0de9c70..355ec8c 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -637,10 +637,8 @@ public class Process { if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) { argsForZygote.add("--enable-assert"); } - if (mountExternal == Zygote.MOUNT_EXTERNAL_MULTIUSER) { - argsForZygote.add("--mount-external-multiuser"); - } else if (mountExternal == Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL) { - argsForZygote.add("--mount-external-multiuser-all"); + if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) { + argsForZygote.add("--mount-external-default"); } argsForZygote.add("--target-sdk-version=" + targetSdkVersion); diff --git a/core/java/android/os/storage/DiskInfo.aidl b/core/java/android/os/storage/DiskInfo.aidl new file mode 100644 index 0000000..5126c19 --- /dev/null +++ b/core/java/android/os/storage/DiskInfo.aidl @@ -0,0 +1,19 @@ +/* + * 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.os.storage; + +parcelable DiskInfo; diff --git a/core/java/android/os/storage/DiskInfo.java b/core/java/android/os/storage/DiskInfo.java new file mode 100644 index 0000000..56c55f1 --- /dev/null +++ b/core/java/android/os/storage/DiskInfo.java @@ -0,0 +1,127 @@ +/* + * 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.os.storage; + +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.DebugUtils; + +import com.android.internal.util.IndentingPrintWriter; +import com.android.internal.util.Preconditions; + +import java.io.CharArrayWriter; + +/** + * Information about a physical disk which may contain one or more + * {@link VolumeInfo}. + * + * @hide + */ +public class DiskInfo implements Parcelable { + public static final int FLAG_ADOPTABLE = 1 << 0; + public static final int FLAG_DEFAULT_PRIMARY = 1 << 1; + public static final int FLAG_SD = 1 << 2; + public static final int FLAG_USB = 1 << 3; + + public final String id; + public final int flags; + public long size; + public String label; + public String[] volumes; + + public DiskInfo(String id, int flags) { + this.id = Preconditions.checkNotNull(id); + this.flags = flags; + } + + public DiskInfo(Parcel parcel) { + id = parcel.readString(); + flags = parcel.readInt(); + size = parcel.readLong(); + label = parcel.readString(); + volumes = parcel.readStringArray(); + } + + public String getDescription(Context context) { + // TODO: splice vendor label into these strings + if ((flags & FLAG_SD) != 0) { + return context.getString(com.android.internal.R.string.storage_sd_card); + } else if ((flags & FLAG_USB) != 0) { + return context.getString(com.android.internal.R.string.storage_usb); + } else { + return null; + } + } + + @Override + public String toString() { + final CharArrayWriter writer = new CharArrayWriter(); + dump(new IndentingPrintWriter(writer, " ", 80)); + return writer.toString(); + } + + public void dump(IndentingPrintWriter pw) { + pw.println("DiskInfo:"); + pw.increaseIndent(); + pw.printPair("id", id); + pw.printPair("flags", DebugUtils.flagsToString(getClass(), "FLAG_", flags)); + pw.printPair("size", size); + pw.printPair("label", label); + pw.printPair("volumes", volumes); + pw.decreaseIndent(); + pw.println(); + } + + @Override + public DiskInfo clone() { + final Parcel temp = Parcel.obtain(); + try { + writeToParcel(temp, 0); + temp.setDataPosition(0); + return CREATOR.createFromParcel(temp); + } finally { + temp.recycle(); + } + } + + public static final Creator<DiskInfo> CREATOR = new Creator<DiskInfo>() { + @Override + public DiskInfo createFromParcel(Parcel in) { + return new DiskInfo(in); + } + + @Override + public DiskInfo[] newArray(int size) { + return new DiskInfo[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeString(id); + parcel.writeInt(flags); + parcel.writeLong(size); + parcel.writeString(label); + parcel.writeStringArray(volumes); + } +} diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java index 6209c2a..10ffd48 100644 --- a/core/java/android/os/storage/IMountService.java +++ b/core/java/android/os/storage/IMountService.java @@ -757,12 +757,13 @@ public interface IMountService extends IInterface { return _result; } - public StorageVolume[] getVolumeList() throws RemoteException { + public StorageVolume[] getVolumeList(int userId) throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); StorageVolume[] _result; try { _data.writeInterfaceToken(DESCRIPTOR); + _data.writeInt(userId); mRemote.transact(Stub.TRANSACTION_getVolumeList, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArray(StorageVolume.CREATOR); @@ -903,6 +904,131 @@ public interface IMountService extends IInterface { } return; } + + @Override + public DiskInfo[] getDisks() throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + DiskInfo[] _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + mRemote.transact(Stub.TRANSACTION_getDisks, _data, _reply, 0); + _reply.readException(); + _result = _reply.createTypedArray(DiskInfo.CREATOR); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } + + @Override + public VolumeInfo[] getVolumes() throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + VolumeInfo[] _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + mRemote.transact(Stub.TRANSACTION_getVolumes, _data, _reply, 0); + _reply.readException(); + _result = _reply.createTypedArray(VolumeInfo.CREATOR); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } + + @Override + public void mount(String volId) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(volId); + mRemote.transact(Stub.TRANSACTION_mount, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + + @Override + public void unmount(String volId) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(volId); + mRemote.transact(Stub.TRANSACTION_unmount, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + + @Override + public void format(String volId) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(volId); + mRemote.transact(Stub.TRANSACTION_format, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + + @Override + public void partitionPublic(String diskId) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(diskId); + mRemote.transact(Stub.TRANSACTION_partitionPublic, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + + @Override + public void partitionPrivate(String diskId) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(diskId); + mRemote.transact(Stub.TRANSACTION_partitionPrivate, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + + @Override + public void partitionMixed(String diskId, int ratio) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(diskId); + _data.writeInt(ratio); + mRemote.transact(Stub.TRANSACTION_partitionMixed, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } } private static final String DESCRIPTOR = "IMountService"; @@ -995,6 +1121,17 @@ public interface IMountService extends IInterface { static final int TRANSACTION_waitForAsecScan = IBinder.FIRST_CALL_TRANSACTION + 43; + static final int TRANSACTION_getDisks = IBinder.FIRST_CALL_TRANSACTION + 44; + static final int TRANSACTION_getVolumes = IBinder.FIRST_CALL_TRANSACTION + 45; + + static final int TRANSACTION_mount = IBinder.FIRST_CALL_TRANSACTION + 46; + static final int TRANSACTION_unmount = IBinder.FIRST_CALL_TRANSACTION + 47; + static final int TRANSACTION_format = IBinder.FIRST_CALL_TRANSACTION + 48; + + static final int TRANSACTION_partitionPublic = IBinder.FIRST_CALL_TRANSACTION + 49; + static final int TRANSACTION_partitionPrivate = IBinder.FIRST_CALL_TRANSACTION + 50; + static final int TRANSACTION_partitionMixed = IBinder.FIRST_CALL_TRANSACTION + 51; + /** * Cast an IBinder object into an IMountService interface, generating a * proxy if needed. @@ -1308,7 +1445,8 @@ public interface IMountService extends IInterface { } case TRANSACTION_getVolumeList: { data.enforceInterface(DESCRIPTOR); - StorageVolume[] result = getVolumeList(); + int userId = data.readInt(); + StorageVolume[] result = getVolumeList(userId); reply.writeNoException(); reply.writeTypedArray(result, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); return true; @@ -1419,6 +1557,63 @@ public interface IMountService extends IInterface { reply.writeNoException(); return true; } + case TRANSACTION_getDisks: { + data.enforceInterface(DESCRIPTOR); + DiskInfo[] disks = getDisks(); + reply.writeNoException(); + reply.writeTypedArray(disks, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + return true; + } + case TRANSACTION_getVolumes: { + data.enforceInterface(DESCRIPTOR); + VolumeInfo[] volumes = getVolumes(); + reply.writeNoException(); + reply.writeTypedArray(volumes, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + return true; + } + case TRANSACTION_mount: { + data.enforceInterface(DESCRIPTOR); + String volId = data.readString(); + mount(volId); + reply.writeNoException(); + return true; + } + case TRANSACTION_unmount: { + data.enforceInterface(DESCRIPTOR); + String volId = data.readString(); + unmount(volId); + reply.writeNoException(); + return true; + } + case TRANSACTION_format: { + data.enforceInterface(DESCRIPTOR); + String volId = data.readString(); + format(volId); + reply.writeNoException(); + return true; + } + case TRANSACTION_partitionPublic: { + data.enforceInterface(DESCRIPTOR); + String diskId = data.readString(); + partitionPublic(diskId); + reply.writeNoException(); + return true; + } + case TRANSACTION_partitionPrivate: { + data.enforceInterface(DESCRIPTOR); + String diskId = data.readString(); + partitionPrivate(diskId); + reply.writeNoException(); + return true; + } + case TRANSACTION_partitionMixed: { + data.enforceInterface(DESCRIPTOR); + String diskId = data.readString(); + int ratio = data.readInt(); + partitionMixed(diskId, ratio); + reply.writeNoException(); + return true; + } } return super.onTransact(code, data, reply, flags); } @@ -1630,7 +1825,7 @@ public interface IMountService extends IInterface { /** * Returns list of all mountable volumes. */ - public StorageVolume[] getVolumeList() throws RemoteException; + public StorageVolume[] getVolumeList(int userId) throws RemoteException; /** * Gets the path on the filesystem for the ASEC container itself. @@ -1705,4 +1900,15 @@ public interface IMountService extends IInterface { public void runMaintenance() throws RemoteException; public void waitForAsecScan() throws RemoteException; + + public DiskInfo[] getDisks() throws RemoteException; + public VolumeInfo[] getVolumes() throws RemoteException; + + public void mount(String volId) throws RemoteException; + public void unmount(String volId) throws RemoteException; + public void format(String volId) throws RemoteException; + + public void partitionPublic(String diskId) throws RemoteException; + public void partitionPrivate(String diskId) throws RemoteException; + public void partitionMixed(String diskId, int ratio) throws RemoteException; } diff --git a/core/java/android/os/storage/IMountServiceListener.java b/core/java/android/os/storage/IMountServiceListener.java index d5c5fa5..3965f9d 100644 --- a/core/java/android/os/storage/IMountServiceListener.java +++ b/core/java/android/os/storage/IMountServiceListener.java @@ -75,16 +75,22 @@ public interface IMountServiceListener extends IInterface { } case TRANSACTION_onStorageStateChanged: { data.enforceInterface(DESCRIPTOR); - String path; - path = data.readString(); - String oldState; - oldState = data.readString(); - String newState; - newState = data.readString(); + final String path = data.readString(); + final String oldState = data.readString(); + final String newState = data.readString(); this.onStorageStateChanged(path, oldState, newState); reply.writeNoException(); return true; } + case TRANSACTION_onVolumeStateChanged: { + data.enforceInterface(DESCRIPTOR); + final VolumeInfo vol = (VolumeInfo) data.readParcelable(null); + final int oldState = data.readInt(); + final int newState = data.readInt(); + onVolumeStateChanged(vol, oldState, newState); + reply.writeNoException(); + return true; + } } return super.onTransact(code, data, reply, flags); } @@ -116,7 +122,7 @@ public interface IMountServiceListener extends IInterface { _data.writeInterfaceToken(DESCRIPTOR); _data.writeInt(((connected) ? (1) : (0))); mRemote.transact(Stub.TRANSACTION_onUsbMassStorageConnectionChanged, _data, - _reply, 0); + _reply, android.os.IBinder.FLAG_ONEWAY); _reply.readException(); } finally { _reply.recycle(); @@ -142,7 +148,27 @@ public interface IMountServiceListener extends IInterface { _data.writeString(path); _data.writeString(oldState); _data.writeString(newState); - mRemote.transact(Stub.TRANSACTION_onStorageStateChanged, _data, _reply, 0); + mRemote.transact(Stub.TRANSACTION_onStorageStateChanged, _data, _reply, + android.os.IBinder.FLAG_ONEWAY); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + + @Override + public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) + throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeParcelable(vol, 0); + _data.writeInt(oldState); + _data.writeInt(newState); + mRemote.transact(Stub.TRANSACTION_onVolumeStateChanged, _data, _reply, + android.os.IBinder.FLAG_ONEWAY); _reply.readException(); } finally { _reply.recycle(); @@ -154,6 +180,8 @@ 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); } /** @@ -173,4 +201,7 @@ public interface IMountServiceListener extends IInterface { */ public void onStorageStateChanged(String path, String oldState, String newState) throws RemoteException; + + public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) + throws RemoteException; } diff --git a/core/java/android/os/storage/StorageEventListener.java b/core/java/android/os/storage/StorageEventListener.java index 6c73d04..29d5494 100644 --- a/core/java/android/os/storage/StorageEventListener.java +++ b/core/java/android/os/storage/StorageEventListener.java @@ -21,7 +21,7 @@ package android.os.storage; * * @hide */ -public abstract class StorageEventListener { +public class StorageEventListener { /** * Called when the detection state of a USB Mass Storage host has changed. * @param connected true if the USB mass storage is connected. @@ -37,4 +37,7 @@ public abstract class StorageEventListener { */ public void onStorageStateChanged(String path, String oldState, String newState) { } + + public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { + } } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 2785ee8..5a5d8b7 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -18,26 +18,32 @@ package android.os.storage; import static android.net.TrafficStats.MB_IN_BYTES; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.ContentResolver; import android.content.Context; import android.os.Environment; +import android.os.FileUtils; import android.os.Handler; import android.os.Looper; import android.os.Message; -import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; import android.provider.Settings; import android.util.Log; import android.util.SparseArray; +import com.android.internal.os.SomeArgs; import com.android.internal.util.Preconditions; import java.io.File; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; import java.util.List; +import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; /** @@ -60,46 +66,70 @@ import java.util.concurrent.atomic.AtomicInteger; public class StorageManager { private static final String TAG = "StorageManager"; + /** {@hide} */ + public static final String PROP_PRIMARY_PHYSICAL = "ro.vold.primary_physical"; + + private final Context mContext; private final ContentResolver mResolver; - /* - * Our internal MountService binder reference - */ private final IMountService mMountService; + private final Looper mLooper; + private final AtomicInteger mNextNonce = new AtomicInteger(0); - /* - * The looper target for callbacks - */ - private final Looper mTgtLooper; + private final ArrayList<StorageEventListenerDelegate> mDelegates = new ArrayList<>(); - /* - * Target listener for binder callbacks - */ - private MountServiceBinderListener mBinderListener; + private static class StorageEventListenerDelegate extends IMountServiceListener.Stub implements + Handler.Callback { + private static final int MSG_STORAGE_STATE_CHANGED = 1; + private static final int MSG_VOLUME_STATE_CHANGED = 2; - /* - * List of our listeners - */ - private List<ListenerDelegate> mListeners = new ArrayList<ListenerDelegate>(); + final StorageEventListener mCallback; + final Handler mHandler; - /* - * Next available nonce - */ - final private AtomicInteger mNextNonce = new AtomicInteger(0); + public StorageEventListenerDelegate(StorageEventListener callback, Looper looper) { + mCallback = callback; + mHandler = new Handler(looper, this); + } - private class MountServiceBinderListener extends IMountServiceListener.Stub { - public void onUsbMassStorageConnectionChanged(boolean available) { - final int size = mListeners.size(); - for (int i = 0; i < size; i++) { - mListeners.get(i).sendShareAvailabilityChanged(available); + @Override + public boolean handleMessage(Message msg) { + final SomeArgs args = (SomeArgs) msg.obj; + switch (msg.what) { + case MSG_STORAGE_STATE_CHANGED: + mCallback.onStorageStateChanged((String) args.arg1, (String) args.arg2, + (String) args.arg3); + args.recycle(); + return true; + case MSG_VOLUME_STATE_CHANGED: + mCallback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3); + args.recycle(); + return true; } + args.recycle(); + return false; + } + + @Override + public void onUsbMassStorageConnectionChanged(boolean connected) throws RemoteException { + // Ignored } + @Override public void onStorageStateChanged(String path, String oldState, String newState) { - final int size = mListeners.size(); - for (int i = 0; i < size; i++) { - mListeners.get(i).sendStorageStateChanged(path, oldState, newState); - } + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = path; + args.arg2 = oldState; + args.arg3 = newState; + mHandler.obtainMessage(MSG_STORAGE_STATE_CHANGED, args).sendToTarget(); + } + + @Override + public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = vol; + args.argi2 = oldState; + args.argi3 = newState; + mHandler.obtainMessage(MSG_VOLUME_STATE_CHANGED, args).sendToTarget(); } } @@ -154,7 +184,7 @@ public class StorageManager { ObbListenerDelegate(OnObbStateChangeListener listener) { nonce = getNextNonce(); mObbEventListenerRef = new WeakReference<OnObbStateChangeListener>(listener); - mHandler = new Handler(mTgtLooper) { + mHandler = new Handler(mLooper) { @Override public void handleMessage(Message msg) { final OnObbStateChangeListener changeListener = getListener(); @@ -162,14 +192,7 @@ public class StorageManager { return; } - StorageEvent e = (StorageEvent) msg.obj; - - if (msg.what == StorageEvent.EVENT_OBB_STATE_CHANGED) { - ObbStateChangedStorageEvent ev = (ObbStateChangedStorageEvent) e; - changeListener.onObbStateChange(ev.path, ev.state); - } else { - Log.e(TAG, "Unsupported event " + msg.what); - } + changeListener.onObbStateChange((String) msg.obj, msg.arg1); } }; } @@ -182,115 +205,7 @@ public class StorageManager { } void sendObbStateChanged(String path, int state) { - ObbStateChangedStorageEvent e = new ObbStateChangedStorageEvent(path, state); - mHandler.sendMessage(e.getMessage()); - } - } - - /** - * Message sent during an OBB status change event. - */ - private class ObbStateChangedStorageEvent extends StorageEvent { - public final String path; - - public final int state; - - public ObbStateChangedStorageEvent(String path, int state) { - super(EVENT_OBB_STATE_CHANGED); - this.path = path; - this.state = state; - } - } - - /** - * Private base class for messages sent between the callback thread - * and the target looper handler. - */ - private class StorageEvent { - static final int EVENT_UMS_CONNECTION_CHANGED = 1; - static final int EVENT_STORAGE_STATE_CHANGED = 2; - static final int EVENT_OBB_STATE_CHANGED = 3; - - private Message mMessage; - - public StorageEvent(int what) { - mMessage = Message.obtain(); - mMessage.what = what; - mMessage.obj = this; - } - - public Message getMessage() { - return mMessage; - } - } - - /** - * Message sent on a USB mass storage connection change. - */ - private class UmsConnectionChangedStorageEvent extends StorageEvent { - public boolean available; - - public UmsConnectionChangedStorageEvent(boolean a) { - super(EVENT_UMS_CONNECTION_CHANGED); - available = a; - } - } - - /** - * Message sent on volume state change. - */ - private class StorageStateChangedStorageEvent extends StorageEvent { - public String path; - public String oldState; - public String newState; - - public StorageStateChangedStorageEvent(String p, String oldS, String newS) { - super(EVENT_STORAGE_STATE_CHANGED); - path = p; - oldState = oldS; - newState = newS; - } - } - - /** - * Private class containing sender and receiver code for StorageEvents. - */ - private class ListenerDelegate { - final StorageEventListener mStorageEventListener; - private final Handler mHandler; - - ListenerDelegate(StorageEventListener listener) { - mStorageEventListener = listener; - mHandler = new Handler(mTgtLooper) { - @Override - public void handleMessage(Message msg) { - StorageEvent e = (StorageEvent) msg.obj; - - if (msg.what == StorageEvent.EVENT_UMS_CONNECTION_CHANGED) { - UmsConnectionChangedStorageEvent ev = (UmsConnectionChangedStorageEvent) e; - mStorageEventListener.onUsbMassStorageConnectionChanged(ev.available); - } else if (msg.what == StorageEvent.EVENT_STORAGE_STATE_CHANGED) { - StorageStateChangedStorageEvent ev = (StorageStateChangedStorageEvent) e; - mStorageEventListener.onStorageStateChanged(ev.path, ev.oldState, ev.newState); - } else { - Log.e(TAG, "Unsupported event " + msg.what); - } - } - }; - } - - StorageEventListener getListener() { - return mStorageEventListener; - } - - void sendShareAvailabilityChanged(boolean available) { - UmsConnectionChangedStorageEvent e = new UmsConnectionChangedStorageEvent(available); - mHandler.sendMessage(e.getMessage()); - } - - void sendStorageStateChanged(String path, String oldState, String newState) { - StorageStateChangedStorageEvent e = new StorageStateChangedStorageEvent(path, oldState, newState); - mHandler.sendMessage(e.getMessage()); + mHandler.obtainMessage(0, state, 0, path).sendToTarget(); } } @@ -311,13 +226,13 @@ public class StorageManager { * * @hide */ - public StorageManager(ContentResolver resolver, Looper tgtLooper) throws RemoteException { - mResolver = resolver; - mTgtLooper = tgtLooper; + public StorageManager(Context context, Looper looper) { + mContext = context; + mResolver = context.getContentResolver(); + mLooper = looper; mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount")); if (mMountService == null) { - Log.e(TAG, "Unable to connect to mount service! - is it running yet?"); - return; + throw new IllegalStateException("Failed to find running mount service"); } } @@ -329,21 +244,15 @@ public class StorageManager { * @hide */ public void registerListener(StorageEventListener listener) { - if (listener == null) { - return; - } - - synchronized (mListeners) { - if (mBinderListener == null ) { - try { - mBinderListener = new MountServiceBinderListener(); - mMountService.registerListener(mBinderListener); - } catch (RemoteException rex) { - Log.e(TAG, "Register mBinderListener failed"); - return; - } + synchronized (mDelegates) { + final StorageEventListenerDelegate delegate = new StorageEventListenerDelegate(listener, + mLooper); + try { + mMountService.registerListener(delegate); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); } - mListeners.add(new ListenerDelegate(listener)); + mDelegates.add(delegate); } } @@ -355,28 +264,19 @@ public class StorageManager { * @hide */ public void unregisterListener(StorageEventListener listener) { - if (listener == null) { - return; - } - - synchronized (mListeners) { - final int size = mListeners.size(); - for (int i=0 ; i<size ; i++) { - ListenerDelegate l = mListeners.get(i); - if (l.getListener() == listener) { - mListeners.remove(i); - break; - } - } - if (mListeners.size() == 0 && mBinderListener != null) { - try { - mMountService.unregisterListener(mBinderListener); - } catch (RemoteException rex) { - Log.e(TAG, "Unregister mBinderListener failed"); - return; + synchronized (mDelegates) { + for (Iterator<StorageEventListenerDelegate> i = mDelegates.iterator(); i.hasNext();) { + final StorageEventListenerDelegate delegate = i.next(); + if (delegate.mCallback == listener) { + try { + mMountService.unregisterListener(delegate); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + i.remove(); } } - } + } } /** @@ -384,12 +284,8 @@ public class StorageManager { * * @hide */ + @Deprecated public void enableUsbMassStorage() { - try { - mMountService.setUsbMassStorageEnabled(true); - } catch (Exception ex) { - Log.e(TAG, "Failed to enable UMS", ex); - } } /** @@ -397,12 +293,8 @@ public class StorageManager { * * @hide */ + @Deprecated public void disableUsbMassStorage() { - try { - mMountService.setUsbMassStorageEnabled(false); - } catch (Exception ex) { - Log.e(TAG, "Failed to disable UMS", ex); - } } /** @@ -411,12 +303,8 @@ public class StorageManager { * * @hide */ + @Deprecated public boolean isUsbMassStorageConnected() { - try { - return mMountService.isUsbMassStorageConnected(); - } catch (Exception ex) { - Log.e(TAG, "Failed to get UMS connection state", ex); - } return false; } @@ -426,12 +314,8 @@ public class StorageManager { * * @hide */ + @Deprecated public boolean isUsbMassStorageEnabled() { - try { - return mMountService.isUsbMassStorageEnabled(); - } catch (RemoteException rex) { - Log.e(TAG, "Failed to get UMS enable state", rex); - } return false; } @@ -548,38 +432,170 @@ public class StorageManager { return null; } - /** - * Gets the state of a volume via its mountpoint. - * @hide - */ - public String getVolumeState(String mountPoint) { - if (mMountService == null) return Environment.MEDIA_REMOVED; + /** {@hide} */ + public @NonNull List<DiskInfo> getDisks() { try { - return mMountService.getVolumeState(mountPoint); + return Arrays.asList(mMountService.getDisks()); } catch (RemoteException e) { - Log.e(TAG, "Failed to get volume state", e); - return null; + throw e.rethrowAsRuntimeException(); } } + /** {@hide} */ + public @Nullable DiskInfo findDiskById(String id) { + Preconditions.checkNotNull(id); + // TODO; go directly to service to make this faster + for (DiskInfo disk : getDisks()) { + if (Objects.equals(disk.id, id)) { + return disk; + } + } + return null; + } + + /** {@hide} */ + public @Nullable VolumeInfo findVolumeById(String id) { + Preconditions.checkNotNull(id); + // TODO; go directly to service to make this faster + for (VolumeInfo vol : getVolumes()) { + if (Objects.equals(vol.id, id)) { + return vol; + } + } + return null; + } + + /** {@hide} */ + public @Nullable VolumeInfo findVolumeByUuid(String fsUuid) { + Preconditions.checkNotNull(fsUuid); + // TODO; go directly to service to make this faster + for (VolumeInfo vol : getVolumes()) { + if (Objects.equals(vol.fsUuid, fsUuid)) { + return vol; + } + } + return null; + } + + /** {@hide} */ + public @NonNull List<VolumeInfo> getVolumes() { + try { + return Arrays.asList(mMountService.getVolumes()); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** {@hide} */ + public void mount(String volId) { + try { + mMountService.mount(volId); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** {@hide} */ + public void unmount(String volId) { + try { + mMountService.unmount(volId); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** {@hide} */ + public void format(String volId) { + try { + mMountService.format(volId); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** {@hide} */ + public void partitionPublic(String diskId) { + try { + mMountService.partitionPublic(diskId); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** {@hide} */ + public void partitionPrivate(String diskId) { + try { + mMountService.partitionPrivate(diskId); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** {@hide} */ + public void partitionMixed(String diskId, int ratio) { + try { + mMountService.partitionMixed(diskId, ratio); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** {@hide} */ + public @Nullable StorageVolume getStorageVolume(File file) { + return getStorageVolume(getVolumeList(), file); + } + + /** {@hide} */ + public static @Nullable StorageVolume getStorageVolume(File file, int userId) { + return getStorageVolume(getVolumeList(userId), file); + } + + /** {@hide} */ + private static @Nullable StorageVolume getStorageVolume(StorageVolume[] volumes, File file) { + File canonicalFile = null; + try { + canonicalFile = file.getCanonicalFile(); + } catch (IOException ignored) { + canonicalFile = null; + } + for (StorageVolume volume : volumes) { + if (volume.getPathFile().equals(file)) { + return volume; + } + if (FileUtils.contains(volume.getPathFile(), canonicalFile)) { + return volume; + } + } + return null; + } + /** - * Returns list of all mountable volumes. + * Gets the state of a volume via its mountpoint. * @hide */ - public StorageVolume[] getVolumeList() { - if (mMountService == null) return new StorageVolume[0]; + @Deprecated + public @NonNull String getVolumeState(String mountPoint) { + final StorageVolume vol = getStorageVolume(new File(mountPoint)); + if (vol != null) { + return vol.getState(); + } else { + return Environment.MEDIA_UNKNOWN; + } + } + + /** {@hide} */ + public @NonNull StorageVolume[] getVolumeList() { + return getVolumeList(mContext.getUserId()); + } + + /** {@hide} */ + public static @NonNull StorageVolume[] getVolumeList(int userId) { + final IMountService mountService = IMountService.Stub.asInterface( + ServiceManager.getService("mount")); try { - Parcelable[] list = mMountService.getVolumeList(); - if (list == null) return new StorageVolume[0]; - int length = list.length; - StorageVolume[] result = new StorageVolume[length]; - for (int i = 0; i < length; i++) { - result[i] = (StorageVolume)list[i]; - } - return result; + return mountService.getVolumeList(userId); } catch (RemoteException e) { - Log.e(TAG, "Failed to get volume list", e); - return null; + throw e.rethrowAsRuntimeException(); } } @@ -587,9 +603,9 @@ public class StorageManager { * Returns list of paths for all mountable volumes. * @hide */ - public String[] getVolumePaths() { + @Deprecated + public @NonNull String[] getVolumePaths() { StorageVolume[] volumes = getVolumeList(); - if (volumes == null) return null; int count = volumes.length; String[] paths = new String[count]; for (int i = 0; i < count; i++) { @@ -599,21 +615,21 @@ public class StorageManager { } /** {@hide} */ - public StorageVolume getPrimaryVolume() { + public @NonNull StorageVolume getPrimaryVolume() { return getPrimaryVolume(getVolumeList()); } /** {@hide} */ - public static StorageVolume getPrimaryVolume(StorageVolume[] volumes) { + public static @NonNull StorageVolume getPrimaryVolume(StorageVolume[] volumes) { for (StorageVolume volume : volumes) { if (volume.isPrimary()) { return volume; } } - Log.w(TAG, "No primary storage defined"); - return null; + throw new IllegalStateException("Missing primary storage"); } + /** {@hide} */ private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10; private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500 * MB_IN_BYTES; private static final long DEFAULT_FULL_THRESHOLD_BYTES = MB_IN_BYTES; diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java index 06565f1..d66e228 100644 --- a/core/java/android/os/storage/StorageVolume.java +++ b/core/java/android/os/storage/StorageVolume.java @@ -17,81 +17,83 @@ package android.os.storage; import android.content.Context; +import android.net.TrafficStats; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; import com.android.internal.util.IndentingPrintWriter; +import com.android.internal.util.Preconditions; import java.io.CharArrayWriter; import java.io.File; /** - * Description of a storage volume and its capabilities, including the - * filesystem path where it may be mounted. + * Information about a storage volume that may be mounted. This is a legacy + * specialization of {@link VolumeInfo} which describes the volume for a + * specific user. + * <p> + * This class may be deprecated in the future. * * @hide */ public class StorageVolume implements Parcelable { - // TODO: switch to more durable token - private int mStorageId; - + private final String mId; + private final int mStorageId; private final File mPath; - private final int mDescriptionId; + private final String mDescription; private final boolean mPrimary; private final boolean mRemovable; private final boolean mEmulated; - private final int mMtpReserveSpace; + private final long mMtpReserveSize; private final boolean mAllowMassStorage; - /** Maximum file size for the storage, or zero for no limit */ private final long mMaxFileSize; - /** When set, indicates exclusive ownership of this volume */ private final UserHandle mOwner; - - private String mUuid; - private String mUserLabel; - private String mState; + private final String mFsUuid; + private final String mState; // StorageVolume extra for ACTION_MEDIA_REMOVED, ACTION_MEDIA_UNMOUNTED, ACTION_MEDIA_CHECKING, // ACTION_MEDIA_NOFS, ACTION_MEDIA_MOUNTED, ACTION_MEDIA_SHARED, ACTION_MEDIA_UNSHARED, // ACTION_MEDIA_BAD_REMOVAL, ACTION_MEDIA_UNMOUNTABLE and ACTION_MEDIA_EJECT broadcasts. public static final String EXTRA_STORAGE_VOLUME = "storage_volume"; - public StorageVolume(File path, int descriptionId, boolean primary, boolean removable, - boolean emulated, int mtpReserveSpace, boolean allowMassStorage, long maxFileSize, - UserHandle owner) { - mPath = path; - mDescriptionId = descriptionId; + public StorageVolume(String id, int storageId, File path, String description, boolean primary, + boolean removable, boolean emulated, long mtpReserveSize, boolean allowMassStorage, + long maxFileSize, UserHandle owner, String fsUuid, String state) { + mId = Preconditions.checkNotNull(id); + mStorageId = storageId; + mPath = Preconditions.checkNotNull(path); + mDescription = Preconditions.checkNotNull(description); mPrimary = primary; mRemovable = removable; mEmulated = emulated; - mMtpReserveSpace = mtpReserveSpace; + mMtpReserveSize = mtpReserveSize; mAllowMassStorage = allowMassStorage; mMaxFileSize = maxFileSize; - mOwner = owner; + mOwner = Preconditions.checkNotNull(owner); + mFsUuid = fsUuid; + mState = Preconditions.checkNotNull(state); } private StorageVolume(Parcel in) { + mId = in.readString(); mStorageId = in.readInt(); mPath = new File(in.readString()); - mDescriptionId = in.readInt(); + mDescription = in.readString(); mPrimary = in.readInt() != 0; mRemovable = in.readInt() != 0; mEmulated = in.readInt() != 0; - mMtpReserveSpace = in.readInt(); + mMtpReserveSize = in.readLong(); mAllowMassStorage = in.readInt() != 0; mMaxFileSize = in.readLong(); mOwner = in.readParcelable(null); - mUuid = in.readString(); - mUserLabel = in.readString(); + mFsUuid = in.readString(); mState = in.readString(); } - public static StorageVolume fromTemplate(StorageVolume template, File path, UserHandle owner) { - return new StorageVolume(path, template.mDescriptionId, template.mPrimary, - template.mRemovable, template.mEmulated, template.mMtpReserveSpace, - template.mAllowMassStorage, template.mMaxFileSize, owner); + public String getId() { + return mId; } /** @@ -113,11 +115,7 @@ public class StorageVolume implements Parcelable { * @return the volume description */ public String getDescription(Context context) { - return context.getResources().getString(mDescriptionId); - } - - public int getDescriptionId() { - return mDescriptionId; + return mDescription; } public boolean isPrimary() { @@ -153,15 +151,6 @@ public class StorageVolume implements Parcelable { } /** - * Do not call this unless you are MountService - */ - public void setStorageId(int index) { - // storage ID is 0x00010001 for primary storage, - // then 0x00020001, 0x00030001, etc. for secondary storages - mStorageId = ((index + 1) << 16) + 1; - } - - /** * Number of megabytes of space to leave unallocated by MTP. * MTP will subtract this value from the free space it reports back * to the host via GetStorageInfo, and will not allow new files to @@ -174,7 +163,7 @@ public class StorageVolume implements Parcelable { * @return MTP reserve space */ public int getMtpReserveSpace() { - return mMtpReserveSpace; + return (int) (mMtpReserveSize / TrafficStats.MB_IN_BYTES); } /** @@ -199,12 +188,8 @@ public class StorageVolume implements Parcelable { return mOwner; } - public void setUuid(String uuid) { - mUuid = uuid; - } - public String getUuid() { - return mUuid; + return mFsUuid; } /** @@ -212,26 +197,18 @@ public class StorageVolume implements Parcelable { * parse or UUID is unknown. */ public int getFatVolumeId() { - if (mUuid == null || mUuid.length() != 9) { + if (mFsUuid == null || mFsUuid.length() != 9) { return -1; } try { - return (int)Long.parseLong(mUuid.replace("-", ""), 16); + return (int) Long.parseLong(mFsUuid.replace("-", ""), 16); } catch (NumberFormatException e) { return -1; } } - public void setUserLabel(String userLabel) { - mUserLabel = userLabel; - } - public String getUserLabel() { - return mUserLabel; - } - - public void setState(String state) { - mState = state; + return mDescription; } public String getState() { @@ -262,18 +239,18 @@ public class StorageVolume implements Parcelable { public void dump(IndentingPrintWriter pw) { pw.println("StorageVolume:"); pw.increaseIndent(); + pw.printPair("mId", mId); pw.printPair("mStorageId", mStorageId); pw.printPair("mPath", mPath); - pw.printPair("mDescriptionId", mDescriptionId); + pw.printPair("mDescription", mDescription); pw.printPair("mPrimary", mPrimary); pw.printPair("mRemovable", mRemovable); pw.printPair("mEmulated", mEmulated); - pw.printPair("mMtpReserveSpace", mMtpReserveSpace); + pw.printPair("mMtpReserveSize", mMtpReserveSize); pw.printPair("mAllowMassStorage", mAllowMassStorage); pw.printPair("mMaxFileSize", mMaxFileSize); pw.printPair("mOwner", mOwner); - pw.printPair("mUuid", mUuid); - pw.printPair("mUserLabel", mUserLabel); + pw.printPair("mFsUuid", mFsUuid); pw.printPair("mState", mState); pw.decreaseIndent(); } @@ -297,18 +274,18 @@ public class StorageVolume implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { + parcel.writeString(mId); parcel.writeInt(mStorageId); parcel.writeString(mPath.toString()); - parcel.writeInt(mDescriptionId); + parcel.writeString(mDescription); parcel.writeInt(mPrimary ? 1 : 0); parcel.writeInt(mRemovable ? 1 : 0); parcel.writeInt(mEmulated ? 1 : 0); - parcel.writeInt(mMtpReserveSpace); + parcel.writeLong(mMtpReserveSize); parcel.writeInt(mAllowMassStorage ? 1 : 0); parcel.writeLong(mMaxFileSize); parcel.writeParcelable(mOwner, flags); - parcel.writeString(mUuid); - parcel.writeString(mUserLabel); + parcel.writeString(mFsUuid); parcel.writeString(mState); } } diff --git a/core/java/android/os/storage/VolumeInfo.aidl b/core/java/android/os/storage/VolumeInfo.aidl new file mode 100644 index 0000000..32d12da --- /dev/null +++ b/core/java/android/os/storage/VolumeInfo.aidl @@ -0,0 +1,19 @@ +/* + * 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.os.storage; + +parcelable VolumeInfo; diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java new file mode 100644 index 0000000..2dc0361 --- /dev/null +++ b/core/java/android/os/storage/VolumeInfo.java @@ -0,0 +1,296 @@ +/* + * 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.os.storage; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.Intent; +import android.mtp.MtpStorage; +import android.os.Environment; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.DebugUtils; +import android.util.SparseArray; + +import com.android.internal.util.IndentingPrintWriter; +import com.android.internal.util.Preconditions; + +import java.io.CharArrayWriter; +import java.io.File; + +/** + * Information about a storage volume that may be mounted. A volume may be a + * partition on a physical {@link DiskInfo}, an emulated volume above some other + * storage medium, or a standalone container like an ASEC or OBB. + * + * @hide + */ +public class VolumeInfo implements Parcelable { + /** Real volume representing internal emulated storage */ + public static final String ID_EMULATED_INTERNAL = "emulated"; + + public static final int TYPE_PUBLIC = 0; + public static final int TYPE_PRIVATE = 1; + public static final int TYPE_EMULATED = 2; + public static final int TYPE_ASEC = 3; + public static final int TYPE_OBB = 4; + + public static final int STATE_UNMOUNTED = 0; + public static final int STATE_MOUNTING = 1; + public static final int STATE_MOUNTED = 2; + public static final int STATE_FORMATTING = 3; + public static final int STATE_UNMOUNTING = 4; + public static final int STATE_UNMOUNTABLE = 5; + + public static final int FLAG_PRIMARY = 1 << 0; + public static final int FLAG_VISIBLE = 1 << 1; + + private static SparseArray<String> sStateToEnvironment = new SparseArray<>(); + private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>(); + + static { + sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTED, Environment.MEDIA_UNMOUNTED); + sStateToEnvironment.put(VolumeInfo.STATE_MOUNTING, Environment.MEDIA_CHECKING); + sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED, Environment.MEDIA_MOUNTED); + sStateToEnvironment.put(VolumeInfo.STATE_FORMATTING, Environment.MEDIA_UNMOUNTED); + sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTING, Environment.MEDIA_EJECTING); + sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTABLE, Environment.MEDIA_UNMOUNTABLE); + + sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTED, Intent.ACTION_MEDIA_UNMOUNTED); + sEnvironmentToBroadcast.put(Environment.MEDIA_CHECKING, Intent.ACTION_MEDIA_CHECKING); + sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED, Intent.ACTION_MEDIA_MOUNTED); + sEnvironmentToBroadcast.put(Environment.MEDIA_EJECTING, Intent.ACTION_MEDIA_EJECT); + sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTABLE, Intent.ACTION_MEDIA_UNMOUNTABLE); + } + + /** vold state */ + public final String id; + public final int type; + public int flags = 0; + public int userId = -1; + public int state = STATE_UNMOUNTED; + public String fsType; + public String fsUuid; + public String fsLabel; + public String path; + + /** Framework state */ + public final int mtpIndex; + public String nickname; + + public DiskInfo disk; + + public VolumeInfo(String id, int type, int mtpIndex) { + this.id = Preconditions.checkNotNull(id); + this.type = type; + this.mtpIndex = mtpIndex; + } + + public VolumeInfo(Parcel parcel) { + id = parcel.readString(); + type = parcel.readInt(); + flags = parcel.readInt(); + userId = parcel.readInt(); + state = parcel.readInt(); + fsType = parcel.readString(); + fsUuid = parcel.readString(); + fsLabel = parcel.readString(); + path = parcel.readString(); + mtpIndex = parcel.readInt(); + nickname = parcel.readString(); + } + + public static @NonNull String getEnvironmentForState(int state) { + final String envState = sStateToEnvironment.get(state); + if (envState != null) { + return envState; + } else { + return Environment.MEDIA_UNKNOWN; + } + } + + public static @Nullable String getBroadcastForEnvironment(String envState) { + return sEnvironmentToBroadcast.get(envState); + } + + public static @Nullable String getBroadcastForState(int state) { + return getBroadcastForEnvironment(getEnvironmentForState(state)); + } + + public String getDescription(Context context) { + if (ID_EMULATED_INTERNAL.equals(id)) { + return context.getString(com.android.internal.R.string.storage_internal); + } else if (!TextUtils.isEmpty(nickname)) { + return nickname; + } else if (!TextUtils.isEmpty(fsLabel)) { + return fsLabel; + } else { + return null; + } + } + + public boolean isPrimary() { + return (flags & FLAG_PRIMARY) != 0; + } + + public boolean isVisible() { + return (flags & FLAG_VISIBLE) != 0; + } + + public boolean isVisibleToUser(int userId) { + if (type == TYPE_PUBLIC && userId == this.userId) { + return isVisible(); + } else if (type == TYPE_EMULATED) { + return isVisible(); + } else { + return false; + } + } + + public File getPathForUser(int userId) { + if (path == null) { + return null; + } else if (type == TYPE_PUBLIC && userId == this.userId) { + return new File(path); + } else if (type == TYPE_EMULATED) { + return new File(path, Integer.toString(userId)); + } else { + return null; + } + } + + public StorageVolume buildStorageVolume(Context context, int userId) { + final boolean removable; + final boolean emulated; + final boolean allowMassStorage = false; + final int mtpStorageId = MtpStorage.getStorageIdForIndex(mtpIndex); + final String envState = getEnvironmentForState(state); + + File userPath = getPathForUser(userId); + if (userPath == null) { + userPath = new File("/dev/null"); + } + + String description = getDescription(context); + if (description == null) { + description = context.getString(android.R.string.unknownName); + } + + long mtpReserveSize = 0; + long maxFileSize = 0; + + if (type == TYPE_EMULATED) { + emulated = true; + mtpReserveSize = StorageManager.from(context).getStorageLowBytes(userPath); + + if (ID_EMULATED_INTERNAL.equals(id)) { + removable = false; + } else { + removable = true; + } + + } else if (type == TYPE_PUBLIC) { + emulated = false; + removable = true; + + if ("vfat".equals(fsType)) { + maxFileSize = 4294967295L; + } + + } else { + throw new IllegalStateException("Unexpected volume type " + type); + } + + return new StorageVolume(id, mtpStorageId, userPath, description, isPrimary(), removable, + emulated, mtpReserveSize, allowMassStorage, maxFileSize, new UserHandle(userId), + fsUuid, envState); + } + + @Override + public String toString() { + final CharArrayWriter writer = new CharArrayWriter(); + dump(new IndentingPrintWriter(writer, " ", 80)); + return writer.toString(); + } + + public void dump(IndentingPrintWriter pw) { + pw.println("VolumeInfo:"); + pw.increaseIndent(); + pw.printPair("id", id); + pw.printPair("type", DebugUtils.valueToString(getClass(), "TYPE_", type)); + pw.printPair("flags", DebugUtils.flagsToString(getClass(), "FLAG_", flags)); + pw.printPair("userId", userId); + pw.printPair("state", DebugUtils.valueToString(getClass(), "STATE_", state)); + pw.println(); + pw.printPair("fsType", fsType); + pw.printPair("fsUuid", fsUuid); + pw.printPair("fsLabel", fsLabel); + pw.println(); + pw.printPair("path", path); + pw.printPair("mtpIndex", mtpIndex); + pw.decreaseIndent(); + pw.println(); + } + + @Override + public VolumeInfo clone() { + final Parcel temp = Parcel.obtain(); + try { + writeToParcel(temp, 0); + temp.setDataPosition(0); + return CREATOR.createFromParcel(temp); + } finally { + temp.recycle(); + } + } + + public static final Creator<VolumeInfo> CREATOR = new Creator<VolumeInfo>() { + @Override + public VolumeInfo createFromParcel(Parcel in) { + return new VolumeInfo(in); + } + + @Override + public VolumeInfo[] newArray(int size) { + return new VolumeInfo[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeString(id); + parcel.writeInt(type); + parcel.writeInt(flags); + parcel.writeInt(userId); + parcel.writeInt(state); + parcel.writeString(fsType); + parcel.writeString(fsUuid); + parcel.writeString(fsLabel); + parcel.writeString(path); + parcel.writeInt(mtpIndex); + parcel.writeString(nickname); + } +} diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java index f32e8cf..3b482eb 100644 --- a/core/java/android/preference/Preference.java +++ b/core/java/android/preference/Preference.java @@ -1438,7 +1438,7 @@ public class Preference implements Comparable<Preference> { protected boolean persistString(String value) { if (shouldPersist()) { // Shouldn't store null - if (value == getPersistedString(null)) { + if (TextUtils.equals(value, getPersistedString(null))) { // It's already there, so the same as persisting return true; } diff --git a/core/java/android/provider/AlarmClock.java b/core/java/android/provider/AlarmClock.java index 724d76d..25a35e1 100644 --- a/core/java/android/provider/AlarmClock.java +++ b/core/java/android/provider/AlarmClock.java @@ -43,8 +43,14 @@ public final class AlarmClock { * should remove this alarm after it has been dismissed. If an identical alarm exists matching * all parameters, the implementation may re-use it instead of creating a new one (in this case, * the alarm should not be removed after dismissal). - * + * </p><p> * This action always enables the alarm. + * </p><p> + * This activity could also be started in Voice Interaction mode. The activity should check + * {@link android.app.Activity#isVoiceInteraction}, and if true, the implementation should + * report a deeplink of the created/enabled alarm using + * {@link android.app.VoiceInteractor.CompleteVoiceRequest}. This allows follow-on voice actions + * such as {@link #ACTION_VOICE_CANCEL_ALARM} to cancel the alarm that was just enabled. * </p> * <h3>Request parameters</h3> * <ul> @@ -63,6 +69,48 @@ public final class AlarmClock { public static final String ACTION_SET_ALARM = "android.intent.action.SET_ALARM"; /** + * Voice Activity Action: Cancel an alarm. + * Requires: The activity must check {@link android.app.Activity#isVoiceInteraction}, i.e. be + * started in Voice Interaction mode. + * <p> + * Cancels the specified alarm by voice. To cancel means to disable, but not delete, the alarm. + * See {@link #ACTION_VOICE_DELETE_ALARM} to delete an alarm by voice. + * </p><p> + * The alarm to cancel can be specified or searched for in one of the following ways: + * <ol> + * <li>The Intent's data URI specifies a deeplink to the alarm. + * <li>If the Intent's data URI is unspecified, then the extra {@link #EXTRA_ALARM_SEARCH_MODE} is + * required to determine how to search for the alarm. + * + * @see #ACTION_VOICE_DELETE_ALARM + * @see #EXTRA_ALARM_SEARCH_MODE + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_VOICE_CANCEL_ALARM = + "android.intent.action.VOICE_CANCEL_ALARM"; + + /** + * Voice Activity Action: Delete an alarm. + * Requires: The activity must check {@link android.app.Activity#isVoiceInteraction}, i.e. be + * started in Voice Interaction mode. + * <p> + * Deletes the specified alarm by voice. + * See {@link #ACTION_VOICE_CANCEL_ALARM} to cancel (disable) an alarm by voice. + * </p><p> + * The alarm to delete can be specified or searched for in one of the following ways: + * <ol> + * <li>The Intent's data URI specifies a deeplink to the alarm. + * <li>If the Intent's data URI is unspecified, then the extra {@link #EXTRA_ALARM_SEARCH_MODE} is + * required to determine how to search for the alarm. + * + * @see #ACTION_VOICE_CANCEL_ALARM + * @see #EXTRA_ALARM_SEARCH_MODE + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_VOICE_DELETE_ALARM = + "android.intent.action.VOICE_DELETE_ALARM"; + + /** * Activity Action: Set a timer. * <p> * Activates an existing timer or creates a new one. @@ -99,6 +147,100 @@ public final class AlarmClock { public static final String ACTION_SHOW_ALARMS = "android.intent.action.SHOW_ALARMS"; /** + * Bundle extra: Specify the type of search mode to look up an alarm. + * <p> + * Used by {@link #ACTION_VOICE_CANCEL_ALARM} and {@link #ACTION_VOICE_DELETE_ALARM} to identify + * the alarm(s) to cancel or delete, respectively. + * </p><p> + * This extra is only required when the alarm is not already identified by a deeplink as + * specified in the Intent's data URI. + * </p><p> + * The value of this extra is a {@link String}, restricted to the following set of supported + * search modes: + * <ul> + * <li><i>Time</i> - {@link #ALARM_SEARCH_MODE_TIME}: Selects the alarm that is most + * closely matched by the search parameters {@link #EXTRA_HOUR}, {@link #EXTRA_MINUTES}, + * {@link #EXTRA_IS_PM}. + * <li><i>Next alarm</i> - {@link #ALARM_SEARCH_MODE_NEXT}: Selects the alarm that will + * ring next, or the alarm that is currently ringing, if any. + * <li><i>All alarms</i> - {@link #ALARM_SEARCH_MODE_ALL}: Selects all alarms. + * <li><i>None</i> - {@link #ALARM_SEARCH_MODE_NONE}: No search mode specified. The + * implementation should ask the user to select a search mode using + * {@link android.app.VoiceInteractor.PickOptionRequest} and proceed with a voice flow to + * identify the alarm. + * </ul> + * </ol> + * + * @see #ALARM_SEARCH_MODE_TIME + * @see #ALARM_SEARCH_MODE_NEXT + * @see #ALARM_SEARCH_MODE_ALL + * @see #ALARM_SEARCH_MODE_NONE + * @see #ACTION_VOICE_CANCEL_ALARM + * @see #ACTION_VOICE_DELETE_ALARM + */ + public static final String EXTRA_ALARM_SEARCH_MODE = + "android.intent.extra.alarm.ALARM_SEARCH_MODE"; + + /** + * Search for the alarm that is most closely matched by the search parameters + * {@link #EXTRA_HOUR}, {@link #EXTRA_MINUTES}, {@link #EXTRA_IS_PM}. + * In this search mode, at least one of these additional extras are required. + * <ul> + * <li>{@link #EXTRA_HOUR} - The hour to search for the alarm. + * <li>{@link #EXTRA_MINUTES} - The minute to search for the alarm. + * <li>{@link #EXTRA_IS_PM} - Whether the hour is AM or PM. + * </ul> + * + * @see #EXTRA_ALARM_SEARCH_MODE + */ + public static final String ALARM_SEARCH_MODE_TIME = "time"; + + /** + * Selects the alarm that will ring next, or the alarm that is currently ringing, if any. + * + * @see #EXTRA_ALARM_SEARCH_MODE + */ + public static final String ALARM_SEARCH_MODE_NEXT = "next"; + + /** + * Selects all alarms. + * + * @see #EXTRA_ALARM_SEARCH_MODE + */ + public static final String ALARM_SEARCH_MODE_ALL = "all"; + + /** + * No search mode specified. The implementation should ask the user to select a search mode + * using {@link android.app.VoiceInteractor.PickOptionRequest} and proceed with a voice flow to + * identify the alarm. + * + * @see #EXTRA_ALARM_SEARCH_MODE + */ + public static final String ALARM_SEARCH_MODE_NONE = "none"; + + /** + * Bundle extra: The AM/PM of the alarm. + * <p> + * Used by {@link #ACTION_VOICE_CANCEL_ALARM} and {@link #ACTION_VOICE_DELETE_ALARM}. + * </p><p> + * This extra is optional and only used when {@link #EXTRA_ALARM_SEARCH_MODE} is set to + * {@link #ALARM_SEARCH_MODE_TIME}. In this search mode, the {@link #EXTRA_IS_PM} is + * used together with {@link #EXTRA_HOUR} and {@link #EXTRA_MINUTES}. The implementation should + * look up the alarm that is most closely matched by these search parameters. + * If {@link #EXTRA_IS_PM} is missing, then the AM/PM of the specified {@link #EXTRA_HOUR} is + * ambiguous and the implementation should ask for clarification from the user. + * </p><p> + * The value is a {@link Boolean}, where false=AM and true=PM. + * </p> + * + * @see #ACTION_VOICE_CANCEL_ALARM + * @see #ACTION_VOICE_DELETE_ALARM + * @see #EXTRA_HOUR + * @see #EXTRA_MINUTES + */ + public static final String EXTRA_IS_PM = "android.intent.extra.alarm.IS_PM"; + + /** * Bundle extra: Weekdays for repeating alarm. * <p> * Used by {@link #ACTION_SET_ALARM}. diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 6517f35..7d57233 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -454,7 +454,6 @@ public class CallLog { long start, int duration, Long dataUsage, boolean addForAllUsers) { final ContentResolver resolver = context.getContentResolver(); int numberPresentation = PRESENTATION_ALLOWED; - boolean isHidden = false; TelecomManager tm = null; try { @@ -469,12 +468,6 @@ public class CallLog { if (address != null) { accountAddress = address.getSchemeSpecificPart(); } - } else { - // We could not find the account through telecom. For call log entries that - // are added with a phone account which is not registered, we automatically - // mark them as hidden. They are unhidden once the account is registered. - Log.i(LOG_TAG, "Marking call log entry as hidden."); - isHidden = true; } } @@ -520,7 +513,6 @@ public class CallLog { values.put(PHONE_ACCOUNT_COMPONENT_NAME, accountComponentString); values.put(PHONE_ACCOUNT_ID, accountId); values.put(PHONE_ACCOUNT_ADDRESS, accountAddress); - values.put(PHONE_ACCOUNT_HIDDEN, Integer.valueOf(isHidden ? 1 : 0)); values.put(NEW, Integer.valueOf(1)); if (callType == MISSED_TYPE) { diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 74b0a1c..bf7f3cb 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -18,6 +18,7 @@ package android.provider; import android.accounts.Account; import android.app.Activity; +import android.app.admin.DevicePolicyManager; import android.content.ActivityNotFoundException; import android.content.ContentProviderClient; import android.content.ContentProviderOperation; @@ -1628,7 +1629,6 @@ public final class ContactsContract { */ public static final String CONTENT_VCARD_TYPE = "text/x-vcard"; - /** * Mimimal ID for corp contacts returned from * {@link PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI}. @@ -1638,6 +1638,14 @@ public final class ContactsContract { public static long ENTERPRISE_CONTACT_ID_BASE = 1000000000; // slightly smaller than 2 ** 30 /** + * Prefix for corp contacts returned from + * {@link PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI}. + * + * @hide + */ + public static String ENTERPRISE_CONTACT_LOOKUP_PREFIX = "c-"; + + /** * Return TRUE if a contact ID is from the contacts provider on the enterprise profile. * * {@link PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI} may return such a contact. @@ -4809,6 +4817,14 @@ public final class ContactsContract { Uri.withAppendedPath(AUTHORITY_URI, "raw_contact_entities"); /** + * The content:// style URI for this table in corp profile + * + * @hide + */ + public static final Uri CORP_CONTENT_URI = + Uri.withAppendedPath(AUTHORITY_URI, "raw_contact_entities_corp"); + + /** * The content:// style URI for this table, specific to the user's profile. */ public static final Uri PROFILE_CONTENT_URI = @@ -5024,9 +5040,17 @@ public final class ContactsContract { * is from the corp profile, use * {@link ContactsContract.Contacts#isEnterpriseContactId(long)}. * </li> + * <li> + * Corp contacts will get artificial {@link #LOOKUP_KEY}s too. + * </li> * </ul> * <p> - * This URI does NOT support selection nor order-by. + * A contact lookup URL built by + * {@link ContactsContract.Contacts#getLookupUri(long, String)} + * with an {@link #_ID} and a {@link #LOOKUP_KEY} returned by this API can be passed to + * {@link ContactsContract.QuickContact#showQuickContact} even if a contact is from the + * corp profile. + * </p> * * <pre> * Uri lookupUri = Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, @@ -6017,10 +6041,18 @@ public final class ContactsContract { * a contact * is from the corp profile, use * {@link ContactsContract.Contacts#isEnterpriseContactId(long)}. - * </li> - * </ul> - * <p> - * This URI does NOT support selection nor order-by. + * </li> + * <li> + * Corp contacts will get artificial {@link #LOOKUP_KEY}s too. + * </li> + * </ul> + * <p> + * A contact lookup URL built by + * {@link ContactsContract.Contacts#getLookupUri(long, String)} + * with an {@link #_ID} and a {@link #LOOKUP_KEY} returned by this API can be passed to + * {@link ContactsContract.QuickContact#showQuickContact} even if a contact is from the + * corp profile. + * </p> * * <pre> * Uri lookupUri = Uri.withAppendedPath(Email.ENTERPRISE_CONTENT_LOOKUP_URI, @@ -8174,6 +8206,9 @@ public final class ContactsContract { */ public static final int MODE_LARGE = 3; + /** @hide */ + public static final int MODE_DEFAULT = MODE_LARGE; + /** * Constructs the QuickContacts intent with a view's rect. * @hide @@ -8216,6 +8251,7 @@ public final class ContactsContract { // Launch pivot dialog through intent for now final Intent intent = new Intent(ACTION_QUICK_CONTACT).addFlags(intentFlags); + // NOTE: This logic and rebuildManagedQuickContactsIntent() must be in sync. intent.setData(lookupUri); intent.setSourceBounds(target); intent.putExtra(EXTRA_MODE, mode); @@ -8224,6 +8260,30 @@ public final class ContactsContract { } /** + * Constructs a QuickContacts intent based on an incoming intent for DevicePolicyManager + * to strip off anything not necessary. + * + * @hide + */ + public static Intent rebuildManagedQuickContactsIntent(String lookupKey, long contactId, + Intent originalIntent) { + final Intent intent = new Intent(ACTION_QUICK_CONTACT); + // Rebuild the URI from a lookup key and a contact ID. + intent.setData(Contacts.getLookupUri(contactId, lookupKey)); + + // Copy flags and always set NEW_TASK because it won't have a parent activity. + intent.setFlags(originalIntent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); + + // Copy extras. + intent.setSourceBounds(originalIntent.getSourceBounds()); + intent.putExtra(EXTRA_MODE, originalIntent.getIntExtra(EXTRA_MODE, MODE_DEFAULT)); + intent.putExtra(EXTRA_EXCLUDE_MIMES, + originalIntent.getStringArrayExtra(EXTRA_EXCLUDE_MIMES)); + return intent; + } + + + /** * Trigger a dialog that lists the various methods of interacting with * the requested {@link Contacts} entry. This may be based on available * {@link ContactsContract.Data} rows under that contact, and may also @@ -8251,7 +8311,7 @@ public final class ContactsContract { // Trigger with obtained rectangle Intent intent = composeQuickContactsIntent(context, target, lookupUri, mode, excludeMimes); - startActivityWithErrorToast(context, intent); + ContactsInternal.startQuickContactWithErrorToast(context, intent); } /** @@ -8284,7 +8344,7 @@ public final class ContactsContract { String[] excludeMimes) { Intent intent = composeQuickContactsIntent(context, target, lookupUri, mode, excludeMimes); - startActivityWithErrorToast(context, intent); + ContactsInternal.startQuickContactWithErrorToast(context, intent); } /** @@ -8317,10 +8377,10 @@ public final class ContactsContract { // Use MODE_LARGE instead of accepting mode as a parameter. The different mode // values defined in ContactsContract only affect very old implementations // of QuickContacts. - Intent intent = composeQuickContactsIntent(context, target, lookupUri, MODE_LARGE, + Intent intent = composeQuickContactsIntent(context, target, lookupUri, MODE_DEFAULT, excludeMimes); intent.putExtra(EXTRA_PRIORITIZED_MIMETYPE, prioritizedMimeType); - startActivityWithErrorToast(context, intent); + ContactsInternal.startQuickContactWithErrorToast(context, intent); } /** @@ -8355,19 +8415,10 @@ public final class ContactsContract { // Use MODE_LARGE instead of accepting mode as a parameter. The different mode // values defined in ContactsContract only affect very old implementations // of QuickContacts. - Intent intent = composeQuickContactsIntent(context, target, lookupUri, MODE_LARGE, + Intent intent = composeQuickContactsIntent(context, target, lookupUri, MODE_DEFAULT, excludeMimes); intent.putExtra(EXTRA_PRIORITIZED_MIMETYPE, prioritizedMimeType); - startActivityWithErrorToast(context, intent); - } - - private static void startActivityWithErrorToast(Context context, Intent intent) { - try { - context.startActivity(intent); - } catch (ActivityNotFoundException e) { - Toast.makeText(context, com.android.internal.R.string.quick_contacts_not_available, - Toast.LENGTH_SHORT).show(); - } + ContactsInternal.startQuickContactWithErrorToast(context, intent); } } diff --git a/core/java/android/provider/ContactsInternal.java b/core/java/android/provider/ContactsInternal.java new file mode 100644 index 0000000..059a603 --- /dev/null +++ b/core/java/android/provider/ContactsInternal.java @@ -0,0 +1,112 @@ +/* + * 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.provider; + +import android.app.admin.DevicePolicyManager; +import android.content.ActivityNotFoundException; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.content.UriMatcher; +import android.net.Uri; +import android.os.Process; +import android.os.UserHandle; +import android.text.TextUtils; +import android.widget.Toast; + +import java.util.List; + +/** + * Contacts related internal methods. + * + * @hide + */ +public class ContactsInternal { + private ContactsInternal() { + } + + /** URI matcher used to parse contact URIs. */ + private static final UriMatcher sContactsUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); + + private static final int CONTACTS_URI_LOOKUP_ID = 1000; + + static { + // Contacts URI matching table + final UriMatcher matcher = sContactsUriMatcher; + matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*/#", CONTACTS_URI_LOOKUP_ID); + } + + /** + * Called by {@link ContactsContract} to star Quick Contact, possibly on the managed profile. + */ + public static void startQuickContactWithErrorToast(Context context, Intent intent) { + final Uri uri = intent.getData(); + + final int match = sContactsUriMatcher.match(uri); + switch (match) { + case CONTACTS_URI_LOOKUP_ID: { + if (maybeStartManagedQuickContact(context, intent)) { + return; // Request handled by DPM. Just return here. + } + break; + } + } + // Launch on the current profile. + startQuickContactWithErrorToastForUser(context, intent, Process.myUserHandle()); + } + + public static void startQuickContactWithErrorToastForUser(Context context, Intent intent, + UserHandle user) { + try { + context.startActivityAsUser(intent, user); + } catch (ActivityNotFoundException e) { + Toast.makeText(context, com.android.internal.R.string.quick_contacts_not_available, + Toast.LENGTH_SHORT).show(); + } + } + + /** + * If the URI in {@code intent} is of a corp contact, launch quick contact on the managed + * profile. + * + * @return the URI in {@code intent} is of a corp contact thus launched on the managed profile. + */ + private static boolean maybeStartManagedQuickContact(Context context, Intent originalIntent) { + final Uri uri = originalIntent.getData(); + + // Decompose into an ID and a lookup key. + final List<String> pathSegments = uri.getPathSegments(); + final long contactId = ContentUris.parseId(uri); + final String lookupKey = pathSegments.get(2); + + // See if it has a corp lookupkey. + if (TextUtils.isEmpty(lookupKey) + || !lookupKey.startsWith( + ContactsContract.Contacts.ENTERPRISE_CONTACT_LOOKUP_PREFIX)) { + return false; // It's not a corp lookup key. + } + + // Launch Quick Contact on the managed profile, if the policy allows. + final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); + final String actualLookupKey = lookupKey.substring( + ContactsContract.Contacts.ENTERPRISE_CONTACT_LOOKUP_PREFIX.length()); + final long actualContactId = + (contactId - ContactsContract.Contacts.ENTERPRISE_CONTACT_ID_BASE); + + dpm.startManagedQuickContact(actualLookupKey, actualContactId, originalIntent); + return true; + } +} diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 5afbd6d..7565654b 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -226,6 +226,35 @@ public final class MediaStore { public static final String INTENT_ACTION_STILL_IMAGE_CAMERA = "android.media.action.STILL_IMAGE_CAMERA"; /** + * The name of the Intent action used to indicate that a camera launch might be imminent. This + * broadcast should be targeted to the package that is receiving + * {@link #INTENT_ACTION_STILL_IMAGE_CAMERA} or + * {@link #INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE}, depending on the context. If such + * intent would launch the resolver activity, this broadcast should not be sent at all. + * <p> + * A receiver of this broadcast should do the absolute minimum amount of work to initialize the + * camera in order to reduce startup time in likely case that shortly after an actual camera + * launch intent would be sent. + * <p> + * In case the actual intent will not be fired, the target package will receive + * {@link #ACTION_STILL_IMAGE_CAMERA_COOLDOWN}. However, it is recommended that the receiver + * also implements a timeout to close the camera after receiving this intent, as there is no + * guarantee that {@link #ACTION_STILL_IMAGE_CAMERA_COOLDOWN} will be delivered. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_STILL_IMAGE_CAMERA_PREWARM = "android.media.action.STILL_IMAGE_CAMERA_PREWARM"; + + /** + * The name of the Intent action used to indicate that an imminent camera launch has been + * cancelled by the user. This broadcast should be targeted to the package that has received + * {@link #ACTION_STILL_IMAGE_CAMERA_PREWARM}. + * <p> + * A receiver of this broadcast should close the camera immediately. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_STILL_IMAGE_CAMERA_COOLDOWN = "android.media.action.STILL_IMAGE_CAMERA_COOLDOWN"; + + /** * The name of the Intent action used to launch a camera in still image mode * for use when the device is secured (e.g. with a pin, password, pattern, * or face unlock). Applications responding to this intent must not expose diff --git a/core/java/android/provider/SearchIndexableData.java b/core/java/android/provider/SearchIndexableData.java index 7b9d1ea..5e0a76d 100644 --- a/core/java/android/provider/SearchIndexableData.java +++ b/core/java/android/provider/SearchIndexableData.java @@ -16,6 +16,7 @@ package android.provider; +import android.annotation.SystemApi; import android.content.Context; import java.util.Locale; @@ -27,6 +28,7 @@ import java.util.Locale; * * @hide */ +@SystemApi public abstract class SearchIndexableData { /** diff --git a/core/java/android/provider/SearchIndexableResource.java b/core/java/android/provider/SearchIndexableResource.java index c807df2..1eb1734 100644 --- a/core/java/android/provider/SearchIndexableResource.java +++ b/core/java/android/provider/SearchIndexableResource.java @@ -16,6 +16,7 @@ package android.provider; +import android.annotation.SystemApi; import android.content.Context; /** @@ -31,6 +32,7 @@ import android.content.Context; * * @hide */ +@SystemApi public class SearchIndexableResource extends SearchIndexableData { /** diff --git a/core/java/android/provider/SearchIndexablesContract.java b/core/java/android/provider/SearchIndexablesContract.java index 1b5f72a..93ac7f6 100644 --- a/core/java/android/provider/SearchIndexablesContract.java +++ b/core/java/android/provider/SearchIndexablesContract.java @@ -16,6 +16,7 @@ package android.provider; +import android.annotation.SystemApi; import android.content.ContentResolver; /** @@ -23,6 +24,7 @@ import android.content.ContentResolver; * * @hide */ +@SystemApi public class SearchIndexablesContract { /** @@ -234,7 +236,7 @@ public class SearchIndexablesContract { /** * The base columns. */ - private static class BaseColumns { + public static class BaseColumns { private BaseColumns() { } diff --git a/core/java/android/provider/SearchIndexablesProvider.java b/core/java/android/provider/SearchIndexablesProvider.java index 9c8f6d0..3120e54 100644 --- a/core/java/android/provider/SearchIndexablesProvider.java +++ b/core/java/android/provider/SearchIndexablesProvider.java @@ -16,6 +16,7 @@ package android.provider; +import android.annotation.SystemApi; import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; @@ -61,6 +62,7 @@ import android.net.Uri; * * @hide */ +@SystemApi public abstract class SearchIndexablesProvider extends ContentProvider { private static final String TAG = "IndexablesProvider"; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index de536bd..5ee8fb3 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -138,7 +138,7 @@ public final class Settings { "android.settings.AIRPLANE_MODE_SETTINGS"; /** - * Activity Action: Modify Airplane mode settings using the users voice. + * Activity Action: Modify Airplane mode settings using a voice command. * <p> * In some cases, a matching Activity may not exist, so ensure you safeguard against this. * <p> @@ -146,15 +146,13 @@ public final class Settings { * {@link android.service.voice.VoiceInteractionSession#startVoiceActivity * startVoiceActivity}. * <p> - * To tell which state airplane mode should be set to, add the - * {@link #EXTRA_AIRPLANE_MODE_ENABLED} extra to this Intent with the state specified. - * If there is no extra in this Intent, no changes will be made. - * <p> - * The activity should verify that + * Note: The activity implementing this intent MUST verify that * {@link android.app.Activity#isVoiceInteraction isVoiceInteraction} returns true before * modifying the setting. * <p> - * Input: Nothing. + * Input: To tell which state airplane mode should be set to, add the + * {@link #EXTRA_AIRPLANE_MODE_ENABLED} extra to this Intent with the state specified. + * If the extra is not included, no changes will be made. * <p> * Output: Nothing. */ @@ -839,6 +837,49 @@ public final class Settings { public static final String ACTION_ZEN_MODE_SETTINGS = "android.settings.ZEN_MODE_SETTINGS"; /** + * Activity Action: Show Zen Mode priority configuration settings. + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_ZEN_MODE_PRIORITY_SETTINGS + = "android.settings.ZEN_MODE_PRIORITY_SETTINGS"; + + /** + * Activity Action: Show Zen Mode automation configuration settings. + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_ZEN_MODE_AUTOMATION_SETTINGS + = "android.settings.ZEN_MODE_AUTOMATION_SETTINGS"; + + /** + * Activity Action: Modify do not disturb mode settings. + * <p> + * In some cases, a matching Activity may not exist, so ensure you safeguard against this. + * <p> + * This intent MUST be started using + * {@link android.service.voice.VoiceInteractionSession#startVoiceActivity + * startVoiceActivity}. + * <p> + * Note: The Activity implementing this intent MUST verify that + * {@link android.app.Activity#isVoiceInteraction isVoiceInteraction}. + * returns true before modifying the setting. + * <p> + * Input: The optional {@link #EXTRA_DO_NOT_DISTURB_MODE_MINUTES} extra can be used to indicate + * how long the user wishes to avoid interruptions for. The optional + * {@link #EXTRA_DO_NOT_DISTURB_MODE_ENABLED} extra can be to indicate if the user is + * enabling or disabling do not disturb mode. If either extra is not included, the + * user maybe asked to provide the value. + * <p> + * Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_VOICE_CONTROL_DO_NOT_DISTURB_MODE = + "android.settings.VOICE_CONTROL_DO_NOT_DISTURB_MODE"; + + /** * Activity Action: Show the regulatory information screen for the device. * <p> * In some cases, a matching Activity may not exist, so ensure you safeguard @@ -885,6 +926,29 @@ public final class Settings { = "android.settings.BATTERY_SAVER_SETTINGS"; /** + * Activity Action: Modify Battery Saver mode setting using a voice command. + * <p> + * In some cases, a matching Activity may not exist, so ensure you safeguard against this. + * <p> + * This intent MUST be started using + * {@link android.service.voice.VoiceInteractionSession#startVoiceActivity + * startVoiceActivity}. + * <p> + * Note: The activity implementing this intent MUST verify that + * {@link android.app.Activity#isVoiceInteraction isVoiceInteraction} returns true before + * modifying the setting. + * <p> + * Input: To tell which state batter saver mode should be set to, add the + * {@link #EXTRA_BATTERY_SAVER_MODE_ENABLED} extra to this Intent with the state specified. + * If the extra is not included, no changes will be made. + * <p> + * Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_VOICE_CONTROL_BATTERY_SAVER_MODE = + "android.settings.VOICE_CONTROL_BATTERY_SAVER_MODE"; + + /** * Activity Action: Show Home selection settings. If there are multiple activities * that can satisfy the {@link Intent#CATEGORY_HOME} intent, this screen allows you * to pick your preferred activity. @@ -998,10 +1062,37 @@ public final class Settings { * Activity Extra: Enable or disable Airplane Mode. * <p> * This can be passed as an extra field to the {@link #ACTION_VOICE_CONTROL_AIRPLANE_MODE} - * intent as a boolean. + * intent as a boolean to indicate if it should be enabled. */ public static final String EXTRA_AIRPLANE_MODE_ENABLED = "airplane_mode_enabled"; + /** + * Activity Extra: Enable or disable Battery saver mode. + * <p> + * This can be passed as an extra field to the {@link #ACTION_VOICE_CONTROL_BATTERY_SAVER_MODE} + * intent as a boolean to indicate if it should be enabled. + */ + public static final String EXTRA_BATTERY_SAVER_MODE_ENABLED = + "android.settings.extra.battery_saver_mode_enabled"; + + /** + * Activity Extra: Enable or disable Do Not Disturb mode. + * <p> + * This can be passed as an extra field to the {@link #ACTION_VOICE_CONTROL_DO_NOT_DISTURB_MODE} + * intent as a boolean to indicate if it should be enabled. + */ + public static final String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = + "android.settings.extra.do_not_disturb_mode_enabled"; + + /** + * Activity Extra: How many minutes to enable do not disturb mode for. + * <p> + * This can be passed as an extra field to the {@link #ACTION_VOICE_CONTROL_DO_NOT_DISTURB_MODE} + * intent to indicate how long do not disturb mode should be enabled for. + */ + public static final String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = + "android.settings.extra.do_not_disturb_mode_minutes"; + private static final String JID_RESOURCE_PREFIX = "android"; public static final String AUTHORITY = "settings"; @@ -3026,7 +3117,7 @@ public final class Settings { }; /** - * These are all pulbic system settings + * These are all public system settings * * @hide */ @@ -3126,7 +3217,7 @@ public final class Settings { } /** - * These are all pulbic system settings + * These are all public system settings * * @hide */ @@ -5255,6 +5346,15 @@ public final class Settings { public static final String SMS_DEFAULT_APPLICATION = "sms_default_application"; /** + * Specifies the package name currently configured to be the emergency assistance application + * + * @see android.telephony.TelephonyManager#ACTION_EMERGENCY_ASSISTANCE + * + * @hide + */ + public static final String EMERGENCY_ASSISTANCE_APPLICATION = "emergency_assistance_application"; + + /** * Names of the packages that the current user has explicitly allowed to * see all of the user's notifications, separated by ':'. * @@ -5354,6 +5454,13 @@ public final class Settings { public static final String SLEEP_TIMEOUT = "sleep_timeout"; /** + * Duration in milliseconds that an app should be inactive before it is considered idle. + * <p/>Type: Long + * @hide + */ + public static final String APP_IDLE_DURATION = "app_idle_duration"; + + /** * This are the settings to be backed up. * * NOTE: Settings are backed up and restored in the order they appear @@ -5416,6 +5523,7 @@ public final class Settings { * since the managed profile doesn't get to change them. */ private static final Set<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>(); + static { CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_ENABLED); CLONE_TO_MANAGED_PROFILE.add(ALLOW_MOCK_LOCATION); @@ -6088,7 +6196,7 @@ public final class Settings { public static final String PACKAGE_VERIFIER_SETTING_VISIBLE = "verifier_setting_visible"; /** - * Run package verificaiton on apps installed through ADB/ADT/USB + * Run package verification on apps installed through ADB/ADT/USB * 1 = perform package verification on ADB installs (default) * 0 = bypass package verification on ADB installs * @hide @@ -6366,6 +6474,14 @@ public final class Settings { "wifi_scan_always_enabled"; /** + * Settings to allow BLE scans to be enabled even when Bluetooth is turned off for + * connectivity. + * @hide + */ + public static final String BLE_SCAN_ALWAYS_AVAILABLE = + "ble_scan_always_enabled"; + + /** * Used to save the Wifi_ON state prior to tethering. * This state will be checked to restore Wifi after * the user turns off tethering. @@ -7093,9 +7209,11 @@ public final class Settings { /** @hide */ public static final int ZEN_MODE_OFF = 0; /** @hide */ public static final int ZEN_MODE_IMPORTANT_INTERRUPTIONS = 1; /** @hide */ public static final int ZEN_MODE_NO_INTERRUPTIONS = 2; + /** @hide */ public static final int ZEN_MODE_ALARMS = 3; /** @hide */ public static String zenModeToString(int mode) { if (mode == ZEN_MODE_IMPORTANT_INTERRUPTIONS) return "ZEN_MODE_IMPORTANT_INTERRUPTIONS"; + if (mode == ZEN_MODE_ALARMS) return "ZEN_MODE_ALARMS"; if (mode == ZEN_MODE_NO_INTERRUPTIONS) return "ZEN_MODE_NO_INTERRUPTIONS"; return "ZEN_MODE_OFF"; } diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java index 0da4fd5..efbb3b8 100644 --- a/core/java/android/provider/VoicemailContract.java +++ b/core/java/android/provider/VoicemailContract.java @@ -258,7 +258,7 @@ public class VoicemailContract { public static Uri insert(Context context, Voicemail voicemail) { ContentResolver contentResolver = context.getContentResolver(); ContentValues contentValues = getContentValues(voicemail); - return contentResolver.insert(Voicemails.CONTENT_URI, contentValues); + return contentResolver.insert(buildSourceUri(context.getPackageName()), contentValues); } /** @@ -273,7 +273,7 @@ public class VoicemailContract { int count = voicemails.size(); for (int i = 0; i < count; i++) { ContentValues contentValues = getContentValues(voicemails.get(i)); - contentResolver.insert(Voicemails.CONTENT_URI, contentValues); + contentResolver.insert(buildSourceUri(context.getPackageName()), contentValues); } return count; } diff --git a/core/java/android/security/IKeystoreService.aidl b/core/java/android/security/IKeystoreService.aidl index d24bc13..579cdbe 100644 --- a/core/java/android/security/IKeystoreService.aidl +++ b/core/java/android/security/IKeystoreService.aidl @@ -60,8 +60,8 @@ interface IKeystoreService { // Keymaster 0.4 methods int addRngEntropy(in byte[] data); - int generateKey(String alias, in KeymasterArguments arguments, int uid, int flags, - out KeyCharacteristics characteristics); + int generateKey(String alias, in KeymasterArguments arguments, in byte[] entropy, int uid, + int flags, out KeyCharacteristics characteristics); int getKeyCharacteristics(String alias, in KeymasterBlob clientId, in KeymasterBlob appId, out KeyCharacteristics characteristics); int importKey(String alias, in KeymasterArguments arguments, int format, @@ -69,8 +69,10 @@ interface IKeystoreService { ExportResult exportKey(String alias, int format, in KeymasterBlob clientId, in KeymasterBlob appId); OperationResult begin(IBinder appToken, String alias, int purpose, boolean pruneable, - in KeymasterArguments params, out KeymasterArguments operationParams); + in KeymasterArguments params, in byte[] entropy, out KeymasterArguments operationParams); OperationResult update(IBinder token, in KeymasterArguments params, in byte[] input); OperationResult finish(IBinder token, in KeymasterArguments params, in byte[] signature); int abort(IBinder handle); + boolean isOperationAuthorized(IBinder token); + int addAuthToken(in byte[] authToken); } diff --git a/core/java/android/security/NetworkSecurityPolicy.java b/core/java/android/security/NetworkSecurityPolicy.java index 0626bbc..0b3bf45 100644 --- a/core/java/android/security/NetworkSecurityPolicy.java +++ b/core/java/android/security/NetworkSecurityPolicy.java @@ -24,8 +24,6 @@ package android.security; * * <p>The policy currently consists of a single flag: whether cleartext network traffic is * permitted. See {@link #isCleartextTrafficPermitted()}. - * - * @hide */ public class NetworkSecurityPolicy { @@ -48,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, {@code WebView}, {@code 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.webkit.WebView}, {@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 diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java index e653b74..5e2accd 100644 --- a/core/java/android/security/keymaster/KeymasterDefs.java +++ b/core/java/android/security/keymaster/KeymasterDefs.java @@ -16,6 +16,9 @@ package android.security.keymaster; +import java.util.HashMap; +import java.util.Map; + /** * Class tracking all the keymaster enum values needed for the binder API to keystore. * This must be kept in sync with hardware/libhardware/include/hardware/keymaster_defs.h @@ -168,6 +171,10 @@ public final class KeymasterDefs { public static final int KM_KEY_FORMAT_PKCS12 = 2; public static final int KM_KEY_FORMAT_RAW = 3; + // User authenticators. + public static final int HW_AUTH_PASSWORD = 1 << 0; + public static final int HW_AUTH_FINGERPRINT = 1 << 1; + // Error codes. public static final int KM_ERROR_OK = 0; public static final int KM_ERROR_ROOT_OF_TRUST_ALREADY_SET = -1; @@ -178,7 +185,7 @@ public final class KeymasterDefs { public static final int KM_ERROR_UNSUPPORTED_KEY_SIZE = -6; public static final int KM_ERROR_UNSUPPORTED_BLOCK_MODE = -7; public static final int KM_ERROR_INCOMPATIBLE_BLOCK_MODE = -8; - public static final int KM_ERROR_UNSUPPORTED_TAG_LENGTH = -9; + public static final int KM_ERROR_UNSUPPORTED_MAC_LENGTH = -9; public static final int KM_ERROR_UNSUPPORTED_PADDING_MODE = -10; public static final int KM_ERROR_INCOMPATIBLE_PADDING_MODE = -11; public static final int KM_ERROR_UNSUPPORTED_DIGEST = -12; @@ -224,7 +231,54 @@ public final class KeymasterDefs { public static final int KM_ERROR_VERSION_MISMATCH = -101; public static final int KM_ERROR_UNKNOWN_ERROR = -1000; + public static final Map<Integer, String> sErrorCodeToString = new HashMap<Integer, String>(); + static { + sErrorCodeToString.put(KM_ERROR_OK, "OK"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_PURPOSE, "Unsupported purpose"); + sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_PURPOSE, "Incompatible purpose"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_ALGORITHM, "Unsupported algorithm"); + sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_ALGORITHM, "Incompatible algorithm"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_KEY_SIZE, "Unsupported key size"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_BLOCK_MODE, "Unsupported block mode"); + sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_BLOCK_MODE, "Incompatible block mode"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_MAC_LENGTH, + "Unsupported MAC or authentication tag length"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_PADDING_MODE, "Unsupported padding mode"); + sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_PADDING_MODE, "Incompatible padding mode"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_DIGEST, "Unsupported digest"); + sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_DIGEST, "Incompatible digest"); + sErrorCodeToString.put(KM_ERROR_INVALID_EXPIRATION_TIME, "Invalid expiration time"); + sErrorCodeToString.put(KM_ERROR_INVALID_USER_ID, "Invalid user ID"); + sErrorCodeToString.put(KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT, + "Invalid user authorization timeout"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_KEY_FORMAT, "Unsupported key format"); + sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_KEY_FORMAT, "Incompatible key format"); + sErrorCodeToString.put(KM_ERROR_INVALID_INPUT_LENGTH, "Invalid input length"); + sErrorCodeToString.put(KM_ERROR_KEY_NOT_YET_VALID, "Key not yet valid"); + sErrorCodeToString.put(KM_ERROR_KEY_EXPIRED, "Key expired"); + sErrorCodeToString.put(KM_ERROR_KEY_USER_NOT_AUTHENTICATED, "Key user not authenticated"); + sErrorCodeToString.put(KM_ERROR_INVALID_OPERATION_HANDLE, "Invalid operation handle"); + sErrorCodeToString.put(KM_ERROR_VERIFICATION_FAILED, "Signature/MAC verification failed"); + sErrorCodeToString.put(KM_ERROR_TOO_MANY_OPERATIONS, "Too many operations"); + sErrorCodeToString.put(KM_ERROR_INVALID_KEY_BLOB, "Invalid key blob"); + sErrorCodeToString.put(KM_ERROR_INVALID_ARGUMENT, "Invalid argument"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_TAG, "Unsupported tag"); + sErrorCodeToString.put(KM_ERROR_INVALID_TAG, "Invalid tag"); + sErrorCodeToString.put(KM_ERROR_MEMORY_ALLOCATION_FAILED, "Memory allocation failed"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_EC_FIELD, "Unsupported EC field"); + sErrorCodeToString.put(KM_ERROR_UNIMPLEMENTED, "Not implemented"); + sErrorCodeToString.put(KM_ERROR_UNKNOWN_ERROR, "Unknown error"); + } + public static int getTagType(int tag) { return tag & (0xF << 28); } + + public static String getErrorMessage(int errorCode) { + String result = sErrorCodeToString.get(errorCode); + if (result != null) { + return result; + } + return String.valueOf(errorCode); + } } diff --git a/core/java/android/security/keymaster/OperationResult.java b/core/java/android/security/keymaster/OperationResult.java index 4fc9d24..9b46ad3 100644 --- a/core/java/android/security/keymaster/OperationResult.java +++ b/core/java/android/security/keymaster/OperationResult.java @@ -28,6 +28,7 @@ import android.os.Parcelable; public class OperationResult implements Parcelable { public final int resultCode; public final IBinder token; + public final long operationHandle; public final int inputConsumed; public final byte[] output; @@ -45,6 +46,7 @@ public class OperationResult implements Parcelable { protected OperationResult(Parcel in) { resultCode = in.readInt(); token = in.readStrongBinder(); + operationHandle = in.readLong(); inputConsumed = in.readInt(); output = in.createByteArray(); } @@ -58,6 +60,7 @@ public class OperationResult implements Parcelable { public void writeToParcel(Parcel out, int flags) { out.writeInt(resultCode); out.writeStrongBinder(token); + out.writeLong(operationHandle); out.writeInt(inputConsumed); out.writeByteArray(output); } diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 0860153..fa782e4 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -77,6 +77,12 @@ public abstract class NotificationListenerService extends Service { */ public static final int INTERRUPTION_FILTER_NONE = 3; + /** + * {@link #getCurrentInterruptionFilter() Interruption filter} constant - + * Alarms only interruption filter. + */ + public static final int INTERRUPTION_FILTER_ALARMS = 4; + /** {@link #getCurrentInterruptionFilter() Interruption filter} constant - returned when * the value is unavailable for any reason. For example, before the notification listener * is connected. diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 979a01b..2702457 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -66,6 +66,7 @@ public class ZenModeConfig implements Parcelable { private static final int MINUTES_MS = 60 * SECONDS_MS; private static final int ZERO_VALUE_MS = 10 * SECONDS_MS; + private static final boolean DEFAULT_ALLOW_REMINDERS = true; private static final boolean DEFAULT_ALLOW_EVENTS = true; private static final int XML_VERSION = 1; @@ -75,6 +76,7 @@ public class ZenModeConfig implements Parcelable { private static final String ALLOW_ATT_CALLS = "calls"; private static final String ALLOW_ATT_MESSAGES = "messages"; private static final String ALLOW_ATT_FROM = "from"; + private static final String ALLOW_ATT_REMINDERS = "reminders"; private static final String ALLOW_ATT_EVENTS = "events"; private static final String SLEEP_TAG = "sleep"; private static final String SLEEP_ATT_MODE = "mode"; @@ -100,6 +102,7 @@ public class ZenModeConfig implements Parcelable { public boolean allowCalls; public boolean allowMessages; + public boolean allowReminders = DEFAULT_ALLOW_REMINDERS; public boolean allowEvents = DEFAULT_ALLOW_EVENTS; public int allowFrom = SOURCE_ANYONE; @@ -119,6 +122,7 @@ public class ZenModeConfig implements Parcelable { public ZenModeConfig(Parcel source) { allowCalls = source.readInt() == 1; allowMessages = source.readInt() == 1; + allowReminders = source.readInt() == 1; allowEvents = source.readInt() == 1; if (source.readInt() == 1) { sleepMode = source.readString(); @@ -147,6 +151,7 @@ public class ZenModeConfig implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(allowCalls ? 1 : 0); dest.writeInt(allowMessages ? 1 : 0); + dest.writeInt(allowReminders ? 1 : 0); dest.writeInt(allowEvents ? 1 : 0); if (sleepMode != null) { dest.writeInt(1); @@ -182,6 +187,7 @@ public class ZenModeConfig implements Parcelable { .append("allowCalls=").append(allowCalls) .append(",allowMessages=").append(allowMessages) .append(",allowFrom=").append(sourceToString(allowFrom)) + .append(",allowReminders=").append(allowReminders) .append(",allowEvents=").append(allowEvents) .append(",sleepMode=").append(sleepMode) .append(",sleepStart=").append(sleepStartHour).append('.').append(sleepStartMinute) @@ -217,6 +223,7 @@ public class ZenModeConfig implements Parcelable { return other.allowCalls == allowCalls && other.allowMessages == allowMessages && other.allowFrom == allowFrom + && other.allowReminders == allowReminders && other.allowEvents == allowEvents && Objects.equals(other.sleepMode, sleepMode) && other.sleepNone == sleepNone @@ -232,9 +239,9 @@ public class ZenModeConfig implements Parcelable { @Override public int hashCode() { - return Objects.hash(allowCalls, allowMessages, allowFrom, allowEvents, sleepMode, sleepNone, - sleepStartHour, sleepStartMinute, sleepEndHour, sleepEndMinute, - Arrays.hashCode(conditionComponents), Arrays.hashCode(conditionIds), + return Objects.hash(allowCalls, allowMessages, allowFrom, allowReminders, allowEvents, + sleepMode, sleepNone, sleepStartHour, sleepStartMinute, sleepEndHour, + sleepEndMinute, Arrays.hashCode(conditionComponents), Arrays.hashCode(conditionIds), exitCondition, exitConditionComponent); } @@ -300,6 +307,8 @@ public class ZenModeConfig implements Parcelable { if (ALLOW_TAG.equals(tag)) { rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false); rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false); + rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS, + DEFAULT_ALLOW_REMINDERS); rt.allowEvents = safeBoolean(parser, ALLOW_ATT_EVENTS, DEFAULT_ALLOW_EVENTS); rt.allowFrom = safeInt(parser, ALLOW_ATT_FROM, SOURCE_ANYONE); if (rt.allowFrom < SOURCE_ANYONE || rt.allowFrom > MAX_SOURCE) { @@ -344,6 +353,7 @@ public class ZenModeConfig implements Parcelable { out.startTag(null, ALLOW_TAG); out.attribute(null, ALLOW_ATT_CALLS, Boolean.toString(allowCalls)); out.attribute(null, ALLOW_ATT_MESSAGES, Boolean.toString(allowMessages)); + out.attribute(null, ALLOW_ATT_REMINDERS, Boolean.toString(allowReminders)); out.attribute(null, ALLOW_ATT_EVENTS, Boolean.toString(allowEvents)); out.attribute(null, ALLOW_ATT_FROM, Integer.toString(allowFrom)); out.endTag(null, ALLOW_TAG); diff --git a/core/java/android/service/voice/IVoiceInteractionSession.aidl b/core/java/android/service/voice/IVoiceInteractionSession.aidl index 4f4b2d5..7c90261 100644 --- a/core/java/android/service/voice/IVoiceInteractionSession.aidl +++ b/core/java/android/service/voice/IVoiceInteractionSession.aidl @@ -20,11 +20,13 @@ import android.content.Intent; import android.graphics.Bitmap; import android.os.Bundle; +import com.android.internal.app.IVoiceInteractionSessionShowCallback; + /** * @hide */ oneway interface IVoiceInteractionSession { - void show(in Bundle sessionArgs, int flags); + void show(in Bundle sessionArgs, int flags, IVoiceInteractionSessionShowCallback showCallback); void hide(); void handleAssist(in Bundle assistData); void handleScreenshot(in Bitmap screenshot); diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 419b92b..fee0c75 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -82,6 +82,12 @@ public class VoiceInteractionService extends Service { */ public static final int START_WITH_SCREENSHOT = 1<<1; + /** + * Flag for use with {@link #showSession}: indicate that the session has been started from the + * system assist gesture. + */ + public static final int START_SOURCE_ASSIST_GESTURE = 1<<2; + IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() { @Override public void ready() { mHandler.sendEmptyMessage(MSG_READY); diff --git a/core/java/android/service/voice/VoiceInteractionServiceInfo.java b/core/java/android/service/voice/VoiceInteractionServiceInfo.java index ebc7507..4bc97c9 100644 --- a/core/java/android/service/voice/VoiceInteractionServiceInfo.java +++ b/core/java/android/service/voice/VoiceInteractionServiceInfo.java @@ -43,6 +43,7 @@ public class VoiceInteractionServiceInfo { private String mSessionService; private String mRecognitionService; private String mSettingsActivity; + private boolean mSupportsAssistGesture; public VoiceInteractionServiceInfo(PackageManager pm, ComponentName comp) throws PackageManager.NameNotFoundException { @@ -94,6 +95,9 @@ public class VoiceInteractionServiceInfo { com.android.internal.R.styleable.VoiceInteractionService_recognitionService); mSettingsActivity = array.getString( com.android.internal.R.styleable.VoiceInteractionService_settingsActivity); + mSupportsAssistGesture = array.getBoolean( + com.android.internal.R.styleable.VoiceInteractionService_supportsAssistGesture, + false); array.recycle(); if (mSessionService == null) { mParseError = "No sessionService specified"; @@ -103,11 +107,6 @@ public class VoiceInteractionServiceInfo { mParseError = "No recognitionService specified"; return; } - /* Not yet time - if (mRecognitionService == null) { - mParseError = "No recogitionService specified"; - return; - } */ } catch (XmlPullParserException e) { mParseError = "Error parsing voice interation service meta-data: " + e; Log.w(TAG, "error parsing voice interaction service meta-data", e); @@ -145,4 +144,8 @@ public class VoiceInteractionServiceInfo { public String getSettingsActivity() { return mSettingsActivity; } + + public boolean getSupportsAssistGesture() { + return mSupportsAssistGesture; + } } diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index 7a5bb90..20d7079 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -16,6 +16,7 @@ package android.service.voice; +import android.app.AssistStructure; import android.app.Dialog; import android.app.Instrumentation; import android.app.VoiceInteractor; @@ -43,6 +44,7 @@ import android.view.ViewTreeObserver; import android.view.WindowManager; import android.widget.FrameLayout; import com.android.internal.app.IVoiceInteractionManagerService; +import com.android.internal.app.IVoiceInteractionSessionShowCallback; import com.android.internal.app.IVoiceInteractor; import com.android.internal.app.IVoiceInteractorCallback; import com.android.internal.app.IVoiceInteractorRequest; @@ -163,9 +165,10 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { final IVoiceInteractionSession mSession = new IVoiceInteractionSession.Stub() { @Override - public void show(Bundle sessionArgs, int flags) { - mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIO(MSG_SHOW, - flags, sessionArgs)); + public void show(Bundle sessionArgs, int flags, + IVoiceInteractionSessionShowCallback showCallback) { + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(MSG_SHOW, + flags, sessionArgs, showCallback)); } @Override @@ -175,6 +178,18 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { @Override public void handleAssist(Bundle assistBundle) { + // We want to pre-warm the AssistStructure before handing it off to the main + // thread. There is a strong argument to be made that it should be handed + // through as a separate param rather than part of the assistBundle. + if (assistBundle != null) { + Bundle assistContext = assistBundle.getBundle(Intent.EXTRA_ASSIST_CONTEXT); + if (assistContext != null) { + AssistStructure as = AssistStructure.getAssistStructure(assistContext); + if (as != null) { + as.ensureData(); + } + } + } mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_HANDLE_ASSIST, assistBundle)); } @@ -412,9 +427,12 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { onHandleScreenshot((Bitmap) msg.obj); break; case MSG_SHOW: - if (DEBUG) Log.d(TAG, "doShow: args=" + msg.obj - + " flags=" + msg.arg1); - doShow((Bundle) msg.obj, msg.arg1); + args = (SomeArgs)msg.obj; + if (DEBUG) Log.d(TAG, "doShow: args=" + args.arg1 + + " flags=" + msg.arg1 + + " showCallback=" + args.arg2); + doShow((Bundle) args.arg1, msg.arg1, + (IVoiceInteractionSessionShowCallback) args.arg2); break; case MSG_HIDE: if (DEBUG) Log.d(TAG, "doHide"); @@ -502,6 +520,10 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { mCallbacks, true); } + public Context getContext() { + return mContext; + } + Request newRequest(IVoiceInteractorCallback callback) { synchronized (this) { Request req = new Request(callback, this); @@ -512,11 +534,7 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { Request removeRequest(IBinder reqInterface) { synchronized (this) { - Request req = mActiveRequests.get(reqInterface); - if (req != null) { - mActiveRequests.remove(req); - } - return req; + return mActiveRequests.remove(reqInterface); } } @@ -527,7 +545,7 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { onCreate(args, startFlags); } - void doShow(Bundle args, int flags) { + void doShow(Bundle args, int flags, final IVoiceInteractionSessionShowCallback showCallback) { if (DEBUG) Log.v(TAG, "Showing window: mWindowAdded=" + mWindowAdded + " mWindowVisible=" + mWindowVisible); @@ -552,6 +570,22 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { mWindowVisible = true; mWindow.show(); } + if (showCallback != null) { + mRootView.invalidate(); + mRootView.getViewTreeObserver().addOnPreDrawListener( + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + mRootView.getViewTreeObserver().removeOnPreDrawListener(this); + try { + showCallback.onShown(); + } catch (RemoteException e) { + Log.w(TAG, "Error calling onShown", e); + } + return true; + } + }); + } } finally { mWindowWasVisible = true; mInShowWindow = false; @@ -586,7 +620,8 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { mRootView = mInflater.inflate( com.android.internal.R.layout.voice_interaction_session, null); mRootView.setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); mWindow.setContentView(mRootView); mRootView.getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsComputer); @@ -720,7 +755,9 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { mWindow = new SoftInputWindow(mContext, "VoiceInteractionSession", mTheme, mCallbacks, this, mDispatcherState, WindowManager.LayoutParams.TYPE_VOICE_INTERACTION, Gravity.BOTTOM, true); - mWindow.getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); + mWindow.getWindow().addFlags( + WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN); initViews(); mWindow.getWindow().setLayout(MATCH_PARENT, MATCH_PARENT); mWindow.setToken(mToken); @@ -799,6 +836,12 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { return false; } + /** + * Called when the user presses the back button while focus is in the session UI. Note + * that this will only happen if the session UI has requested input focus in its window; + * otherwise, the back key will go to whatever window has focus and do whatever behavior + * it normally has there. + */ public void onBackPressed() { hide(); } diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java index 1bdaef0..239b386 100644 --- a/core/java/android/text/DynamicLayout.java +++ b/core/java/android/text/DynamicLayout.java @@ -79,7 +79,8 @@ public class DynamicLayout extends Layout boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { this(base, display, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR, - spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth); + spacingmult, spacingadd, includepad, StaticLayout.BREAK_STRATEGY_SIMPLE, + ellipsize, ellipsizedWidth); } /** @@ -95,7 +96,7 @@ public class DynamicLayout extends Layout TextPaint paint, int width, Alignment align, TextDirectionHeuristic textDir, float spacingmult, float spacingadd, - boolean includepad, + boolean includepad, int breakStrategy, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { super((ellipsize == null) ? display @@ -120,6 +121,7 @@ public class DynamicLayout extends Layout mObjects = new PackedObjectVector<Directions>(1); mIncludePad = includepad; + mBreakStrategy = breakStrategy; /* * This is annoying, but we can't refer to the layout until @@ -279,10 +281,9 @@ public class DynamicLayout extends Layout sBuilder = null; } - // TODO: make sure reflowed is properly initialized if (reflowed == null) { reflowed = new StaticLayout(null); - b = StaticLayout.Builder.obtain(); + b = StaticLayout.Builder.obtain(text, where, where + after, getWidth()); } b.setText(text, where, where + after) @@ -292,7 +293,8 @@ public class DynamicLayout extends Layout .setSpacingMult(getSpacingMultiplier()) .setSpacingAdd(getSpacingAdd()) .setEllipsizedWidth(mEllipsizedWidth) - .setEllipsize(mEllipsizeAt); + .setEllipsize(mEllipsizeAt) + .setBreakStrategy(mBreakStrategy); reflowed.generate(b, false, true); int n = reflowed.getLineCount(); @@ -356,6 +358,8 @@ public class DynamicLayout extends Layout ints[DESCENT] = desc; objects[0] = reflowed.getLineDirections(i); + ints[HYPHEN] = reflowed.getHyphen(i); + if (mEllipsize) { ints[ELLIPSIS_START] = reflowed.getEllipsisStart(i); ints[ELLIPSIS_COUNT] = reflowed.getEllipsisCount(i); @@ -631,6 +635,14 @@ public class DynamicLayout extends Layout return mBottomPadding; } + /** + * @hide + */ + @Override + public int getHyphen(int line) { + return mInts.getValue(line, HYPHEN); + } + @Override public int getEllipsizedWidth() { return mEllipsizedWidth; @@ -707,6 +719,7 @@ public class DynamicLayout extends Layout private boolean mEllipsize; private int mEllipsizedWidth; private TextUtils.TruncateAt mEllipsizeAt; + private int mBreakStrategy; private PackedIntVector mInts; private PackedObjectVector<Directions> mObjects; @@ -739,11 +752,12 @@ public class DynamicLayout extends Layout private static final int TAB = START; private static final int TOP = 1; private static final int DESCENT = 2; - private static final int COLUMNS_NORMAL = 3; + private static final int HYPHEN = 3; + private static final int COLUMNS_NORMAL = 4; - private static final int ELLIPSIS_START = 3; - private static final int ELLIPSIS_COUNT = 4; - private static final int COLUMNS_ELLIPSIZE = 5; + private static final int ELLIPSIS_START = 4; + private static final int ELLIPSIS_COUNT = 5; + private static final int COLUMNS_ELLIPSIZE = 6; private static final int START_MASK = 0x1FFFFFFF; private static final int DIR_SHIFT = 30; diff --git a/core/java/android/text/Hyphenator.java b/core/java/android/text/Hyphenator.java new file mode 100644 index 0000000..a99bdf5 --- /dev/null +++ b/core/java/android/text/Hyphenator.java @@ -0,0 +1,87 @@ +/* + * 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.text; + +import android.util.Log; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.HashMap; +import java.util.Locale; + +/** + * Hyphenator is a wrapper class for a native implementation of automatic hyphenation, + * in essence finding valid hyphenation opportunities in a word. + * + * @hide + */ +public class Hyphenator { + // This class has deliberately simple lifetime management (no finalizer) because in + // the common case a process will use a very small number of locales. + + private static String TAG = "Hyphenator"; + + static HashMap<Locale, Hyphenator> sMap = new HashMap<Locale, Hyphenator>(); + + private long mNativePtr; + + private Hyphenator(long nativePtr) { + mNativePtr = nativePtr; + } + + public static long get(Locale locale) { + Hyphenator result = sMap.get(locale); + return result == null ? 0 : result.mNativePtr; + } + + private static Hyphenator loadHyphenator(Locale locale) { + // TODO: find pattern dictionary (from system location) that best matches locale + if (Locale.US.equals(locale)) { + File f = new File(getSystemHyphenatorLocation(), "hyph-en-us.pat.txt"); + try { + RandomAccessFile rf = new RandomAccessFile(f, "r"); + byte[] buf = new byte[(int)rf.length()]; + rf.read(buf); + rf.close(); + String patternData = new String(buf); + long nativePtr = StaticLayout.nLoadHyphenator(patternData); + return new Hyphenator(nativePtr); + } catch (IOException e) { + Log.e(TAG, "error loading hyphenation " + f, e); + } + } + return null; + } + + private static File getSystemHyphenatorLocation() { + // TODO: move to a sensible location under system + return new File("/system/usr/hyphen-data"); + } + + /** + * Load hyphenation patterns at initialization time. We want to have patterns + * for all locales loaded and ready to use so we don't have to do any file IO + * on the UI thread when drawing text in different locales. + * + * @hide + */ + public static void init() { + Locale l = Locale.US; + sMap.put(l, loadHyphenator(l)); + } +} diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index fcf1828..16ae5e2 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -16,6 +16,7 @@ package android.text; +import android.annotation.IntDef; import android.emoji.EmojiFactory; import android.graphics.Canvas; import android.graphics.Paint; @@ -33,6 +34,8 @@ import android.text.style.TabStopSpan; import com.android.internal.util.ArrayUtils; import com.android.internal.util.GrowingArrayUtils; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Arrays; /** @@ -43,6 +46,31 @@ import java.util.Arrays; * For text that will not change, use a {@link StaticLayout}. */ public abstract class Layout { + /** @hide */ + @IntDef({BREAK_STRATEGY_SIMPLE, BREAK_STRATEGY_HIGH_QUALITY, BREAK_STRATEGY_BALANCED}) + @Retention(RetentionPolicy.SOURCE) + public @interface BreakStrategy {} + + /** + * Value for break strategy indicating simple line breaking. Automatic hyphens are not added + * (though soft hyphens are respected), and modifying text generally doesn't affect the layout + * before it (which yields a more consistent user experience when editing), but layout may not + * be the highest quality. + */ + public static final int BREAK_STRATEGY_SIMPLE = 0; + + /** + * Value for break strategy indicating high quality line breaking, including automatic + * hyphenation and doing whole-paragraph optimization of line breaks. + */ + public static final int BREAK_STRATEGY_HIGH_QUALITY = 1; + + /** + * Value for break strategy indicating balanced line breaking. The breaks are chosen to + * make all lines as close to the same length as possible, including automatic hyphenation. + */ + public static final int BREAK_STRATEGY_BALANCED = 2; + private static final ParagraphStyle[] NO_PARA_SPANS = ArrayUtils.emptyArray(ParagraphStyle.class); @@ -225,17 +253,17 @@ public abstract class Layout { // Draw the lines, one at a time. // The baseline is the top of the following line minus the current line's descent. - for (int i = firstLine; i <= lastLine; i++) { + for (int lineNum = firstLine; lineNum <= lastLine; lineNum++) { int start = previousLineEnd; - previousLineEnd = getLineStart(i + 1); - int end = getLineVisibleEnd(i, start, previousLineEnd); + previousLineEnd = getLineStart(lineNum + 1); + int end = getLineVisibleEnd(lineNum, start, previousLineEnd); int ltop = previousLineBottom; - int lbottom = getLineTop(i+1); + int lbottom = getLineTop(lineNum + 1); previousLineBottom = lbottom; - int lbaseline = lbottom - getLineDescent(i); + int lbaseline = lbottom - getLineDescent(lineNum); - int dir = getParagraphDirection(i); + int dir = getParagraphDirection(lineNum); int left = 0; int right = mWidth; @@ -254,7 +282,7 @@ public abstract class Layout { // just collect the ones present at the start of the paragraph. // If spanEnd is before the end of the paragraph, that's not // our problem. - if (start >= spanEnd && (i == firstLine || isFirstParaLine)) { + if (start >= spanEnd && (lineNum == firstLine || isFirstParaLine)) { spanEnd = sp.nextSpanTransition(start, textLength, ParagraphStyle.class); spans = getParagraphSpans(sp, start, spanEnd, ParagraphStyle.class); @@ -280,7 +308,7 @@ public abstract class Layout { int startLine = getLineForOffset(sp.getSpanStart(spans[n])); // if there is more than one LeadingMarginSpan2, use // the count that is greatest - if (i < startLine + count) { + if (lineNum < startLine + count) { useFirstLineMargin = true; break; } @@ -304,7 +332,7 @@ public abstract class Layout { } } - boolean hasTabOrEmoji = getLineContainsTab(i); + boolean hasTabOrEmoji = getLineContainsTab(lineNum); // Can't tell if we have tabs for sure, currently if (hasTabOrEmoji && !tabStopsIsInitialized) { if (tabStops == null) { @@ -333,7 +361,7 @@ public abstract class Layout { x = right; } } else { - int max = (int)getLineExtent(i, tabStops, false); + int max = (int)getLineExtent(lineNum, tabStops, false); if (align == Alignment.ALIGN_OPPOSITE) { if (dir == DIR_LEFT_TO_RIGHT) { x = right - max; @@ -346,7 +374,8 @@ public abstract class Layout { } } - Directions directions = getLineDirections(i); + paint.setHyphenEdit(getHyphen(lineNum)); + Directions directions = getLineDirections(lineNum); if (directions == DIRS_ALL_LEFT_TO_RIGHT && !mSpannedText && !hasTabOrEmoji) { // XXX: assumes there's nothing additional to be done canvas.drawText(buf, start, end, x, lbaseline, paint); @@ -677,6 +706,15 @@ public abstract class Layout { */ public abstract int getBottomPadding(); + /** + * Returns the hyphen edit for a line. + * + * @hide + */ + public int getHyphen(int line) { + return 0; + } + /** * Returns true if the character at offset and the preceding character @@ -1153,7 +1191,10 @@ public abstract class Layout { return end - 1; } - if (ch != ' ' && ch != '\t') { + // Note: keep this in sync with Minikin LineBreaker::isLineEndSpace() + if (!(ch == ' ' || ch == '\t' || ch == 0x1680 || + (0x2000 <= ch && ch <= 0x200A && ch != 0x2007) || + ch == 0x205F || ch == 0x3000)) { break; } diff --git a/core/java/android/text/SpanSet.java b/core/java/android/text/SpanSet.java index 3ca6033..00f1493 100644 --- a/core/java/android/text/SpanSet.java +++ b/core/java/android/text/SpanSet.java @@ -17,6 +17,7 @@ package android.text; import java.lang.reflect.Array; +import java.util.Arrays; /** * A cached set of spans. Caches the result of {@link Spanned#getSpans(int, int, Class)} and then @@ -54,6 +55,7 @@ public class SpanSet<E> { spanFlags = new int[length]; } + int prevNumberOfSpans = numberOfSpans; numberOfSpans = 0; for (int i = 0; i < length; i++) { final E span = allSpans[i]; @@ -71,6 +73,12 @@ public class SpanSet<E> { numberOfSpans++; } + + // cleanup extra spans left over from previous init() call + if (numberOfSpans < prevNumberOfSpans) { + // prevNumberofSpans was > 0, therefore spans != null + Arrays.fill(spans, numberOfSpans, prevNumberOfSpans, null); + } } /** @@ -103,9 +111,8 @@ public class SpanSet<E> { * Removes all internal references to the spans to avoid memory leaks. */ public void recycle() { - // The spans array is guaranteed to be not null when numberOfSpans is > 0 - for (int i = 0; i < numberOfSpans; i++) { - spans[i] = null; // prevent a leak: no reference kept when TextLine is recycled + if (spans != null) { + Arrays.fill(spans, 0, numberOfSpans, null); } } } diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index ee39e27..2bcb352 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -23,6 +23,7 @@ import android.text.style.LineHeightSpan; import android.text.style.MetricAffectingSpan; import android.text.style.TabStopSpan; import android.util.Log; +import android.util.Pools.SynchronizedPool; import com.android.internal.util.ArrayUtils; import com.android.internal.util.GrowingArrayUtils; @@ -56,28 +57,23 @@ public class StaticLayout extends Layout { mNativePtr = nNewBuilder(); } - static Builder obtain() { - Builder b = null; - synchronized (sLock) { - for (int i = 0; i < sCached.length; i++) { - if (sCached[i] != null) { - b = sCached[i]; - sCached[i] = null; - break; - } - } - } + public static Builder obtain(CharSequence source, int start, int end, int width) { + Builder b = sPool.acquire(); if (b == null) { b = new Builder(); } // set default initial values - b.mWidth = 0; + b.mText = source; + b.mStart = start; + b.mEnd = end; + b.mWidth = width; + b.mAlignment = Alignment.ALIGN_NORMAL; b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR; b.mSpacingMult = 1.0f; b.mSpacingAdd = 0.0f; b.mIncludePad = true; - b.mEllipsizedWidth = 0; + b.mEllipsizedWidth = width; b.mEllipsize = null; b.mMaxLines = Integer.MAX_VALUE; @@ -85,18 +81,11 @@ public class StaticLayout extends Layout { return b; } - static void recycle(Builder b) { + private static void recycle(Builder b) { b.mPaint = null; b.mText = null; MeasuredText.recycle(b.mMeasuredText); - synchronized (sLock) { - for (int i = 0; i < sCached.length; i++) { - if (sCached[i] == null) { - sCached[i] = b; - break; - } - } - } + sPool.release(b); } // release any expensive state @@ -129,6 +118,11 @@ public class StaticLayout extends Layout { return this; } + public Builder setAlignment(Alignment alignment) { + mAlignment = alignment; + return this; + } + public Builder setTextDir(TextDirectionHeuristic textDir) { mTextDir = textDir; return this; @@ -166,12 +160,19 @@ public class StaticLayout extends Layout { return this; } + public Builder setBreakStrategy(@BreakStrategy int breakStrategy) { + mBreakStrategy = breakStrategy; + return this; + } + /** * Measurement and break iteration is done in native code. The protocol for using * the native code is as follows. * - * For each paragraph, do a nSetText of the paragraph text. Then, for each run within the - * paragraph: + * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab + * stops, break strategy (and possibly other parameters in the future). + * + * Then, for each run within the paragraph: * - setLocale (this must be done at least for the first run, optional afterwards) * - one of the following, depending on the type of run: * + addStyleRun (a text run, to be measured in native code) @@ -186,7 +187,7 @@ public class StaticLayout extends Layout { private void setLocale(Locale locale) { if (!locale.equals(mLocale)) { - nSetLocale(mNativePtr, locale.toLanguageTag()); + nSetLocale(mNativePtr, locale.toLanguageTag(), Hyphenator.get(locale)); mLocale = locale; } } @@ -205,10 +206,8 @@ public class StaticLayout extends Layout { } public StaticLayout build() { - // TODO: can optimize based on whether ellipsis is needed - StaticLayout result = new StaticLayout(mText); - result.generate(this, this.mIncludePad, this.mIncludePad); - recycle(this); + StaticLayout result = new StaticLayout(this); + Builder.recycle(this); return result; } @@ -228,6 +227,7 @@ public class StaticLayout extends Layout { int mEnd; TextPaint mPaint; int mWidth; + Alignment mAlignment; TextDirectionHeuristic mTextDir; float mSpacingMult; float mSpacingAdd; @@ -235,6 +235,7 @@ public class StaticLayout extends Layout { int mEllipsizedWidth; TextUtils.TruncateAt mEllipsize; int mMaxLines; + int mBreakStrategy; Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt(); @@ -243,8 +244,7 @@ public class StaticLayout extends Layout { Locale mLocale; - private static final Object sLock = new Object(); - private static final Builder[] sCached = new Builder[3]; + private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3); } public StaticLayout(CharSequence source, TextPaint paint, @@ -314,10 +314,9 @@ public class StaticLayout extends Layout { : new Ellipsizer(source), paint, outerwidth, align, textDir, spacingmult, spacingadd); - Builder b = Builder.obtain(); - b.setText(source, bufstart, bufend) + Builder b = Builder.obtain(source, bufstart, bufend, outerwidth) .setPaint(paint) - .setWidth(outerwidth) + .setAlignment(align) .setTextDir(textDir) .setSpacingMult(spacingmult) .setSpacingAdd(spacingadd) @@ -364,6 +363,35 @@ public class StaticLayout extends Layout { mLines = new int[mLineDirections.length]; } + private StaticLayout(Builder b) { + super((b.mEllipsize == null) + ? b.mText + : (b.mText instanceof Spanned) + ? new SpannedEllipsizer(b.mText) + : new Ellipsizer(b.mText), + b.mPaint, b.mWidth, b.mAlignment, b.mSpacingMult, b.mSpacingAdd); + + if (b.mEllipsize != null) { + Ellipsizer e = (Ellipsizer) getText(); + + e.mLayout = this; + e.mWidth = b.mEllipsizedWidth; + e.mMethod = b.mEllipsize; + mEllipsizedWidth = b.mEllipsizedWidth; + + mColumns = COLUMNS_ELLIPSIZE; + } else { + mColumns = COLUMNS_NORMAL; + mEllipsizedWidth = b.mWidth; + } + + mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns); + mLines = new int[mLineDirections.length]; + mMaximumVisibleLineCount = b.mMaxLines; + + generate(b, b.mIncludePad, b.mIncludePad); + } + /* package */ void generate(Builder b, boolean includepad, boolean trackpad) { CharSequence source = b.mText; int bufStart = b.mStart; @@ -459,7 +487,25 @@ public class StaticLayout extends Layout { byte[] chdirs = measured.mLevels; int dir = measured.mDir; boolean easy = measured.mEasy; - nSetText(b.mNativePtr, chs, paraEnd - paraStart); + + // tab stop locations + int[] variableTabStops = null; + if (spanned != null) { + TabStopSpan[] spans = getParagraphSpans(spanned, paraStart, + paraEnd, TabStopSpan.class); + if (spans.length > 0) { + int[] stops = new int[spans.length]; + for (int i = 0; i < spans.length; i++) { + stops[i] = spans[i].getTabStop(); + } + Arrays.sort(stops, 0, stops.length); + variableTabStops = stops; + } + } + + nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart, + firstWidth, firstWidthLineCount, restWidth, + variableTabStops, TAB_INCREMENT, b.mBreakStrategy); // measurement has to be done before performing line breaking // but we don't want to recompute fontmetrics or span ranges the @@ -505,29 +551,13 @@ public class StaticLayout extends Layout { spanEndCacheCount++; } - // tab stop locations - int[] variableTabStops = null; - if (spanned != null) { - TabStopSpan[] spans = getParagraphSpans(spanned, paraStart, - paraEnd, TabStopSpan.class); - if (spans.length > 0) { - int[] stops = new int[spans.length]; - for (int i = 0; i < spans.length; i++) { - stops[i] = spans[i].getTabStop(); - } - Arrays.sort(stops, 0, stops.length); - variableTabStops = stops; - } - } - nGetWidths(b.mNativePtr, widths); - int breakCount = nComputeLineBreaks(b.mNativePtr, paraEnd - paraStart, firstWidth, - firstWidthLineCount, restWidth, variableTabStops, TAB_INCREMENT, false, lineBreaks, - lineBreaks.breaks, lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length); + int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks, + lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length); int[] breaks = lineBreaks.breaks; float[] lineWidths = lineBreaks.widths; - boolean[] flags = lineBreaks.flags; + int[] flags = lineBreaks.flags; // here is the offset of the starting character of the line we are currently measuring int here = paraStart; @@ -613,7 +643,7 @@ public class StaticLayout extends Layout { fm.top, fm.bottom, v, spacingmult, spacingadd, null, - null, fm, false, + null, fm, 0, needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd, includepad, trackpad, null, null, bufStart, ellipsize, @@ -625,7 +655,7 @@ public class StaticLayout extends Layout { int above, int below, int top, int bottom, int v, float spacingmult, float spacingadd, LineHeightSpan[] chooseHt, int[] chooseHtv, - Paint.FontMetricsInt fm, boolean hasTabOrEmoji, + Paint.FontMetricsInt fm, int flags, boolean needMultiply, byte[] chdirs, int dir, boolean easy, int bufEnd, boolean includePad, boolean trackPad, char[] chs, @@ -718,8 +748,10 @@ public class StaticLayout extends Layout { lines[off + mColumns + START] = end; lines[off + mColumns + TOP] = v; - if (hasTabOrEmoji) - lines[off + TAB] |= TAB_MASK; + // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining + // one bit for start field + lines[off + TAB] |= flags & TAB_MASK; + lines[off + HYPHEN] = flags; lines[off + DIR] |= dir << DIR_SHIFT; Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT; @@ -938,6 +970,14 @@ public class StaticLayout extends Layout { return mBottomPadding; } + /** + * @hide + */ + @Override + public int getHyphen(int line) { + return mLines[mColumns * line + HYPHEN] & 0xff; + } + @Override public int getEllipsisCount(int line) { if (mColumns < COLUMNS_ELLIPSIZE) { @@ -964,9 +1004,15 @@ public class StaticLayout extends Layout { private static native long nNewBuilder(); private static native void nFreeBuilder(long nativePtr); private static native void nFinishBuilder(long nativePtr); - private static native void nSetLocale(long nativePtr, String locale); - private static native void nSetText(long nativePtr, char[] text, int length); + /* package */ static native long nLoadHyphenator(String patternData); + + private static native void nSetLocale(long nativePtr, String locale, long nativeHyphenator); + + // Set up paragraph text and settings; done as one big method to minimize jni crossings + private static native void nSetupParagraph(long nativePtr, char[] text, int length, + float firstWidth, int firstWidthLineCount, float restWidth, + int[] variableTabStops, int defaultTabStop, int breakStrategy); private static native float nAddStyleRun(long nativePtr, long nativePaint, long nativeTypeface, int start, int end, boolean isRtl); @@ -983,25 +1029,24 @@ public class StaticLayout extends Layout { // the arrays inside the LineBreaks objects are passed in as well // to reduce the number of JNI calls in the common case where the // arrays do not have to be resized - private static native int nComputeLineBreaks(long nativePtr, - int length, float firstWidth, int firstWidthLineCount, float restWidth, - int[] variableTabStops, int defaultTabStop, boolean optimize, LineBreaks recycle, - int[] recycleBreaks, float[] recycleWidths, boolean[] recycleFlags, int recycleLength); + private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle, + int[] recycleBreaks, float[] recycleWidths, int[] recycleFlags, int recycleLength); private int mLineCount; private int mTopPadding, mBottomPadding; private int mColumns; private int mEllipsizedWidth; - private static final int COLUMNS_NORMAL = 3; - private static final int COLUMNS_ELLIPSIZE = 5; + private static final int COLUMNS_NORMAL = 4; + private static final int COLUMNS_ELLIPSIZE = 6; private static final int START = 0; private static final int DIR = START; private static final int TAB = START; private static final int TOP = 1; private static final int DESCENT = 2; - private static final int ELLIPSIS_START = 3; - private static final int ELLIPSIS_COUNT = 4; + private static final int HYPHEN = 3; + private static final int ELLIPSIS_START = 4; + private static final int ELLIPSIS_COUNT = 5; private int[] mLines; private Directions[] mLineDirections; @@ -1023,7 +1068,7 @@ public class StaticLayout extends Layout { private static final int INITIAL_SIZE = 16; public int[] breaks = new int[INITIAL_SIZE]; public float[] widths = new float[INITIAL_SIZE]; - public boolean[] flags = new boolean[INITIAL_SIZE]; // hasTabOrEmoji + public int[] flags = new int[INITIAL_SIZE]; // hasTabOrEmoji // breaks, widths, and flags should all have the same length } diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index 4725581..479242c 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -955,6 +955,10 @@ class TextLine { span.updateDrawState(wp); } + // Only draw hyphen on last run in line + if (jnext < mLen) { + wp.setHyphenEdit(0); + } x += handleText(wp, j, jnext, i, inext, runIsRtl, c, x, top, y, bottom, fmi, needWidth || jnext < measureLimit); } diff --git a/core/java/android/text/format/Time.java b/core/java/android/text/format/Time.java index 0c66709..d567d90 100644 --- a/core/java/android/text/format/Time.java +++ b/core/java/android/text/format/Time.java @@ -47,6 +47,7 @@ import libcore.util.ZoneInfoDB; * before 1st Jan 1970 UTC).</li> * <li>Much of the formatting / parsing assumes ASCII text and is therefore not suitable for * use with non-ASCII scripts.</li> + * <li>No support for pseudo-zones like "GMT-07:00".</li> * </ul> * * @deprecated Use {@link java.util.GregorianCalendar} instead. diff --git a/core/java/android/transition/Fade.java b/core/java/android/transition/Fade.java index e7857c0..287c696 100644 --- a/core/java/android/transition/Fade.java +++ b/core/java/android/transition/Fade.java @@ -108,7 +108,7 @@ public class Fade extends Visibility { /** * Utility method to handle creating and running the Animator. */ - private Animator createAnimation(View view, float startAlpha, float endAlpha) { + private Animator createAnimation(final View view, float startAlpha, final float endAlpha) { if (startAlpha == endAlpha) { return null; } @@ -117,9 +117,15 @@ public class Fade extends Visibility { if (DBG) { Log.d(LOG_TAG, "Created animator " + anim); } - FadeAnimatorListener listener = new FadeAnimatorListener(view); + final FadeAnimatorListener listener = new FadeAnimatorListener(view); anim.addListener(listener); anim.addPauseListener(listener); + addListener(new TransitionListenerAdapter() { + @Override + public void onTransitionEnd(Transition transition) { + view.setTransitionAlpha(1); + } + }); return anim; } @@ -143,7 +149,6 @@ public class Fade extends Visibility { private static class FadeAnimatorListener extends AnimatorListenerAdapter { private final View mView; - private boolean mCanceled = false; private float mPausedAlpha = -1; private boolean mLayerTypeChanged = false; @@ -160,18 +165,8 @@ public class Fade extends Visibility { } @Override - public void onAnimationCancel(Animator animator) { - mCanceled = true; - if (mPausedAlpha >= 0) { - mView.setTransitionAlpha(mPausedAlpha); - } - } - - @Override public void onAnimationEnd(Animator animator) { - if (!mCanceled) { - mView.setTransitionAlpha(1); - } + mView.setTransitionAlpha(1); if (mLayerTypeChanged) { mView.setLayerType(View.LAYER_TYPE_NONE, null); } diff --git a/core/java/android/util/DebugUtils.java b/core/java/android/util/DebugUtils.java index 84d9ce8..c44f42b 100644 --- a/core/java/android/util/DebugUtils.java +++ b/core/java/android/util/DebugUtils.java @@ -17,8 +17,10 @@ package android.util; import java.io.PrintWriter; -import java.lang.reflect.Method; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.util.Locale; /** @@ -203,4 +205,57 @@ public class DebugUtils { outBuilder.append(suffix); return outBuilder.toString(); } + + /** + * Use prefixed constants (static final values) on given class to turn value + * into human-readable string. + * + * @hide + */ + public static String valueToString(Class<?> clazz, String prefix, int value) { + for (Field field : clazz.getDeclaredFields()) { + final int modifiers = field.getModifiers(); + if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers) + && field.getType().equals(int.class) && field.getName().startsWith(prefix)) { + try { + if (value == field.getInt(null)) { + return field.getName().substring(prefix.length()); + } + } catch (IllegalAccessException ignored) { + } + } + } + return Integer.toString(value); + } + + /** + * Use prefixed constants (static final values) on given class to turn flags + * into human-readable string. + * + * @hide + */ + public static String flagsToString(Class<?> clazz, String prefix, int flags) { + final StringBuilder res = new StringBuilder(); + + for (Field field : clazz.getDeclaredFields()) { + final int modifiers = field.getModifiers(); + if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers) + && field.getType().equals(int.class) && field.getName().startsWith(prefix)) { + try { + final int value = field.getInt(null); + if ((flags & value) != 0) { + flags &= ~value; + res.append(field.getName().substring(prefix.length())).append('|'); + } + } catch (IllegalAccessException ignored) { + } + } + } + if (flags != 0 || res.length() == 0) { + res.append(Integer.toHexString(flags)); + } else { + res.deleteCharAt(res.length() - 1); + } + return res.toString(); + } } diff --git a/core/java/android/util/MathUtils.java b/core/java/android/util/MathUtils.java index 36d5b50..8b57d3d 100644 --- a/core/java/android/util/MathUtils.java +++ b/core/java/android/util/MathUtils.java @@ -20,7 +20,7 @@ import java.util.Random; /** * A class that contains utility methods related to numbers. - * + * * @hide Pending API council approval */ public final class MathUtils { @@ -32,7 +32,7 @@ public final class MathUtils { } public static float abs(float v) { - return v > 0 ? v : -v; + return v > 0 ? v : -v; } public static int constrain(int amount, int low, int high) { @@ -116,6 +116,14 @@ public final class MathUtils { return v * v; } + public static float dot(float v1x, float v1y, float v2x, float v2y) { + return v1x * v2x + v1y * v2y; + } + + public static float cross(float v1x, float v1y, float v2x, float v2y) { + return v1x * v2y - v1y * v2x; + } + public static float radians(float degrees) { return degrees * DEG_TO_RAD; } @@ -142,16 +150,16 @@ public final class MathUtils { public static float tan(float angle) { return (float) Math.tan(angle); - } + } public static float lerp(float start, float stop, float amount) { return start + (stop - start) * amount; } - + public static float norm(float start, float stop, float value) { return (value - start) / (stop - start); } - + public static float map(float minStart, float minStop, float maxStart, float maxStop, float value) { return maxStart + (maxStart - maxStop) * ((value - minStart) / (minStop - minStart)); } @@ -164,7 +172,7 @@ public final class MathUtils { if (howsmall >= howbig) return howsmall; return (int) (sRandom.nextFloat() * (howbig - howsmall) + howsmall); } - + public static float random(float howbig) { return sRandom.nextFloat() * howbig; } diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index c8149d9..79a8489 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -71,7 +71,12 @@ import java.io.PrintWriter; */ public final class Choreographer { private static final String TAG = "Choreographer"; - private static final boolean DEBUG = false; + + // Prints debug messages about jank which was detected (low volume). + private static final boolean DEBUG_JANK = false; + + // Prints debug messages about every frame and callback registered (high volume). + private static final boolean DEBUG_FRAMES = false; // The default amount of time in ms between animation frames. // When vsync is not enabled, we want to have some idea of how long we should @@ -139,6 +144,7 @@ public final class Choreographer { private boolean mCallbacksRunning; private long mLastFrameTimeNanos; private long mFrameIntervalNanos; + private boolean mDebugPrintNextFrameTimeDelta; /** * Contains information about the current frame for jank-tracking, @@ -166,13 +172,25 @@ public final class Choreographer { public static final int CALLBACK_ANIMATION = 1; /** - * Callback type: Traversal callback. Handles layout and draw. Runs last + * Callback type: Traversal callback. Handles layout and draw. Runs * after all other asynchronous messages have been handled. * @hide */ public static final int CALLBACK_TRAVERSAL = 2; - private static final int CALLBACK_LAST = CALLBACK_TRAVERSAL; + /** + * Callback type: Commit callback. Handles post-draw operations for the frame. + * Runs after traversal completes. The {@link #getFrameTime() frame time} reported + * during this callback may be updated to reflect delays that occurred while + * traversals were in progress in case heavy layout operations caused some frames + * to be skipped. The frame time reported during this callback provides a better + * estimate of the start time of the frame in which animations (and other updates + * to the view hierarchy state) actually took effect. + * @hide + */ + public static final int CALLBACK_COMMIT = 3; + + private static final int CALLBACK_LAST = CALLBACK_COMMIT; private Choreographer(Looper looper) { mLooper = looper; @@ -332,7 +350,7 @@ public final class Choreographer { private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) { - if (DEBUG) { + if (DEBUG_FRAMES) { Log.d(TAG, "PostCallback: type=" + callbackType + ", action=" + action + ", token=" + token + ", delayMillis=" + delayMillis); @@ -376,7 +394,7 @@ public final class Choreographer { } private void removeCallbacksInternal(int callbackType, Object action, Object token) { - if (DEBUG) { + if (DEBUG_FRAMES) { Log.d(TAG, "RemoveCallbacks: type=" + callbackType + ", action=" + action + ", token=" + token); } @@ -492,7 +510,7 @@ public final class Choreographer { if (!mFrameScheduled) { mFrameScheduled = true; if (USE_VSYNC) { - if (DEBUG) { + if (DEBUG_FRAMES) { Log.d(TAG, "Scheduling next frame on vsync."); } @@ -509,7 +527,7 @@ public final class Choreographer { } else { final long nextFrameTime = Math.max( mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now); - if (DEBUG) { + if (DEBUG_FRAMES) { Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms."); } Message msg = mHandler.obtainMessage(MSG_DO_FRAME); @@ -526,6 +544,12 @@ public final class Choreographer { return; // no work to do } + if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) { + mDebugPrintNextFrameTimeDelta = false; + Log.d(TAG, "Frame time delta: " + + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms"); + } + long intendedFrameTimeNanos = frameTimeNanos; startNanos = System.nanoTime(); final long jitterNanos = startNanos - frameTimeNanos; @@ -536,7 +560,7 @@ public final class Choreographer { + "The application may be doing too much work on its main thread."); } final long lastFrameOffset = jitterNanos % mFrameIntervalNanos; - if (DEBUG) { + if (DEBUG_JANK) { Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms " + "which is more than the frame interval of " + (mFrameIntervalNanos * 0.000001f) + " ms! " @@ -547,7 +571,7 @@ public final class Choreographer { } if (frameTimeNanos < mLastFrameTimeNanos) { - if (DEBUG) { + if (DEBUG_JANK) { Log.d(TAG, "Frame time appears to be going backwards. May be due to a " + "previously skipped frame. Waiting for next vsync."); } @@ -569,7 +593,9 @@ public final class Choreographer { mFrameInfo.markPerformTraversalsStart(); doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); - if (DEBUG) { + doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); + + if (DEBUG_FRAMES) { final long endNanos = System.nanoTime(); Log.d(TAG, "Frame " + frame + ": Finished, took " + (endNanos - startNanos) * 0.000001f + " ms, latency " @@ -583,16 +609,43 @@ public final class Choreographer { // We use "now" to determine when callbacks become due because it's possible // for earlier processing phases in a frame to post callbacks that should run // in a following phase, such as an input event that causes an animation to start. - final long now = SystemClock.uptimeMillis(); - callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now); + final long now = System.nanoTime(); + callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked( + now / TimeUtils.NANOS_PER_MS); if (callbacks == null) { return; } mCallbacksRunning = true; + + // Update the frame time if necessary when committing the frame. + // We only update the frame time if we are more than 2 frames late reaching + // the commit phase. This ensures that the frame time which is observed by the + // callbacks will always increase from one frame to the next and never repeat. + // We never want the next frame's starting frame time to end up being less than + // or equal to the previous frame's commit frame time. Keep in mind that the + // next frame has most likely already been scheduled by now so we play it + // safe by ensuring the commit time is always at least one frame behind. + if (callbackType == Choreographer.CALLBACK_COMMIT) { + final long jitterNanos = now - frameTimeNanos; + if (jitterNanos >= 2 * mFrameIntervalNanos) { + final long lastFrameOffset = jitterNanos % mFrameIntervalNanos + + mFrameIntervalNanos; + if (DEBUG_JANK) { + Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f) + + " ms which is more than twice the frame interval of " + + (mFrameIntervalNanos * 0.000001f) + " ms! " + + "Setting frame time to " + (lastFrameOffset * 0.000001f) + + " ms in the past."); + mDebugPrintNextFrameTimeDelta = true; + } + frameTimeNanos = now - lastFrameOffset; + mLastFrameTimeNanos = frameTimeNanos; + } + } } try { for (CallbackRecord c = callbacks; c != null; c = c.next) { - if (DEBUG) { + if (DEBUG_FRAMES) { Log.d(TAG, "RunCallback: type=" + callbackType + ", action=" + c.action + ", token=" + c.token + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime)); diff --git a/core/java/android/view/DisplayListCanvas.java b/core/java/android/view/DisplayListCanvas.java index 3caf6f0..ec8f802 100644 --- a/core/java/android/view/DisplayListCanvas.java +++ b/core/java/android/view/DisplayListCanvas.java @@ -48,7 +48,6 @@ public class DisplayListCanvas extends Canvas { private int mWidth; private int mHeight; - static DisplayListCanvas obtain(@NonNull RenderNode node) { if (node == null) throw new IllegalArgumentException("node cannot be null"); DisplayListCanvas canvas = sPool.acquire(); diff --git a/core/java/android/view/IGraphicsStats.aidl b/core/java/android/view/IGraphicsStats.aidl new file mode 100644 index 0000000..c235eb2 --- /dev/null +++ b/core/java/android/view/IGraphicsStats.aidl @@ -0,0 +1,26 @@ +/** + * 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.view; + +import android.os.ParcelFileDescriptor; + +/** + * @hide + */ +interface IGraphicsStats { + ParcelFileDescriptor requestBufferForProcess(String packageName, IBinder token); +} diff --git a/core/java/android/view/PhoneWindow.java b/core/java/android/view/PhoneWindow.java index cb32697..38f4d1c 100644 --- a/core/java/android/view/PhoneWindow.java +++ b/core/java/android/view/PhoneWindow.java @@ -29,6 +29,7 @@ import android.os.UserHandle; import com.android.internal.R; import com.android.internal.util.ScreenShapeHelper; +import com.android.internal.view.FloatingActionMode; import com.android.internal.view.RootViewSurfaceTaker; import com.android.internal.view.StandaloneActionMode; import com.android.internal.view.menu.ContextMenuBuilder; @@ -41,6 +42,7 @@ import com.android.internal.view.menu.MenuView; import com.android.internal.widget.ActionBarContextView; import com.android.internal.widget.BackgroundFallback; import com.android.internal.widget.DecorContentParent; +import com.android.internal.widget.FloatingToolbar; import com.android.internal.widget.SwipeDismissLayout; import android.app.ActivityManager; @@ -2179,6 +2181,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private ActionBarContextView mPrimaryActionModeView; private PopupWindow mPrimaryActionModePopup; private Runnable mShowPrimaryActionModePopup; + private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener; + private View mFloatingActionModeOriginatingView; + private FloatingToolbar mFloatingToolbar; // View added at runtime to draw under the status bar area private View mStatusGuard; @@ -2703,18 +2708,18 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (mode.getType() == ActionMode.TYPE_PRIMARY) { cleanupPrimaryActionMode(); mPrimaryActionMode = mode; - } else { + } else if (mode.getType() == ActionMode.TYPE_FLOATING) { + if (mFloatingActionMode != null) { + mFloatingActionMode.finish(); + } mFloatingActionMode = mode; } } else { - if (type == ActionMode.TYPE_PRIMARY) { - cleanupPrimaryActionMode(); - mode = createStandaloneActionMode(wrappedCallback); - if (mode != null && callback.onCreateActionMode(mode, mode.getMenu())) { - setHandledPrimaryActionMode(mode); - } else { - mode = null; - } + mode = createActionMode(type, wrappedCallback, originatingView); + if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) { + setHandledActionMode(mode); + } else { + mode = null; } } if (mode != null && getCallback() != null && !isDestroyed()) { @@ -2737,6 +2742,21 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } } + private void cleanupFloatingActionModeViews() { + if (mFloatingToolbar != null) { + mFloatingToolbar.dismiss(); + mFloatingToolbar = null; + } + if (mFloatingActionModeOriginatingView != null) { + if (mFloatingToolbarPreDrawListener != null) { + mFloatingActionModeOriginatingView.getViewTreeObserver() + .removeOnPreDrawListener(mFloatingToolbarPreDrawListener); + mFloatingToolbarPreDrawListener = null; + } + mFloatingActionModeOriginatingView = null; + } + } + public void startChanging() { mChanging = true; } @@ -3128,6 +3148,14 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (cb != null && !isDestroyed() && mFeatureId < 0) { cb.onWindowFocusChanged(hasWindowFocus); } + + if (mFloatingToolbar != null) { + if (hasWindowFocus) { + mFloatingToolbar.show(); + } else { + mFloatingToolbar.dismiss(); + } + } } void updateWindowResizeState() { @@ -3179,6 +3207,10 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } mPrimaryActionModePopup = null; } + if (mFloatingToolbar != null) { + mFloatingToolbar.dismiss(); + mFloatingToolbar = null; + } PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); if (st != null && st.menu != null && mFeatureId < 0) { @@ -3220,7 +3252,27 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { updateColorViewTranslations(); } + private ActionMode createActionMode( + int type, ActionMode.Callback2 callback, View originatingView) { + switch (type) { + case ActionMode.TYPE_PRIMARY: + default: + return createStandaloneActionMode(callback); + case ActionMode.TYPE_FLOATING: + return createFloatingActionMode(originatingView, callback); + } + } + + private void setHandledActionMode(ActionMode mode) { + if (mode.getType() == ActionMode.TYPE_PRIMARY) { + setHandledPrimaryActionMode(mode); + } else if (mode.getType() == ActionMode.TYPE_FLOATING) { + setHandledFloatingActionMode(mode); + } + } + private ActionMode createStandaloneActionMode(ActionMode.Callback callback) { + cleanupPrimaryActionMode(); if (mPrimaryActionModeView == null) { if (isFloating()) { // Use the action bar theme. @@ -3291,6 +3343,35 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); } + private ActionMode createFloatingActionMode( + View originatingView, ActionMode.Callback2 callback) { + if (mFloatingActionMode != null) { + mFloatingActionMode.finish(); + } + cleanupFloatingActionModeViews(); + mFloatingToolbar = new FloatingToolbar(mContext, PhoneWindow.this); + final FloatingActionMode mode = new FloatingActionMode( + mContext, callback, originatingView, mFloatingToolbar); + mFloatingActionModeOriginatingView = originatingView; + mFloatingToolbarPreDrawListener = + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + mode.updateViewLocationInWindow(); + return true; + } + }; + return mode; + } + + private void setHandledFloatingActionMode(ActionMode mode) { + mFloatingActionMode = mode; + mFloatingActionMode.invalidate(); + mFloatingToolbar.show(); + mFloatingActionModeOriginatingView.getViewTreeObserver() + .addOnPreDrawListener(mFloatingToolbarPreDrawListener); + } + /** * Clears out internal references when the action mode is destroyed. */ @@ -3328,6 +3409,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } mPrimaryActionMode = null; } else if (mode == mFloatingActionMode) { + cleanupFloatingActionModeViews(); mFloatingActionMode = null; } if (getCallback() != null && !isDestroyed()) { @@ -3339,6 +3421,15 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } requestFitSystemWindows(); } + + @Override + public void onGetContentRect(ActionMode mode, View view, Rect outRect) { + if (mWrapped instanceof ActionMode.Callback2) { + ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect); + } else { + super.onGetContentRect(mode, view, outRect); + } + } } } diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java index ef98bbc..236cfef 100644 --- a/core/java/android/view/RenderNode.java +++ b/core/java/android/view/RenderNode.java @@ -240,12 +240,7 @@ public class RenderNode { * @see #start(int, int) * @see #isValid() */ - public void end(DisplayListCanvas endCanvas) { - if (!(endCanvas instanceof DisplayListCanvas)) { - throw new IllegalArgumentException("Passed an invalid canvas to end!"); - } - - DisplayListCanvas canvas = (DisplayListCanvas) endCanvas; + public void end(DisplayListCanvas canvas) { canvas.onPostDraw(); long renderNodeData = canvas.finishRecording(); nSetDisplayListData(mNativeRenderNode, renderNodeData); diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java index 6508cca..5cf2c5c 100644 --- a/core/java/android/view/ScaleGestureDetector.java +++ b/core/java/android/view/ScaleGestureDetector.java @@ -130,6 +130,7 @@ public class ScaleGestureDetector { private float mFocusY; private boolean mQuickScaleEnabled; + private boolean mButtonScaleEnabled; private float mCurrSpan; private float mPrevSpan; @@ -151,14 +152,17 @@ public class ScaleGestureDetector { private int mTouchHistoryDirection; private long mTouchHistoryLastAcceptedTime; private int mTouchMinMajor; - private MotionEvent mDoubleTapEvent; - private int mDoubleTapMode = DOUBLE_TAP_MODE_NONE; private final Handler mHandler; + private float mAnchoredScaleStartX; + private float mAnchoredScaleStartY; + private int mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE; + private static final long TOUCH_STABILIZE_TIME = 128; // ms - private static final int DOUBLE_TAP_MODE_NONE = 0; - private static final int DOUBLE_TAP_MODE_IN_PROGRESS = 1; 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; /** @@ -310,8 +314,17 @@ public class ScaleGestureDetector { mGestureDetector.onTouchEvent(event); } + 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) + && (event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0; + + final boolean anchoredScaleCancelled = + mAnchoredScaleMode == ANCHORED_SCALE_MODE_BUTTON && !isAnchoredScaleButtonDown; final boolean streamComplete = action == MotionEvent.ACTION_UP || - action == MotionEvent.ACTION_CANCEL; + action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled; if (action == MotionEvent.ACTION_DOWN || streamComplete) { // Reset any scale in progress with the listener. @@ -321,11 +334,11 @@ public class ScaleGestureDetector { mListener.onScaleEnd(this); mInProgress = false; mInitialSpan = 0; - mDoubleTapMode = DOUBLE_TAP_MODE_NONE; - } else if (mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS && streamComplete) { + mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE; + } else if (inAnchoredScaleMode() && streamComplete) { mInProgress = false; mInitialSpan = 0; - mDoubleTapMode = DOUBLE_TAP_MODE_NONE; + mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE; } if (streamComplete) { @@ -334,25 +347,32 @@ public class ScaleGestureDetector { } } + if (!mInProgress && mButtonScaleEnabled && !inAnchoredScaleMode() + && !streamComplete && isAnchoredScaleButtonDown) { + // Start of a button scale gesture + mAnchoredScaleStartX = event.getX(); + mAnchoredScaleStartY = event.getY(); + mAnchoredScaleMode = ANCHORED_SCALE_MODE_BUTTON; + mInitialSpan = 0; + } + final boolean configChanged = action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_UP || - action == MotionEvent.ACTION_POINTER_DOWN; - + action == MotionEvent.ACTION_POINTER_DOWN || anchoredScaleCancelled; final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP; final int skipIndex = pointerUp ? event.getActionIndex() : -1; // Determine focal point float sumX = 0, sumY = 0; - final int count = event.getPointerCount(); final int div = pointerUp ? count - 1 : count; final float focusX; final float focusY; - if (mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS) { - // In double tap mode, the focal pt is always where the double tap - // gesture started - focusX = mDoubleTapEvent.getX(); - focusY = mDoubleTapEvent.getY(); + if (inAnchoredScaleMode()) { + // In anchored scale mode, the focal pt is always where the double tap + // or button down gesture started + focusX = mAnchoredScaleStartX; + focusY = mAnchoredScaleStartY; if (event.getY() < focusY) { mEventBeforeOrAboveStartingGestureEvent = true; } else { @@ -390,7 +410,7 @@ public class ScaleGestureDetector { final float spanX = devX * 2; final float spanY = devY * 2; final float span; - if (inDoubleTapMode()) { + if (inAnchoredScaleMode()) { span = spanY; } else { span = (float) Math.hypot(spanX, spanY); @@ -402,11 +422,10 @@ public class ScaleGestureDetector { final boolean wasInProgress = mInProgress; mFocusX = focusX; mFocusY = focusY; - if (!inDoubleTapMode() && mInProgress && (span < mMinSpan || configChanged)) { + if (!inAnchoredScaleMode() && mInProgress && (span < mMinSpan || configChanged)) { mListener.onScaleEnd(this); mInProgress = false; mInitialSpan = span; - mDoubleTapMode = DOUBLE_TAP_MODE_NONE; } if (configChanged) { mPrevSpanX = mCurrSpanX = spanX; @@ -414,7 +433,7 @@ public class ScaleGestureDetector { mInitialSpan = mPrevSpan = mCurrSpan = span; } - final int minSpan = inDoubleTapMode() ? mSpanSlop : mMinSpan; + final int minSpan = inAnchoredScaleMode() ? mSpanSlop : mMinSpan; if (!mInProgress && span >= minSpan && (wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) { mPrevSpanX = mCurrSpanX = spanX; @@ -447,9 +466,8 @@ public class ScaleGestureDetector { return true; } - - private boolean inDoubleTapMode() { - return mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS; + private boolean inAnchoredScaleMode() { + return mAnchoredScaleMode != ANCHORED_SCALE_MODE_NONE; } /** @@ -466,8 +484,9 @@ public class ScaleGestureDetector { @Override public boolean onDoubleTap(MotionEvent e) { // Double tap: start watching for a swipe - mDoubleTapEvent = e; - mDoubleTapMode = DOUBLE_TAP_MODE_IN_PROGRESS; + mAnchoredScaleStartX = e.getX(); + mAnchoredScaleStartY = e.getY(); + mAnchoredScaleMode = ANCHORED_SCALE_MODE_DOUBLE_TAP; return true; } }; @@ -484,6 +503,27 @@ 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. + * + * @param scales true to enable stylus or mouse scaling, false to disable. + */ + public void setSecondaryButtonScaleEnabled(boolean scales) { + mButtonScaleEnabled = 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)}. + */ + public boolean isSecondaryButtonScaleEnabled() { + return mButtonScaleEnabled; + } + + /** * Returns {@code true} if a scale gesture is in progress. */ public boolean isInProgress() { @@ -586,7 +626,7 @@ public class ScaleGestureDetector { * @return The current scaling factor. */ public float getScaleFactor() { - if (inDoubleTapMode()) { + if (inAnchoredScaleMode()) { // Drag is moving up; the further away from the gesture // start, the smaller the span should be, the closer, // the larger the span, and therefore the larger the scale diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 031be07..87d5d9a 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -23,7 +23,9 @@ import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.os.Binder; import android.os.IBinder; +import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; import android.os.Trace; @@ -124,7 +126,7 @@ public class ThreadedRenderer extends HardwareRenderer { mRootNode.setClipToBounds(false); mNativeProxy = nCreateProxy(translucent, rootNodePtr); - AtlasInitializer.sInstance.init(context, mNativeProxy); + ProcessInitializer.sInstance.init(context, mNativeProxy); loadSystemProperties(); } @@ -410,15 +412,44 @@ public class ThreadedRenderer extends HardwareRenderer { nTrimMemory(level); } - private static class AtlasInitializer { - static AtlasInitializer sInstance = new AtlasInitializer(); + public static void dumpProfileData(byte[] data, FileDescriptor fd) { + nDumpProfileData(data, fd); + } + + private static class ProcessInitializer { + static ProcessInitializer sInstance = new ProcessInitializer(); + static IGraphicsStats sGraphicsStatsService; + private static IBinder sProcToken; private boolean mInitialized = false; - private AtlasInitializer() {} + private ProcessInitializer() {} synchronized void init(Context context, long renderProxy) { if (mInitialized) return; + mInitialized = true; + initGraphicsStats(context, renderProxy); + initAssetAtlas(context, renderProxy); + } + + private static void initGraphicsStats(Context context, long renderProxy) { + IBinder binder = ServiceManager.getService("graphicsstats"); + if (binder == null) return; + + sGraphicsStatsService = IGraphicsStats.Stub.asInterface(binder); + sProcToken = new Binder(); + try { + final String pkg = context.getApplicationInfo().packageName; + ParcelFileDescriptor pfd = sGraphicsStatsService. + requestBufferForProcess(pkg, sProcToken); + nSetProcessStatsBuffer(renderProxy, pfd.getFd()); + pfd.close(); + } catch (Exception e) { + Log.w(LOG_TAG, "Could not acquire gfx stats buffer", e); + } + } + + private static void initAssetAtlas(Context context, long renderProxy) { IBinder binder = ServiceManager.getService("assetatlas"); if (binder == null) return; @@ -432,7 +463,6 @@ public class ThreadedRenderer extends HardwareRenderer { // TODO Remove after fixing b/15425820 validateMap(context, map); nSetAtlas(renderProxy, buffer, map); - mInitialized = true; } // If IAssetAtlas is not the same class as the IBinder // we are using a remote service and we can safely @@ -477,6 +507,7 @@ public class ThreadedRenderer extends HardwareRenderer { static native void setupShadersDiskCache(String cacheFile); private static native void nSetAtlas(long nativeProxy, GraphicBuffer buffer, long[] map); + private static native void nSetProcessStatsBuffer(long nativeProxy, int fd); private static native long nCreateRootRenderNode(); private static native long nCreateProxy(boolean translucent, long rootRenderNode); @@ -514,4 +545,5 @@ public class ThreadedRenderer extends HardwareRenderer { private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd, @DumpFlags int dumpFlags); + private static native void nDumpProfileData(byte[] data, FileDescriptor fd); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index db8109f..b6f1e3b 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -91,6 +91,7 @@ import android.view.animation.Transformation; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; +import android.widget.Checkable; import android.widget.ScrollBarDrawable; import static android.os.Build.VERSION_CODES.*; @@ -3196,9 +3197,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private static class ForegroundInfo { private Drawable mDrawable; private TintInfo mTintInfo; - private int mGravity = Gravity.START | Gravity.TOP; + private int mGravity = Gravity.FILL; private boolean mInsidePadding = true; - private boolean mBoundsChanged; + private boolean mBoundsChanged = true; private final Rect mSelfBounds = new Rect(); private final Rect mOverlayBounds = new Rect(); } @@ -3582,6 +3583,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // of whether a layout was requested on that View. sIgnoreMeasureCache = targetSdkVersion < KITKAT; + Canvas.sCompatibilityRestore = targetSdkVersion < MNC; + sCompatibilityDone = true; } } @@ -5676,10 +5679,143 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Called when assist structure is being retrieved from a view as part of * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData}. - * @param structure Additional standard structured view structure to supply. - * @param extras Non-standard extensions. + * @param structure Fill in with structured view data. The default implementation + * fills in all data that can be inferred from the view itself. + */ + public void onProvideAssistStructure(ViewAssistStructure structure) { + final int id = mID; + if (id > 0 && (id&0xff000000) != 0 && (id&0x00ff0000) != 0 + && (id&0x0000ffff) != 0) { + String pkg, type, entry; + try { + final Resources res = getResources(); + entry = res.getResourceEntryName(id); + type = res.getResourceTypeName(id); + pkg = res.getResourcePackageName(id); + } catch (Resources.NotFoundException e) { + entry = type = pkg = null; + } + structure.setId(id, pkg, type, entry); + } else { + structure.setId(id, null, null, null); + } + structure.setDimens(mLeft, mTop, mScrollX, mScrollY, mRight-mLeft, mBottom-mTop); + structure.setVisibility(getVisibility()); + structure.setEnabled(isEnabled()); + if (isClickable()) { + structure.setClickable(true); + } + if (isFocusable()) { + structure.setFocusable(true); + } + if (isFocused()) { + structure.setFocused(true); + } + if (isAccessibilityFocused()) { + structure.setAccessibilityFocused(true); + } + if (isSelected()) { + structure.setSelected(true); + } + if (isActivated()) { + structure.setActivated(true); + } + if (isLongClickable()) { + structure.setLongClickable(true); + } + if (this instanceof Checkable) { + structure.setCheckable(true); + if (((Checkable)this).isChecked()) { + structure.setChecked(true); + } + } + structure.setClassName(getAccessibilityClassName().toString()); + structure.setContentDescription(getContentDescription()); + } + + /** + * Called when assist structure is being retrieved from a view as part of + * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData} to + * generate additional virtual structure under this view. The defaullt implementation + * uses {@link #getAccessibilityNodeProvider()} to try to generate this from the + * view's virtual accessibility nodes, if any. You can override this for a more + * optimal implementation providing this data. + */ + public void onProvideVirtualAssistStructure(ViewAssistStructure structure) { + AccessibilityNodeProvider provider = getAccessibilityNodeProvider(); + if (provider != null) { + AccessibilityNodeInfo info = createAccessibilityNodeInfo(); + Log.i("View", "Provider of " + this + ": children=" + info.getChildCount()); + structure.setChildCount(1); + ViewAssistStructure root = structure.newChild(0); + populateVirtualAssistStructure(root, provider, info); + info.recycle(); + } + } + + private void populateVirtualAssistStructure(ViewAssistStructure structure, + AccessibilityNodeProvider provider, AccessibilityNodeInfo info) { + structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()), + null, null, null); + Rect rect = structure.getTempRect(); + info.getBoundsInParent(rect); + structure.setDimens(rect.left, rect.top, 0, 0, rect.width(), rect.height()); + structure.setVisibility(VISIBLE); + structure.setEnabled(info.isEnabled()); + if (info.isClickable()) { + structure.setClickable(true); + } + if (info.isFocusable()) { + structure.setFocusable(true); + } + if (info.isFocused()) { + structure.setFocused(true); + } + if (info.isAccessibilityFocused()) { + structure.setAccessibilityFocused(true); + } + if (info.isSelected()) { + structure.setSelected(true); + } + if (info.isLongClickable()) { + structure.setLongClickable(true); + } + if (info.isCheckable()) { + structure.setCheckable(true); + if (info.isChecked()) { + structure.setChecked(true); + } + } + CharSequence cname = info.getClassName(); + structure.setClassName(cname != null ? cname.toString() : null); + structure.setContentDescription(info.getContentDescription()); + Log.i("View", "vassist " + cname + " @ " + rect.toShortString() + + " text=" + info.getText() + " cd=" + info.getContentDescription()); + if (info.getText() != null || info.getError() != null) { + structure.setText(info.getText(), info.getTextSelectionStart(), + info.getTextSelectionEnd()); + } + final int NCHILDREN = info.getChildCount(); + if (NCHILDREN > 0) { + structure.setChildCount(NCHILDREN); + for (int i=0; i<NCHILDREN; i++) { + AccessibilityNodeInfo cinfo = provider.createAccessibilityNodeInfo( + AccessibilityNodeInfo.getVirtualDescendantId(info.getChildId(i))); + ViewAssistStructure child = structure.newChild(i); + populateVirtualAssistStructure(child, provider, cinfo); + cinfo.recycle(); + } + } + } + + /** + * Dispatch creation of {@link ViewAssistStructure} down the hierarchy. The default + * implementation calls {@link #onProvideAssistStructure} and + * {@link #onProvideVirtualAssistStructure}. */ - public void onProvideAssistStructure(ViewAssistStructure structure, Bundle extras) { + public void dispatchProvideAssistStructure(ViewAssistStructure structure) { + onProvideAssistStructure(structure); + onProvideVirtualAssistStructure(structure); } /** @@ -8264,7 +8400,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return true; } } break; - case R.id.accessibility_action_show_on_screen: { + case R.id.accessibilityActionShowOnScreen: { if (mAttachInfo != null) { final Rect r = mAttachInfo.mTmpInvalRect; getDrawingRect(r); @@ -9959,6 +10095,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param oldt Previous vertical scroll origin. */ protected void onScrollChanged(int l, int t, int oldl, int oldt) { + notifySubtreeAccessibilityStateChangedIfNeeded(); + if (AccessibilityManager.getInstance(mContext).isEnabled()) { postSendViewScrolledAccessibilityEventCallback(); } @@ -11162,6 +11300,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -16674,6 +16813,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } mForegroundInfo.mDrawable = foreground; + mForegroundInfo.mBoundsChanged = true; if (foreground != null) { setWillNotDraw(false); foreground.setCallback(this); @@ -16778,7 +16918,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @Nullable public ColorStateList getForegroundTintList() { return mForegroundInfo != null && mForegroundInfo.mTintInfo != null - ? mBackgroundTint.mTintList : null; + ? mForegroundInfo.mTintInfo.mTintList : null; } /** diff --git a/core/java/android/view/ViewAssistStructure.java b/core/java/android/view/ViewAssistStructure.java index 5132bb9..7d263c5 100644 --- a/core/java/android/view/ViewAssistStructure.java +++ b/core/java/android/view/ViewAssistStructure.java @@ -16,6 +16,8 @@ package android.view; +import android.graphics.Rect; +import android.os.Bundle; import android.text.TextPaint; /** @@ -23,6 +25,37 @@ import android.text.TextPaint; * View.onProvideAssistStructure}. */ public abstract class ViewAssistStructure { + public abstract void setId(int id, String packageName, String typeName, String entryName); + + public abstract void setDimens(int left, int top, int scrollX, int scrollY, int width, + int height); + + public abstract void setVisibility(int visibility); + + public abstract void setEnabled(boolean state); + + public abstract void setClickable(boolean state); + + public abstract void setLongClickable(boolean state); + + public abstract void setFocusable(boolean state); + + public abstract void setFocused(boolean state); + + public abstract void setAccessibilityFocused(boolean state); + + public abstract void setCheckable(boolean state); + + public abstract void setChecked(boolean state); + + public abstract void setSelected(boolean state); + + public abstract void setActivated(boolean state); + + public abstract void setClassName(String className); + + public abstract void setContentDescription(CharSequence contentDescription); + public abstract void setText(CharSequence text); public abstract void setText(CharSequence text, int selectionStart, int selectionEnd); public abstract void setTextPaint(TextPaint paint); @@ -32,4 +65,17 @@ public abstract class ViewAssistStructure { public abstract int getTextSelectionStart(); public abstract int getTextSelectionEnd(); public abstract CharSequence getHint(); + + public abstract Bundle editExtras(); + public abstract void clearExtras(); + + public abstract void setChildCount(int num); + public abstract int getChildCount(); + public abstract ViewAssistStructure newChild(int index); + + public abstract ViewAssistStructure asyncNewChild(int index); + public abstract void asyncCommit(); + + /** @hide */ + public abstract Rect getTempRect(); } diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index 50e64c6..a237afd 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -1005,31 +1005,23 @@ public class ViewDebug { return fields; } - final ArrayList<Field> declaredFields = new ArrayList(); - klass.getDeclaredFieldsUnchecked(false, declaredFields); - - final ArrayList<Field> foundFields = new ArrayList<Field>(); - final int count = declaredFields.size(); - for (int i = 0; i < count; i++) { - final Field field = declaredFields.get(i); - - // Ensure the field type can be resolved. - try { - field.getType(); - } catch (NoClassDefFoundError e) { - continue; - } - - if (field.isAnnotationPresent(ExportedProperty.class)) { - field.setAccessible(true); - foundFields.add(field); - sAnnotations.put(field, field.getAnnotation(ExportedProperty.class)); + try { + final Field[] declaredFields = klass.getDeclaredFieldsUnchecked(false); + final ArrayList<Field> foundFields = new ArrayList<Field>(); + for (final Field field : declaredFields) { + // Fields which can't be resolved have a null type. + if (field.getType() != null && field.isAnnotationPresent(ExportedProperty.class)) { + field.setAccessible(true); + foundFields.add(field); + sAnnotations.put(field, field.getAnnotation(ExportedProperty.class)); + } } + fields = foundFields.toArray(new Field[foundFields.size()]); + map.put(klass, fields); + } catch (NoClassDefFoundError e) { + throw new AssertionError(e); } - fields = foundFields.toArray(new Field[foundFields.size()]); - map.put(klass, fields); - return fields; } @@ -1651,4 +1643,4 @@ public class ViewDebug { } }); } -}
\ No newline at end of file +} diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 87f3e94..d0705bb 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -2852,6 +2852,33 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return false; } + /** + * Dispatch creation of {@link ViewAssistStructure} down the hierarchy. This implementation + * adds in all child views of the view group, in addition to calling the default View + * implementation. + */ + 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); + } + } + } + } + /** @hide */ @Override public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { @@ -5727,12 +5754,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be - resultSize = 0; + resultSize = size; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be - resultSize = 0; + resultSize = size; resultMode = MeasureSpec.UNSPECIFIED; } break; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 294174a..4158340 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -6268,41 +6268,79 @@ public final class ViewRootImpl implements ViewParent, case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: { - if (mAccessibilityFocusedHost != null && mAccessibilityFocusedVirtualView != null) { - // We care only for changes rooted in the focused host. - final long eventSourceId = event.getSourceNodeId(); - final int hostViewId = AccessibilityNodeInfo.getAccessibilityViewId( - eventSourceId); - if (hostViewId != mAccessibilityFocusedHost.getAccessibilityViewId()) { - break; - } - - // We only care about changes that may change the virtual focused view bounds. - final int changes = event.getContentChangeTypes(); - if ((changes & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) != 0 - || changes == AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED) { - AccessibilityNodeProvider provider = mAccessibilityFocusedHost - .getAccessibilityNodeProvider(); - if (provider != null) { - final int virtualChildId = AccessibilityNodeInfo.getVirtualDescendantId( - mAccessibilityFocusedVirtualView.getSourceNodeId()); - if (virtualChildId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { - mAccessibilityFocusedVirtualView = provider - .createAccessibilityNodeInfo( - AccessibilityNodeProvider.HOST_VIEW_ID); - } else { - mAccessibilityFocusedVirtualView = provider - .createAccessibilityNodeInfo(virtualChildId); - } - } - } - } + handleWindowContentChangedEvent(event); } break; } mAccessibilityManager.sendAccessibilityEvent(event); return true; } + /** + * Updates the focused virtual view, when necessary, in response to a + * content changed event. + * <p> + * This is necessary to get updated bounds after a position change. + * + * @param event an accessibility event of type + * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} + */ + private void handleWindowContentChangedEvent(AccessibilityEvent event) { + // No virtual view focused, nothing to do here. + if (mAccessibilityFocusedHost == null || mAccessibilityFocusedVirtualView == null) { + return; + } + + // If we have a node but no provider, abort. + final AccessibilityNodeProvider provider = + mAccessibilityFocusedHost.getAccessibilityNodeProvider(); + if (provider == null) { + // TODO: Should we clear the focused virtual view? + return; + } + + // We only care about change types that may affect the bounds of the + // focused virtual view. + final int changes = event.getContentChangeTypes(); + if ((changes & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) == 0 + && changes != AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED) { + return; + } + + final long eventSourceNodeId = event.getSourceNodeId(); + final int changedViewId = AccessibilityNodeInfo.getAccessibilityViewId(eventSourceNodeId); + + // Search up the tree for subtree containment. + boolean hostInSubtree = false; + View root = mAccessibilityFocusedHost; + while (root != null && !hostInSubtree) { + if (changedViewId == root.getAccessibilityViewId()) { + hostInSubtree = true; + } else { + final ViewParent parent = root.getParent(); + if (parent instanceof View) { + root = (View) parent; + } else { + root = null; + } + } + } + + // We care only about changes in subtrees containing the host view. + if (!hostInSubtree) { + return; + } + + final long focusedSourceNodeId = mAccessibilityFocusedVirtualView.getSourceNodeId(); + int focusedChildId = AccessibilityNodeInfo.getVirtualDescendantId(focusedSourceNodeId); + if (focusedChildId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { + // TODO: Should we clear the focused virtual view? + focusedChildId = AccessibilityNodeProvider.HOST_VIEW_ID; + } + + // Refresh the node for the focused virtual view. + mAccessibilityFocusedVirtualView = provider.createAccessibilityNodeInfo(focusedChildId); + } + @Override public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) { postSendWindowContentChangedCallback(source, changeType); diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 9a92932..36f047e 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -1856,14 +1856,14 @@ public abstract class Window { public abstract int getStatusBarColor(); /** - * Sets the color of the status bar to {@param color}. + * Sets the color of the status bar to {@code color}. * * For this to take effect, * the window must be drawing the system bar backgrounds with * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} and * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS} must not be set. * - * If {@param color} is not opaque, consider setting + * If {@code color} is not opaque, consider setting * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}. * <p> diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 66dae7b..54d78f3 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -223,6 +223,7 @@ public interface WindowManager extends ViewManager { @ViewDebug.IntToString(from = TYPE_MAGNIFICATION_OVERLAY, to = "TYPE_MAGNIFICATION_OVERLAY"), @ViewDebug.IntToString(from = TYPE_PRIVATE_PRESENTATION, to = "TYPE_PRIVATE_PRESENTATION"), @ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION, to = "TYPE_VOICE_INTERACTION"), + @ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION_STARTING, to = "TYPE_VOICE_INTERACTION_STARTING"), }) public int type; @@ -549,6 +550,12 @@ public interface WindowManager extends ViewManager { public static final int TYPE_ACCESSIBILITY_OVERLAY = FIRST_SYSTEM_WINDOW+32; /** + * Window type: Starting window for voice interaction layer. + * @hide + */ + public static final int TYPE_VOICE_INTERACTION_STARTING = FIRST_SYSTEM_WINDOW+33; + + /** * End of types of system windows. */ public static final int LAST_SYSTEM_WINDOW = 2999; diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 77082b0..0736ed8 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -266,7 +266,8 @@ public class AccessibilityNodeInfo implements Parcelable { * Action to set the selection. Performing this action with no arguments * clears the selection. * <p> - * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_SELECTION_START_INT}, + * <strong>Arguments:</strong> + * {@link #ACTION_ARGUMENT_SELECTION_START_INT}, * {@link #ACTION_ARGUMENT_SELECTION_END_INT}<br> * <strong>Example:</strong> * <code><pre><p> @@ -302,7 +303,8 @@ public class AccessibilityNodeInfo implements Parcelable { * null</code> or empty {@link CharSequence} will clear the text. This action will also put the * cursor at the end of text. * <p> - * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE}<br> + * <strong>Arguments:</strong> + * {@link #ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE}<br> * <strong>Example:</strong> * <code><pre><p> * Bundle arguments = new Bundle(); @@ -326,12 +328,15 @@ public class AccessibilityNodeInfo implements Parcelable { * Argument for which movement granularity to be used when traversing the node text. * <p> * <strong>Type:</strong> int<br> - * <strong>Actions:</strong> {@link #ACTION_NEXT_AT_MOVEMENT_GRANULARITY}, - * {@link #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY} + * <strong>Actions:</strong> + * <ul> + * <li>{@link AccessibilityAction#ACTION_NEXT_AT_MOVEMENT_GRANULARITY}</li> + * <li>{@link AccessibilityAction#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY}</li> + * </ul> * </p> * - * @see #ACTION_NEXT_AT_MOVEMENT_GRANULARITY - * @see #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY + * @see AccessibilityAction#ACTION_NEXT_AT_MOVEMENT_GRANULARITY + * @see AccessibilityAction#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY */ public static final String ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT = "ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT"; @@ -340,12 +345,15 @@ public class AccessibilityNodeInfo implements Parcelable { * Argument for which HTML element to get moving to the next/previous HTML element. * <p> * <strong>Type:</strong> String<br> - * <strong>Actions:</strong> {@link #ACTION_NEXT_HTML_ELEMENT}, - * {@link #ACTION_PREVIOUS_HTML_ELEMENT} + * <strong>Actions:</strong> + * <ul> + * <li>{@link AccessibilityAction#ACTION_NEXT_HTML_ELEMENT}</li> + * <li>{@link AccessibilityAction#ACTION_PREVIOUS_HTML_ELEMENT}</li> + * </ul> * </p> * - * @see #ACTION_NEXT_HTML_ELEMENT - * @see #ACTION_PREVIOUS_HTML_ELEMENT + * @see AccessibilityAction#ACTION_NEXT_HTML_ELEMENT + * @see AccessibilityAction#ACTION_PREVIOUS_HTML_ELEMENT */ public static final String ACTION_ARGUMENT_HTML_ELEMENT_STRING = "ACTION_ARGUMENT_HTML_ELEMENT_STRING"; @@ -355,12 +363,14 @@ public class AccessibilityNodeInfo implements Parcelable { * or to move it otherwise. * <p> * <strong>Type:</strong> boolean<br> - * <strong>Actions:</strong> {@link #ACTION_NEXT_AT_MOVEMENT_GRANULARITY}, - * {@link #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY} - * </p> + * <strong>Actions:</strong> + * <ul> + * <li>{@link AccessibilityAction#ACTION_NEXT_AT_MOVEMENT_GRANULARITY}</li> + * <li>{@link AccessibilityAction#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY}</li> + * </ul> * - * @see #ACTION_NEXT_AT_MOVEMENT_GRANULARITY - * @see #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY + * @see AccessibilityAction#ACTION_NEXT_AT_MOVEMENT_GRANULARITY + * @see AccessibilityAction#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY */ public static final String ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN = "ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN"; @@ -369,10 +379,12 @@ public class AccessibilityNodeInfo implements Parcelable { * Argument for specifying the selection start. * <p> * <strong>Type:</strong> int<br> - * <strong>Actions:</strong> {@link #ACTION_SET_SELECTION} - * </p> + * <strong>Actions:</strong> + * <ul> + * <li>{@link AccessibilityAction#ACTION_SET_SELECTION}</li> + * </ul> * - * @see #ACTION_SET_SELECTION + * @see AccessibilityAction#ACTION_SET_SELECTION */ public static final String ACTION_ARGUMENT_SELECTION_START_INT = "ACTION_ARGUMENT_SELECTION_START_INT"; @@ -381,26 +393,58 @@ public class AccessibilityNodeInfo implements Parcelable { * Argument for specifying the selection end. * <p> * <strong>Type:</strong> int<br> - * <strong>Actions:</strong> {@link #ACTION_SET_SELECTION} - * </p> + * <strong>Actions:</strong> + * <ul> + * <li>{@link AccessibilityAction#ACTION_SET_SELECTION}</li> + * </ul> * - * @see #ACTION_SET_SELECTION + * @see AccessibilityAction#ACTION_SET_SELECTION */ public static final String ACTION_ARGUMENT_SELECTION_END_INT = "ACTION_ARGUMENT_SELECTION_END_INT"; /** - * Argument for specifying the text content to set + * Argument for specifying the text content to set. * <p> * <strong>Type:</strong> CharSequence<br> - * <strong>Actions:</strong> {@link #ACTION_SET_TEXT} - * </p> + * <strong>Actions:</strong> + * <ul> + * <li>{@link AccessibilityAction#ACTION_SET_TEXT}</li> + * </ul> * - * @see #ACTION_SET_TEXT + * @see AccessibilityAction#ACTION_SET_TEXT */ public static final String ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE = "ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE"; + /** + * Argument for specifying the collection row to make visible on screen. + * <p> + * <strong>Type:</strong> int<br> + * <strong>Actions:</strong> + * <ul> + * <li>{@link AccessibilityAction#ACTION_SCROLL_TO_POSITION}</li> + * </ul> + * + * @see AccessibilityAction#ACTION_SCROLL_TO_POSITION + */ + public static final String ACTION_ARGUMENT_ROW_INT = + "android.view.accessibility.action.ARGUMENT_ROW_INT"; + + /** + * Argument for specifying the collection column to make visible on screen. + * <p> + * <strong>Type:</strong> int<br> + * <strong>Actions:</strong> + * <ul> + * <li>{@link AccessibilityAction#ACTION_SCROLL_TO_POSITION}</li> + * </ul> + * + * @see AccessibilityAction#ACTION_SCROLL_TO_POSITION + */ + public static final String ACTION_ARGUMENT_COLUMN_INT = + "android.view.accessibility.action.ARGUMENT_COLUMN_INT"; + // Focus types /** @@ -539,7 +583,7 @@ public class AccessibilityNodeInfo implements Parcelable { // Housekeeping. private static final int MAX_POOL_SIZE = 50; private static final SynchronizedPool<AccessibilityNodeInfo> sPool = - new SynchronizedPool<AccessibilityNodeInfo>(MAX_POOL_SIZE); + new SynchronizedPool<>(MAX_POOL_SIZE); private boolean mSealed; @@ -970,7 +1014,7 @@ public class AccessibilityNodeInfo implements Parcelable { } if (mActions == null) { - mActions = new ArrayList<AccessibilityAction>(); + mActions = new ArrayList<>(); } mActions.remove(action); @@ -1562,7 +1606,7 @@ public class AccessibilityNodeInfo implements Parcelable { } /** - * Sets whether this node is visible to the user. + * Gets whether this node is visible to the user. * * @return Whether the node is visible to the user. */ @@ -3411,9 +3455,24 @@ public class AccessibilityNodeInfo implements Parcelable { * @see View#requestRectangleOnScreen(Rect) */ public static final AccessibilityAction ACTION_SHOW_ON_SCREEN = - new AccessibilityAction(R.id.accessibility_action_show_on_screen, null); + new AccessibilityAction(R.id.accessibilityActionShowOnScreen, null); + + /** + * Action that scrolls the node to make the specified collection + * position visible on screen. + * <p> + * <strong>Arguments:</strong> + * <ul> + * <li>{@link AccessibilityNodeInfo#ACTION_ARGUMENT_ROW_INT}</li> + * <li>{@link AccessibilityNodeInfo#ACTION_ARGUMENT_COLUMN_INT}</li> + * <ul> + * + * @see AccessibilityNodeInfo#getCollectionInfo() + */ + public static final AccessibilityAction ACTION_SCROLL_TO_POSITION = + new AccessibilityAction(R.id.accessibilityActionScrollToPosition, null); - private static final ArraySet<AccessibilityAction> sStandardActions = new ArraySet<AccessibilityAction>(); + private static final ArraySet<AccessibilityAction> sStandardActions = new ArraySet<>(); static { sStandardActions.add(ACTION_FOCUS); sStandardActions.add(ACTION_CLEAR_FOCUS); @@ -3438,6 +3497,7 @@ public class AccessibilityNodeInfo implements Parcelable { sStandardActions.add(ACTION_DISMISS); sStandardActions.add(ACTION_SET_TEXT); sStandardActions.add(ACTION_SHOW_ON_SCREEN); + sStandardActions.add(ACTION_SCROLL_TO_POSITION); } private final int mActionId; @@ -3658,7 +3718,7 @@ public class AccessibilityNodeInfo implements Parcelable { private static final int MAX_POOL_SIZE = 20; private static final SynchronizedPool<CollectionInfo> sPool = - new SynchronizedPool<CollectionInfo>(MAX_POOL_SIZE); + new SynchronizedPool<>(MAX_POOL_SIZE); private int mRowCount; private int mColumnCount; @@ -3804,7 +3864,7 @@ public class AccessibilityNodeInfo implements Parcelable { private static final int MAX_POOL_SIZE = 20; private static final SynchronizedPool<CollectionItemInfo> sPool = - new SynchronizedPool<CollectionItemInfo>(MAX_POOL_SIZE); + new SynchronizedPool<>(MAX_POOL_SIZE); /** * Obtains a pooled instance that is a clone of another one. diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java index 4737e9b..0b18bb8 100644 --- a/core/java/android/webkit/WebChromeClient.java +++ b/core/java/android/webkit/WebChromeClient.java @@ -284,13 +284,19 @@ public class WebChromeClient { * currently set for that origin. The host application should invoke the * specified callback with the desired permission state. See * {@link GeolocationPermissions} for details. + * + * If this method isn't overridden, the callback is invoked with permission + * denied state. + * * @param origin The origin of the web content attempting to use the * Geolocation API. * @param callback The callback to use to set the permission state for the * origin. */ public void onGeolocationPermissionsShowPrompt(String origin, - GeolocationPermissions.Callback callback) {} + GeolocationPermissions.Callback callback) { + callback.invoke(origin, false, false); + } /** * Notify the host application that a request for Geolocation permissions, diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index aa77d5e..e7c4328 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -40,6 +40,7 @@ import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; +import android.view.ViewAssistStructure; import android.view.ViewDebug; import android.view.ViewGroup; import android.view.ViewTreeObserver; @@ -2424,6 +2425,13 @@ public class WebView extends AbsoluteLayout return WebView.class.getName(); } + @Override + public void onProvideVirtualAssistStructure(ViewAssistStructure structure) { + super.onProvideVirtualAssistStructure(structure); + // TODO: enable when chromium backend lands. + // mProvider.getViewDelegate().onProvideVirtualAssistStructure(structure); + } + /** @hide */ @Override public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java index 53c7e04..8a2b3fa 100644 --- a/core/java/android/webkit/WebViewClient.java +++ b/core/java/android/webkit/WebViewClient.java @@ -200,8 +200,6 @@ public class WebViewClient { public static final int ERROR_FILE_NOT_FOUND = -14; /** Too many requests during this load */ public static final int ERROR_TOO_MANY_REQUESTS = -15; - /** Request blocked by the browser */ - public static final int ERROR_BLOCKED = -16; /** * Report an error to the host application. These errors are unrecoverable diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java index fa2ce1b..d5787de 100644 --- a/core/java/android/webkit/WebViewProvider.java +++ b/core/java/android/webkit/WebViewProvider.java @@ -32,6 +32,7 @@ import android.print.PrintDocumentAdapter; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; +import android.view.ViewAssistStructure; import android.view.ViewGroup.LayoutParams; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; @@ -298,6 +299,8 @@ public interface WebViewProvider { interface ViewDelegate { public boolean shouldDelayChildPressedState(); + public void onProvideVirtualAssistStructure(ViewAssistStructure structure); + public AccessibilityNodeProvider getAccessibilityNodeProvider(); public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info); diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 168066a..e7b6238 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -61,6 +61,7 @@ import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; @@ -1504,11 +1505,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te super.onInitializeAccessibilityNodeInfoInternal(info); if (isEnabled()) { if (canScrollUp()) { - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + info.addAction(AccessibilityAction.ACTION_SCROLL_BACKWARD); info.setScrollable(true); } if (canScrollDown()) { - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + info.addAction(AccessibilityAction.ACTION_SCROLL_FORWARD); info.setScrollable(true); } } @@ -2502,18 +2503,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (position == getSelectedItemPosition()) { info.setSelected(true); - info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION); + info.addAction(AccessibilityAction.ACTION_CLEAR_SELECTION); } else { - info.addAction(AccessibilityNodeInfo.ACTION_SELECT); + info.addAction(AccessibilityAction.ACTION_SELECT); } if (isClickable()) { - info.addAction(AccessibilityNodeInfo.ACTION_CLICK); + info.addAction(AccessibilityAction.ACTION_CLICK); info.setClickable(true); } if (isLongClickable()) { - info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK); + info.addAction(AccessibilityAction.ACTION_LONG_CLICK); info.setLongClickable(true); } } diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java index 4fadc19..36bce0b 100644 --- a/core/java/android/widget/ActionMenuPresenter.java +++ b/core/java/android/widget/ActionMenuPresenter.java @@ -16,6 +16,10 @@ package android.widget; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Configuration; @@ -24,6 +28,7 @@ import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; +import android.util.SparseArray; import android.util.SparseBooleanArray; import android.view.ActionProvider; import android.view.Gravity; @@ -32,6 +37,7 @@ import android.view.SoundEffectConstants; import android.view.View; import android.view.View.MeasureSpec; import android.view.ViewGroup; +import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ListPopupWindow.ForwardingListener; import com.android.internal.transition.ActionBarTransition; @@ -45,6 +51,7 @@ import com.android.internal.view.menu.MenuView; import com.android.internal.view.menu.SubMenuBuilder; import java.util.ArrayList; +import java.util.List; /** * MenuPresenter for building action menus as seen in the action bar and action modes. @@ -54,6 +61,7 @@ import java.util.ArrayList; public class ActionMenuPresenter extends BaseMenuPresenter implements ActionProvider.SubUiVisibilityListener { private static final String TAG = "ActionMenuPresenter"; + private static final int ITEM_ANIMATION_DURATION = 150; private OverflowMenuButton mOverflowButton; private boolean mReserveOverflow; @@ -71,8 +79,6 @@ public class ActionMenuPresenter extends BaseMenuPresenter // Group IDs that have been added as actions - used temporarily, allocated here for reuse. private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray(); - private View mScrapActionButtonView; - private OverflowPopup mOverflowPopup; private ActionButtonSubmenu mActionButtonPopup; @@ -84,6 +90,18 @@ public class ActionMenuPresenter extends BaseMenuPresenter final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback(); int mOpenSubMenuId; + // These collections are used to store pre- and post-layout information for menu items, + // which is used to determine appropriate animations to run for changed items. + private SparseArray<MenuItemLayoutInfo> mPreLayoutItems = + new SparseArray<MenuItemLayoutInfo>(); + private SparseArray<MenuItemLayoutInfo> mPostLayoutItems = + new SparseArray<MenuItemLayoutInfo>(); + + // The list of currently running animations on menu items. + private List<ItemAnimationInfo> mRunningItemAnimations = new ArrayList<ItemAnimationInfo>(); + + + public ActionMenuPresenter(Context context) { super(context, com.android.internal.R.layout.action_menu_layout, com.android.internal.R.layout.action_menu_item_layout); @@ -125,9 +143,6 @@ public class ActionMenuPresenter extends BaseMenuPresenter mActionItemWidthLimit = width; mMinCellSize = (int) (ActionMenuView.MIN_CELL_SIZE * res.getDisplayMetrics().density); - - // Drop a scrap view as it may no longer reflect the proper context/config. - mScrapActionButtonView = null; } public void onConfigurationChanged(Configuration newConfig) { @@ -202,10 +217,187 @@ public class ActionMenuPresenter extends BaseMenuPresenter return item.isActionButton(); } + /** + * Store layout information about current items in the menu. This is stored for + * both pre- and post-layout phases and compared in runItemAnimations() to determine + * the animations that need to be run on any item changes. + * + * @param preLayout Whether this is being called in the pre-layout phase. This is passed + * into the MenuItemLayoutInfo structure to store the appropriate position values. + */ + private void computeMenuItemAnimationInfo(boolean preLayout) { + final ViewGroup menuViewParent = (ViewGroup) mMenuView; + final int count = menuViewParent.getChildCount(); + SparseArray items = preLayout ? mPreLayoutItems : mPostLayoutItems; + for (int i = 0; i < count; ++i) { + View child = menuViewParent.getChildAt(i); + final int id = child.getId(); + if (id > 0 && child.getWidth() != 0 && child.getHeight() != 0) { + MenuItemLayoutInfo info = new MenuItemLayoutInfo(child, preLayout); + items.put(id, info); + } + } + } + + /** + * This method is called once both the pre-layout and post-layout steps have + * happened. It figures out which views are new (didn't exist prior to layout), + * gone (existed pre-layout, but are now gone), or changed (exist in both, + * but in a different location) and runs appropriate animations on those views. + * Items are tracked by ids, since the underlying views that represent items + * pre- and post-layout may be different. + */ + private void runItemAnimations() { + for (int i = 0; i < mPreLayoutItems.size(); ++i) { + int id = mPreLayoutItems.keyAt(i); + final MenuItemLayoutInfo menuItemLayoutInfoPre = mPreLayoutItems.get(id); + final int postLayoutIndex = mPostLayoutItems.indexOfKey(id); + if (postLayoutIndex >= 0) { + // item exists pre and post: see if it's changed + final MenuItemLayoutInfo menuItemLayoutInfoPost = + mPostLayoutItems.valueAt(postLayoutIndex); + PropertyValuesHolder pvhX = null; + PropertyValuesHolder pvhY = null; + if (menuItemLayoutInfoPre.left != menuItemLayoutInfoPost.left) { + pvhX = PropertyValuesHolder.ofFloat(View.TRANSLATION_X, + (menuItemLayoutInfoPre.left - menuItemLayoutInfoPost.left), 0); + } + if (menuItemLayoutInfoPre.top != menuItemLayoutInfoPost.top) { + pvhY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, + menuItemLayoutInfoPre.top - menuItemLayoutInfoPost.top, 0); + } + if (pvhX != null || pvhY != null) { + for (int j = 0; j < mRunningItemAnimations.size(); ++j) { + ItemAnimationInfo oldInfo = mRunningItemAnimations.get(j); + if (oldInfo.id == id && oldInfo.animType == ItemAnimationInfo.MOVE) { + oldInfo.animator.cancel(); + } + } + ObjectAnimator anim; + if (pvhX != null) { + if (pvhY != null) { + anim = ObjectAnimator.ofPropertyValuesHolder(menuItemLayoutInfoPost.view, + pvhX, pvhY); + } else { + anim = ObjectAnimator.ofPropertyValuesHolder(menuItemLayoutInfoPost.view, pvhX); + } + } else { + anim = ObjectAnimator.ofPropertyValuesHolder(menuItemLayoutInfoPost.view, pvhY); + } + anim.setDuration(ITEM_ANIMATION_DURATION); + anim.start(); + ItemAnimationInfo info = new ItemAnimationInfo(id, menuItemLayoutInfoPost, anim, + ItemAnimationInfo.MOVE); + mRunningItemAnimations.add(info); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + for (int j = 0; j < mRunningItemAnimations.size(); ++j) { + if (mRunningItemAnimations.get(j).animator == animation) { + mRunningItemAnimations.remove(j); + break; + } + } + } + }); + } + mPostLayoutItems.remove(id); + } else { + // item used to be there, is now gone + float oldAlpha = 1; + for (int j = 0; j < mRunningItemAnimations.size(); ++j) { + ItemAnimationInfo oldInfo = mRunningItemAnimations.get(j); + if (oldInfo.id == id && oldInfo.animType == ItemAnimationInfo.FADE_IN) { + oldAlpha = oldInfo.menuItemLayoutInfo.view.getAlpha(); + oldInfo.animator.cancel(); + } + } + ObjectAnimator anim = ObjectAnimator.ofFloat(menuItemLayoutInfoPre.view, View.ALPHA, + oldAlpha, 0); + // Re-using the view from pre-layout assumes no view recycling + ((ViewGroup) mMenuView).getOverlay().add(menuItemLayoutInfoPre.view); + anim.setDuration(ITEM_ANIMATION_DURATION); + anim.start(); + ItemAnimationInfo info = new ItemAnimationInfo(id, menuItemLayoutInfoPre, anim, ItemAnimationInfo.FADE_OUT); + mRunningItemAnimations.add(info); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + for (int j = 0; j < mRunningItemAnimations.size(); ++j) { + if (mRunningItemAnimations.get(j).animator == animation) { + mRunningItemAnimations.remove(j); + break; + } + } + ((ViewGroup) mMenuView).getOverlay().remove(menuItemLayoutInfoPre.view); + } + }); + } + } + for (int i = 0; i < mPostLayoutItems.size(); ++i) { + int id = mPostLayoutItems.keyAt(i); + final int postLayoutIndex = mPostLayoutItems.indexOfKey(id); + if (postLayoutIndex >= 0) { + // item is new + final MenuItemLayoutInfo menuItemLayoutInfo = + mPostLayoutItems.valueAt(postLayoutIndex); + float oldAlpha = 0; + for (int j = 0; j < mRunningItemAnimations.size(); ++j) { + ItemAnimationInfo oldInfo = mRunningItemAnimations.get(j); + if (oldInfo.id == id && oldInfo.animType == ItemAnimationInfo.FADE_OUT) { + oldAlpha = oldInfo.menuItemLayoutInfo.view.getAlpha(); + oldInfo.animator.cancel(); + } + } + ObjectAnimator anim = ObjectAnimator.ofFloat(menuItemLayoutInfo.view, View.ALPHA, + oldAlpha, 1); + anim.start(); + anim.setDuration(ITEM_ANIMATION_DURATION); + ItemAnimationInfo info = new ItemAnimationInfo(id, menuItemLayoutInfo, anim, ItemAnimationInfo.FADE_IN); + mRunningItemAnimations.add(info); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + for (int j = 0; j < mRunningItemAnimations.size(); ++j) { + if (mRunningItemAnimations.get(j).animator == animation) { + mRunningItemAnimations.remove(j); + break; + } + } + } + }); + } + } + mPreLayoutItems.clear(); + mPostLayoutItems.clear(); + } + + /** + * Gets position/existence information on menu items before and after layout, + * which is then fed into runItemAnimations() + */ + private void setupItemAnimations() { + final ViewGroup menuViewParent = (ViewGroup) mMenuView; + computeMenuItemAnimationInfo(true); + final ViewTreeObserver observer = menuViewParent.getViewTreeObserver(); + if (observer != null) { + observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + computeMenuItemAnimationInfo(false); + observer.removeOnPreDrawListener(this); + runItemAnimations(); + return true; + } + }); + } + } + @Override public void updateMenuView(boolean cleared) { final ViewGroup menuViewParent = (ViewGroup) ((View) mMenuView).getParent(); if (menuViewParent != null) { + setupItemAnimations(); ActionBarTransition.beginDelayedTransition(menuViewParent); } super.updateMenuView(cleared); @@ -431,10 +623,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter MenuItemImpl item = visibleItems.get(i); if (item.requiresActionButton()) { - View v = getItemView(item, mScrapActionButtonView, parent); - if (mScrapActionButtonView == null) { - mScrapActionButtonView = v; - } + View v = getItemView(item, null, parent); if (mStrictWidthLimit) { cellsRemaining -= ActionMenuView.measureChildForCells(v, cellSize, cellsRemaining, querySpec, 0); @@ -460,10 +649,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter (!mStrictWidthLimit || cellsRemaining > 0); if (isAction) { - View v = getItemView(item, mScrapActionButtonView, parent); - if (mScrapActionButtonView == null) { - mScrapActionButtonView = v; - } + View v = getItemView(item, null, parent); if (mStrictWidthLimit) { final int cells = ActionMenuView.measureChildForCells(v, cellSize, cellsRemaining, querySpec, 0); @@ -819,4 +1005,53 @@ public class ActionMenuPresenter extends BaseMenuPresenter boolean mHasTintMode; boolean mHasTintList; } + + /** + * This class holds layout information for a menu item. This is used to determine + * pre- and post-layout information about menu items, which will then be used to + * determine appropriate item animations. + */ + private static class MenuItemLayoutInfo { + View view; + int left; + int top; + + MenuItemLayoutInfo(View view, boolean preLayout) { + left = view.getLeft(); + top = view.getTop(); + if (preLayout) { + // We track translation for pre-layout because a view might be mid-animation + // and we need this information to know where to animate from + left += view.getTranslationX(); + top += view.getTranslationY(); + } + this.view = view; + } + } + + /** + * This class is used to store information about currently-running item animations. + * This is used when new animations are scheduled to determine whether any existing + * animations need to be canceled, based on whether the running animations overlap + * with any new animations. For example, if an item is currently animating from + * location A to B and another change dictates that it be animated to C, then the current + * A-B animation will be canceled and a new animation to C will be started. + */ + private static class ItemAnimationInfo { + int id; + MenuItemLayoutInfo menuItemLayoutInfo; + Animator animator; + int animType; + static final int MOVE = 0; + static final int FADE_IN = 1; + static final int FADE_OUT = 2; + + ItemAnimationInfo(int id, MenuItemLayoutInfo info, Animator anim, int animType) { + this.id = id; + menuItemLayoutInfo = info; + animator = anim; + this.animType = animType; + } + } + } diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java index 1716dbd..45eee34 100644 --- a/core/java/android/widget/AnalogClock.java +++ b/core/java/android/widget/AnalogClock.java @@ -40,8 +40,10 @@ import java.util.TimeZone; * @attr ref android.R.styleable#AnalogClock_dial * @attr ref android.R.styleable#AnalogClock_hand_hour * @attr ref android.R.styleable#AnalogClock_hand_minute + * @deprecated This widget is no longer supported. */ @RemoteView +@Deprecated public class AnalogClock extends View { private Time mCalendar; diff --git a/core/java/android/widget/DatePickerCalendarDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java index 7b8a979..a157087 100755 --- a/core/java/android/widget/DatePickerCalendarDelegate.java +++ b/core/java/android/widget/DatePickerCalendarDelegate.java @@ -49,7 +49,6 @@ import java.util.Locale; * A delegate for picking up a date (day / month / year). */ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate { - private static final int USE_LOCALE = 0; private static final int UNINITIALIZED = -1; @@ -61,9 +60,9 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate { private static final int ANIMATION_DURATION = 300; - public static final int[] ATTRS_TEXT_COLOR = new int[]{com.android.internal.R.attr.textColor}; - - public static final int[] ATTRS_DISABLED_ALPHA = new int[]{ + private static final int[] ATTRS_TEXT_COLOR = new int[] { + com.android.internal.R.attr.textColor}; + private static final int[] ATTRS_DISABLED_ALPHA = new int[] { com.android.internal.R.attr.disabledAlpha}; private SimpleDateFormat mYearFormat; @@ -157,6 +156,8 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate { header.setBackground(a.getDrawable(R.styleable.DatePicker_headerBackground)); } + a.recycle(); + // Set up picker container. mAnimator = (ViewAnimator) mContainer.findViewById(R.id.animator); @@ -174,32 +175,10 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate { mYearPickerView.setDate(mCurrentDate.getTimeInMillis()); mYearPickerView.setOnYearSelectedListener(mOnYearSelectedListener); - final int yearTextAppearanceResId = a.getResourceId( - R.styleable.DatePicker_yearListItemTextAppearance, 0); - if (yearTextAppearanceResId != 0) { - mYearPickerView.setYearTextAppearance(yearTextAppearanceResId); - } - - final int yearActivatedTextAppearanceResId = a.getResourceId( - R.styleable.DatePicker_yearListItemActivatedTextAppearance, 0); - if (yearActivatedTextAppearanceResId != 0) { - mYearPickerView.setYearActivatedTextAppearance(yearActivatedTextAppearanceResId); - } - - a.recycle(); - // Set up content descriptions. mSelectDay = res.getString(R.string.select_day); mSelectYear = res.getString(R.string.select_year); - final Animation inAnim = new AlphaAnimation(0, 1); - inAnim.setDuration(ANIMATION_DURATION); - mAnimator.setInAnimation(inAnim); - - final Animation outAnim = new AlphaAnimation(1, 0); - outAnim.setDuration(ANIMATION_DURATION); - mAnimator.setOutAnimation(outAnim); - // Initialize for current locale. This also initializes the date, so no // need to call onDateChanged. onLocaleChanged(mCurrentLocale); diff --git a/core/java/android/widget/DayPickerAdapter.java b/core/java/android/widget/DayPickerAdapter.java index 4f9f09e..9a4b6f5 100644 --- a/core/java/android/widget/DayPickerAdapter.java +++ b/core/java/android/widget/DayPickerAdapter.java @@ -18,10 +18,15 @@ package android.widget; import com.android.internal.widget.PagerAdapter; +import android.annotation.IdRes; +import android.annotation.LayoutRes; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.util.SparseArray; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.SimpleMonthView.OnDayClickListener; @@ -37,9 +42,13 @@ class DayPickerAdapter extends PagerAdapter { private final Calendar mMinDate = Calendar.getInstance(); private final Calendar mMaxDate = Calendar.getInstance(); - private final SparseArray<SimpleMonthView> mItems = new SparseArray<>(); + private final SparseArray<ViewHolder> mItems = new SparseArray<>(); - private Calendar mSelectedDay = Calendar.getInstance(); + private final LayoutInflater mInflater; + private final int mLayoutResId; + private final int mCalendarViewId; + + private Calendar mSelectedDay = null; private int mMonthTextAppearance; private int mDayOfWeekTextAppearance; @@ -51,19 +60,29 @@ class DayPickerAdapter extends PagerAdapter { private OnDaySelectedListener mOnDaySelectedListener; + private int mCount; private int mFirstDayOfWeek; - public DayPickerAdapter(Context context) { + public DayPickerAdapter(@NonNull Context context, @LayoutRes int layoutResId, + @IdRes int calendarViewId) { + mInflater = LayoutInflater.from(context); + mLayoutResId = layoutResId; + mCalendarViewId = calendarViewId; + final TypedArray ta = context.obtainStyledAttributes(new int[] { com.android.internal.R.attr.colorControlHighlight}); mDayHighlightColor = ta.getColorStateList(0); ta.recycle(); } - public void setRange(Calendar min, Calendar max) { + public void setRange(@NonNull Calendar min, @NonNull Calendar max) { mMinDate.setTimeInMillis(min.getTimeInMillis()); mMaxDate.setTimeInMillis(max.getTimeInMillis()); + final int diffYear = mMaxDate.get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR); + final int diffMonth = mMaxDate.get(Calendar.MONTH) - mMinDate.get(Calendar.MONTH); + mCount = diffMonth + MONTHS_IN_YEAR * diffYear + 1; + // Positions are now invalid, clear everything and start over. notifyDataSetChanged(); } @@ -80,7 +99,7 @@ class DayPickerAdapter extends PagerAdapter { // Update displayed views. final int count = mItems.size(); for (int i = 0; i < count; i++) { - final SimpleMonthView monthView = mItems.valueAt(i); + final SimpleMonthView monthView = mItems.valueAt(i).calendar; monthView.setFirstDayOfWeek(weekStart); } } @@ -94,23 +113,25 @@ class DayPickerAdapter extends PagerAdapter { * * @param day the selected day */ - public void setSelectedDay(Calendar day) { + public void setSelectedDay(@Nullable Calendar day) { final int oldPosition = getPositionForDay(mSelectedDay); final int newPosition = getPositionForDay(day); // Clear the old position if necessary. - if (oldPosition != newPosition) { - final SimpleMonthView oldMonthView = mItems.get(oldPosition, null); + if (oldPosition != newPosition && oldPosition >= 0) { + final ViewHolder oldMonthView = mItems.get(oldPosition, null); if (oldMonthView != null) { - oldMonthView.setSelectedDay(-1); + oldMonthView.calendar.setSelectedDay(-1); } } // Set the new position. - final SimpleMonthView newMonthView = mItems.get(newPosition, null); - if (newMonthView != null) { - final int dayOfMonth = day.get(Calendar.DAY_OF_MONTH); - newMonthView.setSelectedDay(dayOfMonth); + if (newPosition >= 0) { + final ViewHolder newMonthView = mItems.get(newPosition, null); + if (newMonthView != null) { + final int dayOfMonth = day.get(Calendar.DAY_OF_MONTH); + newMonthView.calendar.setSelectedDay(dayOfMonth); + } } mSelectedDay = day; @@ -155,14 +176,13 @@ class DayPickerAdapter extends PagerAdapter { @Override public int getCount() { - final int diffYear = mMaxDate.get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR); - final int diffMonth = mMaxDate.get(Calendar.MONTH) - mMinDate.get(Calendar.MONTH); - return diffMonth + MONTHS_IN_YEAR * diffYear + 1; + return mCount; } @Override public boolean isViewFromObject(View view, Object object) { - return view == object; + final ViewHolder holder = (ViewHolder) object; + return view == holder.container; } private int getMonthForPosition(int position) { @@ -173,7 +193,11 @@ class DayPickerAdapter extends PagerAdapter { return position / MONTHS_IN_YEAR + mMinDate.get(Calendar.YEAR); } - private int getPositionForDay(Calendar day) { + private int getPositionForDay(@Nullable Calendar day) { + if (day == null) { + return -1; + } + final int yearOffset = (day.get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR)); final int monthOffset = (day.get(Calendar.MONTH) - mMinDate.get(Calendar.MONTH)); return yearOffset * MONTHS_IN_YEAR + monthOffset; @@ -181,7 +205,9 @@ class DayPickerAdapter extends PagerAdapter { @Override public Object instantiateItem(ViewGroup container, int position) { - final SimpleMonthView v = new SimpleMonthView(container.getContext()); + final View itemView = mInflater.inflate(mLayoutResId, container, false); + + final SimpleMonthView v = (SimpleMonthView) itemView.findViewById(mCalendarViewId); v.setOnDayClickListener(mOnDayClickListener); v.setMonthTextAppearance(mMonthTextAppearance); v.setDayOfWeekTextAppearance(mDayOfWeekTextAppearance); @@ -205,7 +231,7 @@ class DayPickerAdapter extends PagerAdapter { final int year = getYearForPosition(position); final int selectedDay; - if (mSelectedDay.get(Calendar.MONTH) == month) { + if (mSelectedDay != null && mSelectedDay.get(Calendar.MONTH) == month) { selectedDay = mSelectedDay.get(Calendar.DAY_OF_MONTH); } else { selectedDay = -1; @@ -227,33 +253,34 @@ class DayPickerAdapter extends PagerAdapter { v.setMonthParams(selectedDay, month, year, mFirstDayOfWeek, enabledDayRangeStart, enabledDayRangeEnd); + v.setPrevEnabled(position > 0); + v.setNextEnabled(position < mCount - 1); - mItems.put(position, v); + final ViewHolder holder = new ViewHolder(position, itemView, v); + mItems.put(position, holder); - container.addView(v); + container.addView(itemView); - return v; + return holder; } @Override public void destroyItem(ViewGroup container, int position, Object object) { - container.removeView(mItems.get(position)); + final ViewHolder holder = (ViewHolder) object; + container.removeView(holder.container); mItems.remove(position); } @Override public int getItemPosition(Object object) { - final int index = mItems.indexOfValue((SimpleMonthView) object); - if (index < 0) { - return mItems.keyAt(index); - } - return -1; + final ViewHolder holder = (ViewHolder) object; + return holder.position; } @Override public CharSequence getPageTitle(int position) { - final SimpleMonthView v = mItems.get(position); + final SimpleMonthView v = mItems.get(position).calendar; if (v != null) { return v.getTitle(); } @@ -275,9 +302,29 @@ class DayPickerAdapter extends PagerAdapter { } } } + + @Override + public void onNavigationClick(SimpleMonthView view, int direction, boolean animate) { + if (mOnDaySelectedListener != null) { + mOnDaySelectedListener.onNavigationClick(DayPickerAdapter.this, direction, animate); + } + } }; + private static class ViewHolder { + public final int position; + public final View container; + public final SimpleMonthView calendar; + + public ViewHolder(int position, View container, SimpleMonthView calendar) { + this.position = position; + this.container = container; + this.calendar = calendar; + } + } + public interface OnDaySelectedListener { public void onDaySelected(DayPickerAdapter view, Calendar day); + public void onNavigationClick(DayPickerAdapter view, int direction, boolean animate); } } diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java index a7ae926..ec2528f 100644 --- a/core/java/android/widget/DayPickerView.java +++ b/core/java/android/widget/DayPickerView.java @@ -88,7 +88,8 @@ class DayPickerView extends ViewPager { a.recycle(); // Set up adapter. - mAdapter = new DayPickerAdapter(context); + mAdapter = new DayPickerAdapter(context, + R.layout.date_picker_month_item_material, R.id.month_view); mAdapter.setMonthTextAppearance(monthTextAppearanceResId); mAdapter.setDayOfWeekTextAppearance(dayOfWeekTextAppearanceResId); mAdapter.setDayTextAppearance(dayTextAppearanceResId); @@ -128,6 +129,14 @@ class DayPickerView extends ViewPager { mOnDaySelectedListener.onDaySelected(DayPickerView.this, day); } } + + @Override + public void onNavigationClick(DayPickerAdapter view, int direction, boolean animate) { + // ViewPager clamps input values, so we don't need to worry + // about passing invalid indices. + final int nextItem = getCurrentItem() + direction; + setCurrentItem(nextItem, animate); + } }); } @@ -178,7 +187,6 @@ class DayPickerView extends ViewPager { * @param setSelected whether to set the specified day as selected */ private void setDate(long timeInMillis, boolean animate, boolean setSelected) { - // Set the selected day if (setSelected) { mSelectedDay.setTimeInMillis(timeInMillis); } @@ -187,6 +195,9 @@ class DayPickerView extends ViewPager { if (position != getCurrentItem()) { setCurrentItem(position, animate); } + + mTempCalendar.setTimeInMillis(timeInMillis); + mAdapter.setSelectedDay(mTempCalendar); } public long getDate() { diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 87fcd81..32b99a8 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -16,18 +16,6 @@ package android.widget; -import android.content.UndoManager; -import android.content.UndoOperation; -import android.content.UndoOwner; -import android.os.Build; -import android.os.Parcel; -import android.os.Parcelable; -import android.text.InputFilter; -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 android.R; import android.app.PendingIntent; import android.app.PendingIntent.CanceledException; @@ -35,6 +23,9 @@ import android.content.ClipData; import android.content.ClipData.Item; import android.content.Context; import android.content.Intent; +import android.content.UndoManager; +import android.content.UndoOperation; +import android.content.UndoOwner; import android.content.pm.PackageManager; import android.content.res.TypedArray; import android.graphics.Canvas; @@ -46,13 +37,17 @@ 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; +import android.os.Parcelable; import android.os.ParcelableParcel; import android.os.SystemClock; import android.provider.Settings; import android.text.DynamicLayout; import android.text.Editable; +import android.text.InputFilter; import android.text.InputType; import android.text.Layout; import android.text.ParcelableSpan; @@ -105,6 +100,11 @@ import android.widget.AdapterView.OnItemClickListener; import android.widget.TextView.Drawables; 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; import java.util.Arrays; import java.util.Comparator; @@ -2939,7 +2939,27 @@ public class Editor { * The default callback provides a subset of Select All, Cut, Copy and Paste actions, depending * on which of these this TextView supports. */ - private class SelectionActionModeCallback implements ActionMode.Callback { + private class SelectionActionModeCallback extends ActionMode.Callback2 { + private final Path mSelectionPath = new Path(); + private final RectF mSelectionBounds = new RectF(); + + private int mSelectionHandleHeight; + private int mInsertionHandleHeight; + + public SelectionActionModeCallback() { + SelectionModifierCursorController selectionController = getSelectionController(); + if (selectionController.mStartHandle == null) { + selectionController.initDrawables(); + selectionController.initHandles(); + } + mSelectionHandleHeight = Math.max( + mSelectHandleLeft.getMinimumHeight(), mSelectHandleRight.getMinimumHeight()); + InsertionPointCursorController insertionController = getInsertionController(); + if (insertionController != null) { + insertionController.getHandle(); + mInsertionHandleHeight = mSelectHandleCenter.getMinimumHeight(); + } + } @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { @@ -2956,13 +2976,6 @@ public class Editor { mode.setSubtitle(null); mode.setTitleOptionalHint(true); - 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.canCut()) { menu.add(0, TextView.ID_CUT, 0, com.android.internal.R.string.cut). setIcon(styledAttributes.getResourceId( @@ -2990,6 +3003,13 @@ public class Editor { 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.isSuggestionsEnabled() && isCursorInsideSuggestionSpan()) { menu.add(0, TextView.ID_REPLACE, 0, com.android.internal.R.string.replace). setShowAsAction( @@ -3057,6 +3077,40 @@ public class Editor { mSelectionActionMode = null; } + + @Override + public void onGetContentRect(ActionMode mode, View view, Rect outRect) { + if (!view.equals(mTextView) || mTextView.getLayout() == null) { + super.onGetContentRect(mode, view, outRect); + return; + } + if (mTextView.getSelectionStart() != mTextView.getSelectionEnd()) { + // We have a selection. + mSelectionPath.reset(); + mTextView.getLayout().getSelectionPath( + mTextView.getSelectionStart(), mTextView.getSelectionEnd(), mSelectionPath); + mSelectionPath.computeBounds(mSelectionBounds, true); + mSelectionBounds.bottom += mSelectionHandleHeight; + } else { + // We have a single cursor. + int line = mTextView.getLayout().getLineForOffset(mTextView.getSelectionStart()); + float primaryHorizontal = + mTextView.getLayout().getPrimaryHorizontal(mTextView.getSelectionStart()); + mSelectionBounds.set( + primaryHorizontal, + mTextView.getLayout().getLineTop(line), + primaryHorizontal + 1, + mTextView.getLayout().getLineTop(line + 1) + mInsertionHandleHeight); + } + // Take TextView's padding and scroll into account. + int textHorizontalOffset = mTextView.viewportToContentHorizontalOffset(); + int textVerticalOffset = mTextView.viewportToContentVerticalOffset(); + outRect.set( + (int) Math.floor(mSelectionBounds.left + textHorizontalOffset), + (int) Math.floor(mSelectionBounds.top + textVerticalOffset), + (int) Math.ceil(mSelectionBounds.right + textHorizontalOffset), + (int) Math.ceil(mSelectionBounds.bottom + textVerticalOffset)); + } } private void onReplace() { @@ -3066,97 +3120,6 @@ public class Editor { showSuggestions(); } - private class ActionPopupWindow extends PinnedPopupWindow implements OnClickListener { - private static final int POPUP_TEXT_LAYOUT = - com.android.internal.R.layout.text_edit_action_popup_text; - private TextView mPasteTextView; - private TextView mReplaceTextView; - - @Override - protected void createPopupWindow() { - mPopupWindow = new PopupWindow(mTextView.getContext(), null, - com.android.internal.R.attr.textSelectHandleWindowStyle); - mPopupWindow.setClippingEnabled(true); - } - - @Override - protected void initContentView() { - LinearLayout linearLayout = new LinearLayout(mTextView.getContext()); - linearLayout.setOrientation(LinearLayout.HORIZONTAL); - mContentView = linearLayout; - mContentView.setBackgroundResource( - com.android.internal.R.drawable.text_edit_paste_window); - - LayoutInflater inflater = (LayoutInflater) mTextView.getContext(). - getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - LayoutParams wrapContent = new LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - - mPasteTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null); - mPasteTextView.setLayoutParams(wrapContent); - mContentView.addView(mPasteTextView); - mPasteTextView.setText(com.android.internal.R.string.paste); - mPasteTextView.setOnClickListener(this); - - mReplaceTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null); - mReplaceTextView.setLayoutParams(wrapContent); - mContentView.addView(mReplaceTextView); - mReplaceTextView.setText(com.android.internal.R.string.replace); - mReplaceTextView.setOnClickListener(this); - } - - @Override - public void show() { - boolean canPaste = mTextView.canPaste(); - boolean canSuggest = mTextView.isSuggestionsEnabled() && isCursorInsideSuggestionSpan(); - mPasteTextView.setVisibility(canPaste ? View.VISIBLE : View.GONE); - mReplaceTextView.setVisibility(canSuggest ? View.VISIBLE : View.GONE); - - if (!canPaste && !canSuggest) return; - - super.show(); - } - - @Override - public void onClick(View view) { - if (view == mPasteTextView && mTextView.canPaste()) { - mTextView.onTextContextMenuItem(TextView.ID_PASTE); - hide(); - } else if (view == mReplaceTextView) { - onReplace(); - } - } - - @Override - protected int getTextOffset() { - return (mTextView.getSelectionStart() + mTextView.getSelectionEnd()) / 2; - } - - @Override - protected int getVerticalLocalPosition(int line) { - return mTextView.getLayout().getLineTop(line) - mContentView.getMeasuredHeight(); - } - - @Override - protected int clipVertically(int positionY) { - if (positionY < 0) { - final int offset = getTextOffset(); - final Layout layout = mTextView.getLayout(); - final int line = layout.getLineForOffset(offset); - positionY += layout.getLineBottom(line) - layout.getLineTop(line); - positionY += mContentView.getMeasuredHeight(); - - // Assumes insertion and selection handles share the same height - final Drawable handle = mTextView.getContext().getDrawable( - mTextView.mTextSelectHandleRes); - positionY += handle.getIntrinsicHeight(); - } - - return positionY; - } - } - /** * A listener to call {@link InputMethodManager#updateCursorAnchorInfo(View, CursorAnchorInfo)} * while the input method is requesting the cursor/anchor position. Does nothing as long as @@ -4102,7 +4065,6 @@ public class Editor { } class SelectionModifierCursorController implements CursorController { - private static final int DELAY_BEFORE_REPLACE_ACTION = 200; // milliseconds // The cursor controller handles, lazily created when shown. private SelectionStartHandleView mStartHandle; private SelectionEndHandleView mEndHandle; @@ -4664,8 +4626,8 @@ public class Editor { // Otherwise the user inserted the composition. String newText = TextUtils.substring(source, start, end); - EditOperation edit = new EditOperation(mEditor, false, "", dstart, newText); - recordEdit(edit); + EditOperation edit = new EditOperation(mEditor, "", dstart, newText); + recordEdit(edit, false /* forceMerge */); return true; } @@ -4684,11 +4646,15 @@ public class Editor { // Build a new operation with all the information from this edit. String newText = TextUtils.substring(source, start, end); String oldText = TextUtils.substring(dest, dstart, dend); - EditOperation edit = new EditOperation(mEditor, forceMerge, oldText, dstart, newText); - recordEdit(edit); + EditOperation edit = new EditOperation(mEditor, oldText, dstart, newText); + recordEdit(edit, forceMerge); } - private void recordEdit(EditOperation edit) { + /** + * Fetches the last undo operation and checks to see if a new edit should be merged into it. + * If forceMerge is true then the new edit is always merged. + */ + private void recordEdit(EditOperation edit, boolean forceMerge) { // Fetch the last edit operation and attempt to merge in the new edit. final UndoManager um = mEditor.mUndoManager; um.beginUpdate("Edit text"); @@ -4698,6 +4664,11 @@ public class Editor { // Add this as the first edit. if (DEBUG_UNDO) Log.d(TAG, "filter: adding first op " + edit); um.addOperation(edit, UndoManager.MERGE_MODE_NONE); + } else if (forceMerge) { + // Forced merges take priority because they could be the result of a non-user-edit + // change and this case should not create a new undo operation. + if (DEBUG_UNDO) Log.d(TAG, "filter: force merge " + edit); + lastEdit.forceMergeWith(edit); } else if (!mIsUserEdit) { // An application directly modified the Editable outside of a text edit. Treat this // as a new change and don't attempt to merge. @@ -4773,7 +4744,6 @@ public class Editor { private static final int TYPE_REPLACE = 2; private int mType; - private boolean mForceMerge; private String mOldText; private int mOldTextStart; private String mNewText; @@ -4784,13 +4754,10 @@ public class Editor { /** * Constructs an edit operation from a text input operation on editor that replaces the - * oldText starting at dstart with newText. If forceMerge is true then always forcibly - * merge this operation with any previous one. + * oldText starting at dstart with newText. */ - public EditOperation(Editor editor, boolean forceMerge, String oldText, int dstart, - String newText) { + public EditOperation(Editor editor, String oldText, int dstart, String newText) { super(editor.mUndoOwner); - mForceMerge = forceMerge; mOldText = oldText; mNewText = newText; @@ -4817,7 +4784,6 @@ public class Editor { public EditOperation(Parcel src, ClassLoader loader) { super(src, loader); mType = src.readInt(); - mForceMerge = src.readInt() != 0; mOldText = src.readString(); mOldTextStart = src.readInt(); mNewText = src.readString(); @@ -4829,7 +4795,6 @@ public class Editor { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mType); - dest.writeInt(mForceMerge ? 1 : 0); dest.writeString(mOldText); dest.writeInt(mOldTextStart); dest.writeString(mNewText); @@ -4881,10 +4846,6 @@ public class Editor { Log.d(TAG, "mergeWith old " + this); Log.d(TAG, "mergeWith new " + edit); } - if (edit.mForceMerge) { - forceMergeWith(edit); - return true; - } switch (mType) { case TYPE_INSERT: return mergeInsertWith(edit); @@ -4942,7 +4903,7 @@ public class Editor { * Forcibly creates a single merged edit operation by simulating the entire text * contents being replaced. */ - private void forceMergeWith(EditOperation edit) { + public void forceMergeWith(EditOperation edit) { if (DEBUG_UNDO) Log.d(TAG, "forceMerge"); Editor editor = getOwnerData(); diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java index c6e3dc8..be0ca71 100644 --- a/core/java/android/widget/GridView.java +++ b/core/java/android/widget/GridView.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.Intent; import android.content.res.TypedArray; import android.graphics.Rect; +import android.os.Bundle; import android.os.Trace; import android.util.AttributeSet; import android.util.MathUtils; @@ -32,12 +33,15 @@ import android.view.ViewDebug; import android.view.ViewGroup; import android.view.ViewRootImpl; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.accessibility.AccessibilityNodeProvider; import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo; import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo; import android.view.animation.GridLayoutAnimationController; import android.widget.RemoteViews.RemoteView; +import com.android.internal.R; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -117,7 +121,7 @@ public class GridView extends AbsListView { } public GridView(Context context, AttributeSet attrs) { - this(context, attrs, com.android.internal.R.attr.gridViewStyle); + this(context, attrs, R.attr.gridViewStyle); } public GridView(Context context, AttributeSet attrs, int defStyleAttr) { @@ -128,30 +132,30 @@ public class GridView extends AbsListView { super(context, attrs, defStyleAttr, defStyleRes); final TypedArray a = context.obtainStyledAttributes( - attrs, com.android.internal.R.styleable.GridView, defStyleAttr, defStyleRes); + attrs, R.styleable.GridView, defStyleAttr, defStyleRes); int hSpacing = a.getDimensionPixelOffset( - com.android.internal.R.styleable.GridView_horizontalSpacing, 0); + R.styleable.GridView_horizontalSpacing, 0); setHorizontalSpacing(hSpacing); int vSpacing = a.getDimensionPixelOffset( - com.android.internal.R.styleable.GridView_verticalSpacing, 0); + R.styleable.GridView_verticalSpacing, 0); setVerticalSpacing(vSpacing); - int index = a.getInt(com.android.internal.R.styleable.GridView_stretchMode, STRETCH_COLUMN_WIDTH); + int index = a.getInt(R.styleable.GridView_stretchMode, STRETCH_COLUMN_WIDTH); if (index >= 0) { setStretchMode(index); } - int columnWidth = a.getDimensionPixelOffset(com.android.internal.R.styleable.GridView_columnWidth, -1); + int columnWidth = a.getDimensionPixelOffset(R.styleable.GridView_columnWidth, -1); if (columnWidth > 0) { setColumnWidth(columnWidth); } - int numColumns = a.getInt(com.android.internal.R.styleable.GridView_numColumns, 1); + int numColumns = a.getInt(R.styleable.GridView_numColumns, 1); setNumColumns(numColumns); - index = a.getInt(com.android.internal.R.styleable.GridView_gravity, -1); + index = a.getInt(R.styleable.GridView_gravity, -1); if (index >= 0) { setGravity(index); } @@ -2356,6 +2360,36 @@ public class GridView extends AbsListView { final CollectionInfo collectionInfo = CollectionInfo.obtain( rowsCount, columnsCount, false, selectionMode); info.setCollectionInfo(collectionInfo); + + if (columnsCount > 0 || rowsCount > 0) { + info.addAction(AccessibilityAction.ACTION_SCROLL_TO_POSITION); + } + } + + /** @hide */ + @Override + public boolean performAccessibilityActionInternal(int action, Bundle arguments) { + if (super.performAccessibilityActionInternal(action, arguments)) { + return true; + } + + switch (action) { + case R.id.accessibilityActionScrollToPosition: { + // GridView only supports scrolling in one direction, so we can + // ignore the column argument. + final int numColumns = getNumColumns(); + final int row = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_ROW_INT, -1); + final int position = Math.min(row * numColumns, getCount() - 1); + if (row >= 0) { + // The accessibility service gets data asynchronously, so + // we'll be a little lenient by clamping the last position. + smoothScrollToPosition(position); + return true; + } + } break; + } + + return false; } @Override diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index 041796b..6d2f368 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -687,12 +687,19 @@ public class ImageView extends View { return mDrawMatrix; } + /** + * Adds a transformation {@link Matrix} that is applied + * to the view's drawable when it is drawn. Allows custom scaling, + * translation, and perspective distortion. + * + * @param matrix the transformation parameters in matrix form + */ public void setImageMatrix(Matrix matrix) { - // collaps null and identity to just null + // collapse null and identity to just null if (matrix != null && matrix.isIdentity()) { matrix = null; } - + // don't invalidate unless we're actually changing our matrix if (matrix == null && !mMatrix.isIdentity() || matrix != null && !mMatrix.equals(matrix)) { diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index ee2c055..def5929 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -16,6 +16,7 @@ package android.widget; +import android.os.Bundle; import android.os.Trace; import com.android.internal.R; import com.android.internal.util.Predicate; @@ -42,6 +43,7 @@ import android.view.ViewGroup; import android.view.ViewParent; import android.view.ViewRootImpl; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo; import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo; import android.view.accessibility.AccessibilityNodeProvider; @@ -3893,6 +3895,33 @@ public class ListView extends AbsListView { final CollectionInfo collectionInfo = CollectionInfo.obtain( rowsCount, 1, false, selectionMode); info.setCollectionInfo(collectionInfo); + + if (rowsCount > 0) { + info.addAction(AccessibilityAction.ACTION_SCROLL_TO_POSITION); + } + } + + /** @hide */ + @Override + public boolean performAccessibilityActionInternal(int action, Bundle arguments) { + if (super.performAccessibilityActionInternal(action, arguments)) { + return true; + } + + switch (action) { + case R.id.accessibilityActionScrollToPosition: { + final int row = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_ROW_INT, -1); + final int position = Math.min(row, getCount() - 1); + if (row >= 0) { + // The accessibility service gets data asynchronously, so + // we'll be a little lenient by clamping the last position. + smoothScrollToPosition(position); + return true; + } + } break; + } + + return false; } @Override diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index 50d701a..205d35e 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -316,7 +316,7 @@ public class ProgressBar extends View { mProgressTintInfo = new ProgressTintInfo(); } mProgressTintInfo.mProgressTintMode = Drawable.parseTintMode(a.getInt( - R.styleable.ProgressBar_progressBackgroundTintMode, -1), null); + R.styleable.ProgressBar_progressTintMode, -1), null); mProgressTintInfo.mHasProgressTintMode = true; } @@ -334,7 +334,7 @@ public class ProgressBar extends View { mProgressTintInfo = new ProgressTintInfo(); } mProgressTintInfo.mProgressBackgroundTintMode = Drawable.parseTintMode(a.getInt( - R.styleable.ProgressBar_progressTintMode, -1), null); + R.styleable.ProgressBar_progressBackgroundTintMode, -1), null); mProgressTintInfo.mHasProgressBackgroundTintMode = true; } @@ -365,7 +365,7 @@ public class ProgressBar extends View { mProgressTintInfo.mHasSecondaryProgressTint = true; } - if (a.hasValue(R.styleable.ProgressBar_indeterminateTint)) { + if (a.hasValue(R.styleable.ProgressBar_indeterminateTintMode)) { if (mProgressTintInfo == null) { mProgressTintInfo = new ProgressTintInfo(); } @@ -1810,9 +1810,7 @@ public class ProgressBar extends View { } if (mRefreshProgressRunnable != null) { removeCallbacks(mRefreshProgressRunnable); - } - if (mRefreshProgressRunnable != null && mRefreshIsPosted) { - removeCallbacks(mRefreshProgressRunnable); + mRefreshIsPosted = false; } if (mAccessibilityEventSender != null) { removeCallbacks(mAccessibilityEventSender); diff --git a/core/java/android/widget/RadialTimePickerView.java b/core/java/android/widget/RadialTimePickerView.java index 20aa972..9571109 100644 --- a/core/java/android/widget/RadialTimePickerView.java +++ b/core/java/android/widget/RadialTimePickerView.java @@ -79,8 +79,10 @@ public class RadialTimePickerView extends View { // Transparent alpha level private static final int ALPHA_TRANSPARENT = 0; - private static final int DEGREES_FOR_ONE_HOUR = 30; - private static final int DEGREES_FOR_ONE_MINUTE = 6; + private static final int HOURS_IN_CIRCLE = 12; + private static final int MINUTES_IN_CIRCLE = 60; + private static final int DEGREES_FOR_ONE_HOUR = 360 / HOURS_IN_CIRCLE; + private static final int DEGREES_FOR_ONE_MINUTE = 360 / MINUTES_IN_CIRCLE; private static final int[] HOURS_NUMBERS = {12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; private static final int[] HOURS_NUMBERS_24 = {0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; @@ -122,8 +124,9 @@ public class RadialTimePickerView extends View { private final Paint mPaintCenter = new Paint(); private final Paint[][] mPaintSelector = new Paint[2][3]; - private final int[][] mColorSelector = new int[2][3]; - private final IntHolder[][] mAlphaSelector = new IntHolder[2][3]; + + private final int mSelectorColor; + private final int mSelectorDotColor; private final Paint mPaintBackground = new Paint(); @@ -139,14 +142,15 @@ public class RadialTimePickerView extends View { private final float[] mInnerTextX = new float[12]; private final float[] mInnerTextY = new float[12]; - private final int[] mLineLength = new int[3]; - private final int[] mSelectionDegrees = new int[3]; + private final int[] mSelectionDegrees = new int[2]; private final ArrayList<Animator> mHoursToMinutesAnims = new ArrayList<>(); private final ArrayList<Animator> mMinuteToHoursAnims = new ArrayList<>(); private final RadialPickerTouchHelper mTouchHelper; + private final Path mSelectorPath = new Path(); + private boolean mIs24HourMode; private boolean mShowHours; @@ -165,13 +169,13 @@ public class RadialTimePickerView extends View { private int mYCenter; private int mCircleRadius; - private int mMinHypotenuseForInnerNumber; - private int mMaxHypotenuseForOuterNumber; - private int mHalfwayHypotenusePoint; + private int mMinDistForInnerNumber; + private int mMaxDistForOuterNumber; + private int mHalfwayDist; private String[] mOuterTextHours; private String[] mInnerTextHours; - private String[] mOuterTextMinutes; + private String[] mMinutesText; private AnimatorSet mTransition; private int mAmOrPm; @@ -316,11 +320,6 @@ public class RadialTimePickerView extends View { for (int i = 0; i < mAlpha.length; i++) { mAlpha[i] = new IntHolder(ALPHA_OPAQUE); } - for (int i = 0; i < mAlphaSelector.length; i++) { - for (int j = 0; j < mAlphaSelector[i].length; j++) { - mAlphaSelector[i][j] = new IntHolder(ALPHA_OPAQUE); - } - } mTextColor[HOURS] = a.getColorStateList(R.styleable.TimePicker_numbersTextColor); mTextColor[HOURS_INNER] = a.getColorStateList(R.styleable.TimePicker_numbersInnerTextColor); @@ -345,33 +344,28 @@ public class RadialTimePickerView extends View { final int[] activatedStateSet = StateSet.get( StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED); + mSelectorColor = selectorActivatedColor; + mSelectorDotColor = mTextColor[HOURS].getColorForState(activatedStateSet, 0); + mPaintSelector[HOURS][SELECTOR_CIRCLE] = new Paint(); mPaintSelector[HOURS][SELECTOR_CIRCLE].setAntiAlias(true); - mColorSelector[HOURS][SELECTOR_CIRCLE] = selectorActivatedColor; mPaintSelector[HOURS][SELECTOR_DOT] = new Paint(); mPaintSelector[HOURS][SELECTOR_DOT].setAntiAlias(true); - mColorSelector[HOURS][SELECTOR_DOT] = - mTextColor[HOURS].getColorForState(activatedStateSet, 0); mPaintSelector[HOURS][SELECTOR_LINE] = new Paint(); mPaintSelector[HOURS][SELECTOR_LINE].setAntiAlias(true); mPaintSelector[HOURS][SELECTOR_LINE].setStrokeWidth(2); - mColorSelector[HOURS][SELECTOR_LINE] = selectorActivatedColor; mPaintSelector[MINUTES][SELECTOR_CIRCLE] = new Paint(); mPaintSelector[MINUTES][SELECTOR_CIRCLE].setAntiAlias(true); - mColorSelector[MINUTES][SELECTOR_CIRCLE] = selectorActivatedColor; mPaintSelector[MINUTES][SELECTOR_DOT] = new Paint(); mPaintSelector[MINUTES][SELECTOR_DOT].setAntiAlias(true); - mColorSelector[MINUTES][SELECTOR_DOT] = - mTextColor[MINUTES].getColorForState(activatedStateSet, 0); mPaintSelector[MINUTES][SELECTOR_LINE] = new Paint(); mPaintSelector[MINUTES][SELECTOR_LINE].setAntiAlias(true); mPaintSelector[MINUTES][SELECTOR_LINE].setStrokeWidth(2); - mColorSelector[MINUTES][SELECTOR_LINE] = selectorActivatedColor; mPaintBackground.setColor(a.getColor(R.styleable.TimePicker_numbersBackgroundColor, context.getColor(R.color.timepicker_default_numbers_background_color_material))); @@ -469,11 +463,10 @@ public class RadialTimePickerView extends View { private void setCurrentHourInternal(int hour, boolean callback, boolean autoAdvance) { final int degrees = (hour % 12) * DEGREES_FOR_ONE_HOUR; mSelectionDegrees[HOURS] = degrees; - mSelectionDegrees[HOURS_INNER] = degrees; // 0 is 12 AM (midnight) and 12 is 12 PM (noon). final int amOrPm = (hour == 0 || (hour % 24) < 12) ? AM : PM; - final boolean isOnInnerCircle = mIs24HourMode && hour >= 1 && hour <= 12; + final boolean isOnInnerCircle = getInnerCircleForHour(hour); if (mAmOrPm != amOrPm || mIsOnInnerCircle != isOnInnerCircle) { mAmOrPm = amOrPm; mIsOnInnerCircle = isOnInnerCircle; @@ -495,8 +488,7 @@ public class RadialTimePickerView extends View { * @return the current hour between 0 and 23 (inclusive) */ public int getCurrentHour() { - return getHourForDegrees( - mSelectionDegrees[mIsOnInnerCircle ? HOURS_INNER : HOURS], mIsOnInnerCircle); + return getHourForDegrees(mSelectionDegrees[HOURS], mIsOnInnerCircle); } private int getHourForDegrees(int degrees, boolean innerCircle) { @@ -504,11 +496,11 @@ public class RadialTimePickerView extends View { if (mIs24HourMode) { // Convert the 12-hour value into 24-hour time based on where the // selector is positioned. - if (innerCircle && hour == 0) { - // Inner circle is 1 through 12. + if (!innerCircle && hour == 0) { + // Outer circle is 1 through 12. hour = 12; - } else if (!innerCircle && hour != 0) { - // Outer circle is 13 through 23 and 0. + } else if (innerCircle && hour != 0) { + // Inner circle is 13 through 23 and 0. hour += 12; } } else if (mAmOrPm == PM) { @@ -517,6 +509,9 @@ public class RadialTimePickerView extends View { return hour; } + /** + * @param hour the hour in 24-hour time or 12-hour time + */ private int getDegreesForHour(int hour) { // Convert to be 0-11. if (mIs24HourMode) { @@ -529,12 +524,19 @@ public class RadialTimePickerView extends View { return hour * DEGREES_FOR_ONE_HOUR; } + /** + * @param hour the hour in 24-hour time or 12-hour time + */ + private boolean getInnerCircleForHour(int hour) { + return mIs24HourMode && (hour == 0 || hour > 12); + } + public void setCurrentMinute(int minute) { setCurrentMinuteInternal(minute, true); } private void setCurrentMinuteInternal(int minute, boolean callback) { - mSelectionDegrees[MINUTES] = (minute % 60) * DEGREES_FOR_ONE_MINUTE; + mSelectionDegrees[MINUTES] = (minute % MINUTES_IN_CIRCLE) * DEGREES_FOR_ONE_MINUTE; invalidate(); @@ -579,6 +581,7 @@ public class RadialTimePickerView extends View { initData(); invalidate(); + mTouchHelper.invalidateRoot(); } public void showMinutes(boolean animate) { @@ -594,14 +597,15 @@ public class RadialTimePickerView extends View { initData(); invalidate(); + mTouchHelper.invalidateRoot(); } private void initHoursAndMinutesText() { // Initialize the hours and minutes numbers. for (int i = 0; i < 12; i++) { mHours12Texts[i] = String.format("%d", HOURS_NUMBERS[i]); - mOuterHours24Texts[i] = String.format("%02d", HOURS_NUMBERS_24[i]); - mInnerHours24Texts[i] = String.format("%d", HOURS_NUMBERS[i]); + mInnerHours24Texts[i] = String.format("%02d", HOURS_NUMBERS_24[i]); + mOuterHours24Texts[i] = String.format("%d", HOURS_NUMBERS[i]); mMinutesTexts[i] = String.format("%02d", MINUTES_NUMBERS[i]); } } @@ -612,22 +616,16 @@ public class RadialTimePickerView extends View { mInnerTextHours = mInnerHours24Texts; } else { mOuterTextHours = mHours12Texts; - mInnerTextHours = null; + mInnerTextHours = mHours12Texts; } - mOuterTextMinutes = mMinutesTexts; + mMinutesText = mMinutesTexts; final int hoursAlpha = mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT; mAlpha[HOURS].setValue(hoursAlpha); - mAlphaSelector[HOURS][SELECTOR_CIRCLE].setValue(hoursAlpha); - mAlphaSelector[HOURS][SELECTOR_DOT].setValue(hoursAlpha); - mAlphaSelector[HOURS][SELECTOR_LINE].setValue(hoursAlpha); final int minutesAlpha = mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE; mAlpha[MINUTES].setValue(minutesAlpha); - mAlphaSelector[MINUTES][SELECTOR_CIRCLE].setValue(minutesAlpha); - mAlphaSelector[MINUTES][SELECTOR_DOT].setValue(minutesAlpha); - mAlphaSelector[MINUTES][SELECTOR_LINE].setValue(minutesAlpha); } @Override @@ -640,9 +638,9 @@ public class RadialTimePickerView extends View { mYCenter = getHeight() / 2; mCircleRadius = Math.min(mXCenter, mYCenter); - mMinHypotenuseForInnerNumber = mCircleRadius - mTextInset[HOURS_INNER] - mSelectorRadius; - mMaxHypotenuseForOuterNumber = mCircleRadius - mTextInset[HOURS] + mSelectorRadius; - mHalfwayHypotenusePoint = mCircleRadius - (mTextInset[HOURS] + mTextInset[HOURS_INNER]) / 2; + mMinDistForInnerNumber = mCircleRadius - mTextInset[HOURS_INNER] - mSelectorRadius; + mMaxDistForOuterNumber = mCircleRadius - mTextInset[HOURS] + mSelectorRadius; + mHalfwayDist = mCircleRadius - (mTextInset[HOURS] + mTextInset[HOURS_INNER]) / 2; calculatePositionsHours(); calculatePositionsMinutes(); @@ -675,7 +673,7 @@ public class RadialTimePickerView extends View { mOuterTextHours, mOuterTextX[HOURS], mOuterTextY[HOURS], mPaint[HOURS], hoursAlpha, !mIsOnInnerCircle, mSelectionDegrees[HOURS], false); - // Draw inner hours (12-23) for 24-hour time. + // Draw inner hours (13-00) for 24-hour time. if (mIs24HourMode && mInnerTextHours != null) { drawTextElements(canvas, mTextSize[HOURS_INNER], mTypeface, mTextColor[HOURS_INNER], mInnerTextHours, mInnerTextX, mInnerTextY, mPaint[HOURS], hoursAlpha, @@ -687,6 +685,7 @@ public class RadialTimePickerView extends View { private void drawMinutes(Canvas canvas, float alphaMod) { final int minutesAlpha = (int) (mAlpha[MINUTES].getValue() * alphaMod + 0.5f); if (minutesAlpha > 0) { + // Draw the minute selector under the elements. drawSelector(canvas, MINUTES, mSelectorPath, alphaMod); // Exclude the selector region, then draw minutes with no @@ -694,7 +693,7 @@ public class RadialTimePickerView extends View { canvas.save(Canvas.CLIP_SAVE_FLAG); canvas.clipPath(mSelectorPath, Region.Op.DIFFERENCE); drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mTextColor[MINUTES], - mOuterTextMinutes, mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES], + mMinutesText, mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES], minutesAlpha, false, 0, false); canvas.restore(); @@ -703,7 +702,7 @@ public class RadialTimePickerView extends View { canvas.save(Canvas.CLIP_SAVE_FLAG); canvas.clipPath(mSelectorPath, Region.Op.INTERSECT); drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mTextColor[MINUTES], - mOuterTextMinutes, mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES], + mMinutesText, mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES], minutesAlpha, true, mSelectionDegrees[MINUTES], true); canvas.restore(); } @@ -714,69 +713,61 @@ public class RadialTimePickerView extends View { canvas.drawCircle(mXCenter, mYCenter, mCenterDotRadius, mPaintCenter); } + private int applyAlpha(int argb, int alpha) { + final int srcAlpha = (argb >> 24) & 0xFF; + final int dstAlpha = (int) (srcAlpha * (alpha / 255.0) + 0.5f); + return (0xFFFFFF & argb) | (dstAlpha << 24); + } + private int getMultipliedAlpha(int argb, int alpha) { return (int) (Color.alpha(argb) * (alpha / 255.0) + 0.5); } - private final Path mSelectorPath = new Path(); - private void drawSelector(Canvas canvas, int index, Path selectorPath, float alphaMod) { - // Calculate the current radius at which to place the selection circle. - mLineLength[index] = mCircleRadius - mTextInset[index]; - - final double selectionRadians = Math.toRadians(mSelectionDegrees[index]); - - float pointX = mXCenter + (int) (mLineLength[index] * Math.sin(selectionRadians)); - float pointY = mYCenter - (int) (mLineLength[index] * Math.cos(selectionRadians)); + final int alpha = (int) (mAlpha[index % 2].getValue() * alphaMod + 0.5f); + final int color = applyAlpha(mSelectorColor, alpha); - int color; - int alpha; - Paint paint; - - // Draw the selection circle - color = mColorSelector[index % 2][SELECTOR_CIRCLE]; - alpha = (int) (mAlphaSelector[index % 2][SELECTOR_CIRCLE].getValue() * alphaMod + 0.5f); - paint = mPaintSelector[index % 2][SELECTOR_CIRCLE]; + // Calculate the current radius at which to place the selection circle. + final int selRadius = mSelectorRadius; + final int selLength = mCircleRadius - mTextInset[index]; + final double selAngleRad = Math.toRadians(mSelectionDegrees[index % 2]); + final float selCenterX = mXCenter + selLength * (float) Math.sin(selAngleRad); + final float selCenterY = mYCenter - selLength * (float) Math.cos(selAngleRad); + + // Draw the selection circle. + final Paint paint = mPaintSelector[index % 2][SELECTOR_CIRCLE]; paint.setColor(color); - paint.setAlpha(getMultipliedAlpha(color, alpha)); - canvas.drawCircle(pointX, pointY, mSelectorRadius, paint); + canvas.drawCircle(selCenterX, selCenterY, selRadius, paint); // If needed, set up the clip path for later. if (selectorPath != null) { - mSelectorPath.reset(); - mSelectorPath.addCircle(pointX, pointY, mSelectorRadius, Path.Direction.CCW); + selectorPath.reset(); + selectorPath.addCircle(selCenterX, selCenterY, selRadius, Path.Direction.CCW); } - // Draw the dot if needed. - final boolean shouldDrawDot = mSelectionDegrees[index] % 30 != 0; + // Draw the dot if we're between two items. + final boolean shouldDrawDot = mSelectionDegrees[index % 2] % 30 != 0; if (shouldDrawDot) { - // We're not on a direct tick - color = mColorSelector[index % 2][SELECTOR_DOT]; - alpha = (int) (mAlphaSelector[index % 2][SELECTOR_DOT].getValue() * alphaMod + 0.5f); - paint = mPaintSelector[index % 2][SELECTOR_DOT]; - paint.setColor(color); - paint.setAlpha(getMultipliedAlpha(color, alpha)); - canvas.drawCircle(pointX, pointY, mSelectorDotRadius, paint); + final Paint dotPaint = mPaintSelector[index % 2][SELECTOR_DOT]; + dotPaint.setColor(mSelectorDotColor); + canvas.drawCircle(selCenterX, selCenterY, mSelectorDotRadius, dotPaint); } // Shorten the line to only go from the edge of the center dot to the // edge of the selection circle. - final double sin = Math.sin(selectionRadians); - final double cos = Math.cos(selectionRadians); - final int lineLength = mLineLength[index] - mSelectorRadius; + final double sin = Math.sin(selAngleRad); + final double cos = Math.cos(selAngleRad); + final int lineLength = selLength - selRadius; final int centerX = mXCenter + (int) (mCenterDotRadius * sin); final int centerY = mYCenter - (int) (mCenterDotRadius * cos); - pointX = centerX + (int) (lineLength * sin); - pointY = centerY - (int) (lineLength * cos); - - // Draw the line - color = mColorSelector[index % 2][SELECTOR_LINE]; - alpha = (int) (mAlphaSelector[index % 2][SELECTOR_LINE].getValue() * alphaMod + 0.5f); - paint = mPaintSelector[index % 2][SELECTOR_LINE]; - paint.setColor(color); - paint.setStrokeWidth(mSelectorStroke); - paint.setAlpha(getMultipliedAlpha(color, alpha)); - canvas.drawLine(mXCenter, mYCenter, pointX, pointY, paint); + final float linePointX = centerX + (int) (lineLength * sin); + final float linePointY = centerY - (int) (lineLength * cos); + + // Draw the line. + final Paint linePaint = mPaintSelector[index % 2][SELECTOR_LINE]; + linePaint.setColor(color); + linePaint.setStrokeWidth(mSelectorStroke); + canvas.drawLine(mXCenter, mYCenter, linePointX, linePointY, linePaint); } private void calculatePositionsHours() { @@ -890,21 +881,8 @@ public class RadialTimePickerView extends View { if (mHoursToMinutesAnims.size() == 0) { mHoursToMinutesAnims.add(getFadeOutAnimator(mAlpha[HOURS], ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); - mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_CIRCLE], - ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); - mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_DOT], - ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); - mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_LINE], - ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); - mHoursToMinutesAnims.add(getFadeInAnimator(mAlpha[MINUTES], ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener)); - mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_CIRCLE], - ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener)); - mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_DOT], - ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener)); - mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_LINE], - ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener)); } if (mTransition != null && mTransition.isRunning()) { @@ -919,21 +897,8 @@ public class RadialTimePickerView extends View { if (mMinuteToHoursAnims.size() == 0) { mMinuteToHoursAnims.add(getFadeOutAnimator(mAlpha[MINUTES], ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); - mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_CIRCLE], - ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); - mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_DOT], - ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); - mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_LINE], - ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); - mMinuteToHoursAnims.add(getFadeInAnimator(mAlpha[HOURS], ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener)); - mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_CIRCLE], - ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener)); - mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_DOT], - ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener)); - mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_LINE], - ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener)); } if (mTransition != null && mTransition.isRunning()) { @@ -945,56 +910,43 @@ public class RadialTimePickerView extends View { } private int getDegreesFromXY(float x, float y, boolean constrainOutside) { - final double hypotenuse = Math.sqrt( - (y - mYCenter) * (y - mYCenter) + (x - mXCenter) * (x - mXCenter)); + // Ensure the point is inside the touchable area. + final int innerBound; + final int outerBound; + if (mIs24HourMode && mShowHours) { + innerBound = mMinDistForInnerNumber; + outerBound = mMaxDistForOuterNumber; + } else { + final int index = mShowHours ? HOURS : MINUTES; + final int center = mCircleRadius - mTextInset[index]; + innerBound = center - mSelectorRadius; + outerBound = center + mSelectorRadius; + } - // Basic check if we're outside the range of the disk - if (constrainOutside && hypotenuse > mCircleRadius) { + final double dX = x - mXCenter; + final double dY = y - mYCenter; + final double distFromCenter = Math.sqrt(dX * dX + dY * dY); + if (distFromCenter < innerBound || constrainOutside && distFromCenter > outerBound) { return -1; } - // Check - if (mIs24HourMode && mShowHours) { - if (hypotenuse >= mMinHypotenuseForInnerNumber - && hypotenuse <= mHalfwayHypotenusePoint) { - mIsOnInnerCircle = true; - } else if ((hypotenuse <= mMaxHypotenuseForOuterNumber || !constrainOutside) - && hypotenuse >= mHalfwayHypotenusePoint) { - mIsOnInnerCircle = false; - } else { - return -1; - } + // Convert to degrees. + final int degrees = (int) (Math.toDegrees(Math.atan2(dY, dX) + Math.PI / 2) + 0.5); + if (degrees < 0) { + return degrees + 360; } else { - final int index = (mShowHours) ? HOURS : MINUTES; - final float length = (mCircleRadius - mTextInset[index]); - final int distanceToNumber = (int) (hypotenuse - length); - final int maxAllowedDistance = mTextInset[index]; - if (distanceToNumber < -maxAllowedDistance - || (constrainOutside && distanceToNumber > maxAllowedDistance)) { - return -1; - } + return degrees; } + } - final float opposite = Math.abs(y - mYCenter); - int degrees = (int) (Math.toDegrees(Math.asin(opposite / hypotenuse)) + 0.5); - - // Now we have to translate to the correct quadrant. - final boolean rightSide = (x > mXCenter); - final boolean topSide = (y < mYCenter); - if (rightSide) { - if (topSide) { - degrees = 90 - degrees; - } else { - degrees = 90 + degrees; - } - } else { - if (topSide) { - degrees = 270 + degrees; - } else { - degrees = 270 - degrees; - } + private boolean getInnerCircleFromXY(float x, float y) { + if (mIs24HourMode && mShowHours) { + final double dX = x - mXCenter; + final double dY = y - mYCenter; + final double distFromCenter = Math.sqrt(dX * dX + dY * dY); + return distFromCenter <= mHalfwayDist; } - return degrees; + return false; } boolean mChangedDuringTouch = false; @@ -1034,34 +986,28 @@ public class RadialTimePickerView extends View { private boolean handleTouchInput( float x, float y, boolean forceSelection, boolean autoAdvance) { - // Calling getDegreesFromXY has side effects, so cache - // whether we used to be on the inner circle. - final boolean wasOnInnerCircle = mIsOnInnerCircle; + final boolean isOnInnerCircle = getInnerCircleFromXY(x, y); final int degrees = getDegreesFromXY(x, y, false); if (degrees == -1) { return false; } - final int[] selectionDegrees = mSelectionDegrees; final int type; final int newValue; final boolean valueChanged; if (mShowHours) { final int snapDegrees = snapOnly30s(degrees, 0) % 360; - valueChanged = selectionDegrees[HOURS] != snapDegrees - || selectionDegrees[HOURS_INNER] != snapDegrees - || wasOnInnerCircle != mIsOnInnerCircle; - - selectionDegrees[HOURS] = snapDegrees; - selectionDegrees[HOURS_INNER] = snapDegrees; + valueChanged = mIsOnInnerCircle != isOnInnerCircle + || mSelectionDegrees[HOURS] != snapDegrees; + mIsOnInnerCircle = isOnInnerCircle; + mSelectionDegrees[HOURS] = snapDegrees; type = HOURS; newValue = getCurrentHour(); } else { final int snapDegrees = snapPrefer30s(degrees) % 360; - valueChanged = selectionDegrees[MINUTES] != snapDegrees; - - selectionDegrees[MINUTES] = snapDegrees; + valueChanged = mSelectionDegrees[MINUTES] != snapDegrees; + mSelectionDegrees[MINUTES] = snapDegrees; type = MINUTES; newValue = getCurrentMinute(); } @@ -1179,17 +1125,11 @@ public class RadialTimePickerView extends View { @Override protected int getVirtualViewAt(float x, float y) { final int id; - - // Calling getDegreesXY() has side-effects, so we need to cache the - // current inner circle value and restore after the call. - final boolean wasOnInnerCircle = mIsOnInnerCircle; final int degrees = getDegreesFromXY(x, y, true); - final boolean isOnInnerCircle = mIsOnInnerCircle; - mIsOnInnerCircle = wasOnInnerCircle; - if (degrees != -1) { final int snapDegrees = snapOnly30s(degrees, 0) % 360; if (mShowHours) { + final boolean isOnInnerCircle = getInnerCircleFromXY(x, y); final int hour24 = getHourForDegrees(snapDegrees, isOnInnerCircle); final int hour = mIs24HourMode ? hour24 : hour24To12(hour24); id = makeId(TYPE_HOUR, hour); @@ -1200,8 +1140,10 @@ public class RadialTimePickerView extends View { // If the touched minute is closer to the current minute // than it is to the snapped minute, return current. + final int currentOffset = getCircularDiff(current, touched, MINUTES_IN_CIRCLE); + final int snappedOffset = getCircularDiff(snapped, touched, MINUTES_IN_CIRCLE); final int minute; - if (Math.abs(current - touched) < Math.abs(snapped - touched)) { + if (currentOffset < snappedOffset) { minute = current; } else { minute = snapped; @@ -1215,6 +1157,20 @@ public class RadialTimePickerView extends View { return id; } + /** + * Returns the difference in degrees between two values along a circle. + * + * @param first value in the range [0,max] + * @param second value in the range [0,max] + * @param max the maximum value along the circle + * @return the difference in between the two values + */ + private int getCircularDiff(int first, int second, int max) { + final int diff = Math.abs(first - second); + final int midpoint = max / 2; + return (diff > midpoint) ? (max - diff) : diff; + } + @Override protected void getVisibleVirtualViews(IntArray virtualViewIds) { if (mShowHours) { @@ -1225,7 +1181,7 @@ public class RadialTimePickerView extends View { } } else { final int current = getCurrentMinute(); - for (int i = 0; i < 60; i += MINUTE_INCREMENT) { + for (int i = 0; i < MINUTES_IN_CIRCLE; i += MINUTE_INCREMENT) { virtualViewIds.add(makeId(TYPE_MINUTE, i)); // If the current minute falls between two increments, @@ -1283,7 +1239,7 @@ public class RadialTimePickerView extends View { if (value < current && nextValue > current) { // The current value is between two snap values. return makeId(type, current); - } else if (nextValue < 60) { + } else if (nextValue < MINUTES_IN_CIRCLE) { return makeId(type, nextValue); } } @@ -1337,7 +1293,7 @@ public class RadialTimePickerView extends View { final float centerRadius; final float degrees; if (type == TYPE_HOUR) { - final boolean innerCircle = mIs24HourMode && value > 0 && value <= 12; + final boolean innerCircle = getInnerCircleForHour(value); if (innerCircle) { centerRadius = mCircleRadius - mTextInset[HOURS_INNER]; radius = mSelectorRadius; diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java index 349f3f0..a50941b 100644 --- a/core/java/android/widget/RemoteViewsAdapter.java +++ b/core/java/android/widget/RemoteViewsAdapter.java @@ -815,12 +815,12 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback mContext = context; mIntent = intent; - mAppWidgetId = intent.getIntExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1); - - mLayoutInflater = LayoutInflater.from(context); if (mIntent == null) { throw new IllegalArgumentException("Non-null Intent must be specified."); } + + mAppWidgetId = intent.getIntExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1); + mLayoutInflater = LayoutInflater.from(context); mRequestedViews = new RemoteViewsFrameLayoutRefSet(); // Strip the previously injected app widget id from service intent diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java index 4e5a39a..d9f1f0e 100644 --- a/core/java/android/widget/SimpleMonthView.java +++ b/core/java/android/widget/SimpleMonthView.java @@ -26,6 +26,7 @@ import android.graphics.Paint.Align; import android.graphics.Paint.Style; import android.graphics.Rect; import android.graphics.Typeface; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.text.TextPaint; import android.text.format.DateFormat; @@ -59,6 +60,12 @@ class SimpleMonthView extends View { private static final String DEFAULT_TITLE_FORMAT = "MMMMy"; private static final String DAY_OF_WEEK_FORMAT = "EEEEE"; + /** Virtual view ID for previous button. */ + private static final int ITEM_ID_PREV = 0x101; + + /** Virtual view ID for next button. */ + private static final int ITEM_ID_NEXT = 0x100; + private final TextPaint mMonthPaint = new TextPaint(); private final TextPaint mDayOfWeekPaint = new TextPaint(); private final TextPaint mDayPaint = new TextPaint(); @@ -66,27 +73,43 @@ class SimpleMonthView extends View { private final Paint mDayHighlightPaint = new Paint(); private final Calendar mCalendar = Calendar.getInstance(); - private final Calendar mDayLabelCalendar = Calendar.getInstance(); + private final Calendar mDayOfWeekLabelCalendar = Calendar.getInstance(); private final MonthViewTouchHelper mTouchHelper; private final SimpleDateFormat mTitleFormatter; private final SimpleDateFormat mDayOfWeekFormatter; + // Desired dimensions. + private final int mDesiredMonthHeight; + private final int mDesiredDayOfWeekHeight; + private final int mDesiredDayHeight; + private final int mDesiredCellWidth; + private final int mDesiredDaySelectorRadius; + + // Next/previous drawables. + private final Drawable mPrevDrawable; + private final Drawable mNextDrawable; + private final Rect mPrevHitArea; + private final Rect mNextHitArea; + private final CharSequence mPrevContentDesc; + private final CharSequence mNextContentDesc; + private CharSequence mTitle; private int mMonth; private int mYear; + // Dimensions as laid out. + private int mMonthHeight; + private int mDayOfWeekHeight; + private int mDayHeight; + private int mCellWidth; + private int mDaySelectorRadius; + private int mPaddedWidth; private int mPaddedHeight; - private final int mMonthHeight; - private final int mDayOfWeekHeight; - private final int mDayHeight; - private final int mCellWidth; - private final int mDaySelectorRadius; - /** The day of month for the selected day, or -1 if no day is selected. */ private int mActivatedDay = -1; @@ -122,7 +145,10 @@ class SimpleMonthView extends View { private ColorStateList mDayTextColor; - private int mTouchedDay = -1; + private int mTouchedItem = -1; + + private boolean mPrevEnabled; + private boolean mNextEnabled; public SimpleMonthView(Context context) { this(context, null); @@ -140,11 +166,18 @@ class SimpleMonthView extends View { super(context, attrs, defStyleAttr, defStyleRes); final Resources res = context.getResources(); - mMonthHeight = res.getDimensionPixelSize(R.dimen.date_picker_month_height); - mDayOfWeekHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_of_week_height); - mDayHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_height); - mCellWidth = res.getDimensionPixelSize(R.dimen.date_picker_day_width); - mDaySelectorRadius = res.getDimensionPixelSize(R.dimen.date_picker_day_selector_radius); + mDesiredMonthHeight = res.getDimensionPixelSize(R.dimen.date_picker_month_height); + mDesiredDayOfWeekHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_of_week_height); + mDesiredDayHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_height); + mDesiredCellWidth = res.getDimensionPixelSize(R.dimen.date_picker_day_width); + mDesiredDaySelectorRadius = res.getDimensionPixelSize(R.dimen.date_picker_day_selector_radius); + + mPrevDrawable = context.getDrawable(R.drawable.ic_chevron_left); + mNextDrawable = context.getDrawable(R.drawable.ic_chevron_right); + mPrevHitArea = mPrevDrawable != null ? new Rect() : null; + mNextHitArea = mNextDrawable != null ? new Rect() : null; + mPrevContentDesc = res.getText(R.string.date_picker_prev_month_button); + mNextContentDesc = res.getText(R.string.date_picker_next_month_button); // Set up accessibility components. mTouchHelper = new MonthViewTouchHelper(this); @@ -160,6 +193,18 @@ class SimpleMonthView extends View { initPaints(res); } + public void setNextEnabled(boolean enabled) { + mNextEnabled = enabled; + mTouchHelper.invalidateRoot(); + invalidate(); + } + + public void setPrevEnabled(boolean enabled) { + mPrevEnabled = enabled; + mTouchHelper.invalidateRoot(); + invalidate(); + } + /** * Applies the specified text appearance resource to a paint, returning the * text color if one is set in the text appearance. @@ -192,7 +237,16 @@ class SimpleMonthView extends View { } public void setMonthTextAppearance(int resId) { - applyTextAppearance(mMonthPaint, resId); + final ColorStateList monthColor = applyTextAppearance(mMonthPaint, resId); + if (monthColor != null) { + if (mPrevDrawable != null) { + mPrevDrawable.setTintList(monthColor); + } + if (mNextDrawable != null) { + mNextDrawable.setTintList(monthColor); + } + } + invalidate(); } @@ -300,23 +354,26 @@ class SimpleMonthView extends View { @Override public boolean onTouchEvent(MotionEvent event) { + final int x = (int) (event.getX() + 0.5f); + final int y = (int) (event.getY() + 0.5f); + switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: - final int touchedDay = getDayAtLocation(event.getX(), event.getY()); - if (mTouchedDay != touchedDay) { - mTouchedDay = touchedDay; + final int touchedItem = getItemAtLocation(x, y); + if (mTouchedItem != touchedItem) { + mTouchedItem = touchedItem; invalidate(); } break; case MotionEvent.ACTION_UP: - final int clickedDay = getDayAtLocation(event.getX(), event.getY()); - onDayClicked(clickedDay); + final int clickedItem = getItemAtLocation(x, y); + onItemClicked(clickedItem, true); // Fall through. case MotionEvent.ACTION_CANCEL: // Reset touched day on stream end. - mTouchedDay = -1; + mTouchedItem = -1; invalidate(); break; } @@ -332,6 +389,7 @@ class SimpleMonthView extends View { drawMonth(canvas); drawDaysOfWeek(canvas); drawDays(canvas); + drawButtons(canvas); canvas.translate(-paddingLeft, -paddingTop); } @@ -347,34 +405,43 @@ class SimpleMonthView extends View { } private void drawDaysOfWeek(Canvas canvas) { - final float cellWidthHalf = mPaddedWidth / (DAYS_IN_WEEK * 2); - - // Vertically centered within the cell height. - final float lineHeight = mDayOfWeekPaint.ascent() + mDayOfWeekPaint.descent(); - final float y = mMonthHeight + (mDayOfWeekHeight - lineHeight) / 2f; - - for (int i = 0; i < DAYS_IN_WEEK; i++) { - final int calendarDay = (i + mWeekStart) % DAYS_IN_WEEK; - mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay); - - final String dayLabel = mDayOfWeekFormatter.format(mDayLabelCalendar.getTime()); - final float x = (2 * i + 1) * cellWidthHalf; - canvas.drawText(dayLabel, x, y, mDayOfWeekPaint); + final TextPaint p = mDayOfWeekPaint; + final int headerHeight = mMonthHeight; + final int rowHeight = mDayOfWeekHeight; + final int colWidth = mCellWidth; + + // Text is vertically centered within the day of week height. + final float halfLineHeight = (p.ascent() + p.descent()) / 2f; + final int rowCenter = headerHeight + rowHeight / 2; + + for (int col = 0; col < DAYS_IN_WEEK; col++) { + final int colCenter = colWidth * col + colWidth / 2; + final int dayOfWeek = (col + mWeekStart) % DAYS_IN_WEEK; + final String label = getDayOfWeekLabel(dayOfWeek); + canvas.drawText(label, colCenter, rowCenter - halfLineHeight, p); } } + private String getDayOfWeekLabel(int dayOfWeek) { + mDayOfWeekLabelCalendar.set(Calendar.DAY_OF_WEEK, dayOfWeek); + return mDayOfWeekFormatter.format(mDayOfWeekLabelCalendar.getTime()); + } + /** * Draws the month days. */ private void drawDays(Canvas canvas) { - final int cellWidthHalf = mPaddedWidth / (DAYS_IN_WEEK * 2); + final TextPaint p = mDayPaint; + final int headerHeight = mMonthHeight + mDayOfWeekHeight; + final int rowHeight = mDayHeight; + final int colWidth = mCellWidth; - // Vertically centered within the cell height. - final float halfLineHeight = (mDayPaint.ascent() + mDayPaint.descent()) / 2; - float centerY = mMonthHeight + mDayOfWeekHeight + mDayHeight / 2f; + // Text is vertically centered within the row height. + final float halfLineHeight = (p.ascent() + p.descent()) / 2f; + int rowCenter = headerHeight + rowHeight / 2; - for (int day = 1, j = findDayOffset(); day <= mDaysInMonth; day++) { - final int x = (2 * j + 1) * cellWidthHalf; + for (int day = 1, col = findDayOffset(); day <= mDaysInMonth; day++) { + final int colCenter = colWidth * col + colWidth / 2; int stateMask = 0; if (day >= mEnabledDayStart && day <= mEnabledDayEnd) { @@ -386,12 +453,12 @@ class SimpleMonthView extends View { stateMask |= StateSet.VIEW_STATE_ACTIVATED; // Adjust the circle to be centered on the row. - canvas.drawCircle(x, centerY, mDaySelectorRadius, mDaySelectorPaint); - } else if (mTouchedDay == day) { + canvas.drawCircle(colCenter, rowCenter, mDaySelectorRadius, mDaySelectorPaint); + } else if (mTouchedItem == day) { stateMask |= StateSet.VIEW_STATE_PRESSED; // Adjust the circle to be centered on the row. - canvas.drawCircle(x, centerY, mDaySelectorRadius, mDayHighlightPaint); + canvas.drawCircle(colCenter, rowCenter, mDaySelectorRadius, mDayHighlightPaint); } final boolean isDayToday = mToday == day; @@ -402,19 +469,29 @@ class SimpleMonthView extends View { final int[] stateSet = StateSet.get(stateMask); dayTextColor = mDayTextColor.getColorForState(stateSet, 0); } - mDayPaint.setColor(dayTextColor); + p.setColor(dayTextColor); - canvas.drawText("" + day, x, centerY - halfLineHeight, mDayPaint); + canvas.drawText(Integer.toString(day), colCenter, rowCenter - halfLineHeight, p); - j++; + col++; - if (j == DAYS_IN_WEEK) { - j = 0; - centerY += mDayHeight; + if (col == DAYS_IN_WEEK) { + col = 0; + rowCenter += rowHeight; } } } + private void drawButtons(Canvas canvas) { + if (mPrevEnabled && mPrevDrawable != null) { + mPrevDrawable.draw(canvas); + } + + if (mNextEnabled && mNextDrawable != null) { + mNextDrawable.draw(canvas); + } + } + private static boolean isValidDayOfWeek(int day) { return day >= Calendar.SUNDAY && day <= Calendar.SATURDAY; } @@ -558,9 +635,9 @@ class SimpleMonthView extends View { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final int preferredHeight = mDayHeight * mNumWeeks + mDayOfWeekHeight + mMonthHeight - + getPaddingTop() + getPaddingBottom(); - final int preferredWidth = mCellWidth * DAYS_IN_WEEK + final int preferredHeight = mDesiredDayHeight * mNumWeeks + mDesiredDayOfWeekHeight + + mDesiredMonthHeight + getPaddingTop() + getPaddingBottom(); + final int preferredWidth = mDesiredCellWidth * DAYS_IN_WEEK + getPaddingStart() + getPaddingEnd(); final int resolvedWidth = resolveSize(preferredWidth, widthMeasureSpec); final int resolvedHeight = resolveSize(preferredHeight, heightMeasureSpec); @@ -568,9 +645,73 @@ class SimpleMonthView extends View { } @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - mPaddedWidth = w - getPaddingLeft() - getPaddingRight(); - mPaddedHeight = w - getPaddingTop() - getPaddingBottom(); + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (!changed) { + return; + } + + // Let's initialize a completely reasonable number of variables. + final int w = right - left; + final int h = bottom - top; + final int paddingLeft = getPaddingLeft(); + final int paddingTop = getPaddingTop(); + final int paddingRight = getPaddingRight(); + final int paddingBottom = getPaddingBottom(); + final int paddedRight = w - paddingRight; + final int paddedBottom = h - paddingBottom; + final int paddedWidth = paddedRight - paddingLeft; + final int paddedHeight = paddedBottom - paddingTop; + if (paddedWidth == mPaddedWidth || paddedHeight == mPaddedHeight) { + return; + } + + mPaddedWidth = paddedWidth; + mPaddedHeight = paddedHeight; + + // We may have been laid out smaller than our preferred size. If so, + // scale all dimensions to fit. + final int measuredPaddedHeight = getMeasuredHeight() - paddingTop - paddingBottom; + final float scaleH = paddedHeight / (float) measuredPaddedHeight; + final int monthHeight = (int) (mDesiredMonthHeight * scaleH); + final int cellWidth = mPaddedWidth / DAYS_IN_WEEK; + mMonthHeight = monthHeight; + mDayOfWeekHeight = (int) (mDesiredDayOfWeekHeight * scaleH); + mDayHeight = (int) (mDesiredDayHeight * scaleH); + mCellWidth = cellWidth; + + // Compute the largest day selector radius that's still within the clip + // bounds and desired selector radius. + final int maxSelectorWidth = cellWidth / 2 + Math.min(paddingLeft, paddingRight); + final int maxSelectorHeight = mDayHeight / 2 + paddingBottom; + mDaySelectorRadius = Math.min(mDesiredDaySelectorRadius, + Math.min(maxSelectorWidth, maxSelectorHeight)); + + // Vertically center the previous/next drawables within the month + // header, horizontally center within the day cell, then expand the + // hit area to ensure it's at least 48x48dp. + final Drawable prevDrawable = mPrevDrawable; + if (prevDrawable != null) { + final int dW = prevDrawable.getIntrinsicWidth(); + final int dH = prevDrawable.getIntrinsicHeight(); + final int iconTop = (monthHeight - dH) / 2; + final int iconLeft = (cellWidth - dW) / 2; + + // Button bounds don't include padding, but hit area does. + prevDrawable.setBounds(iconLeft, iconTop, iconLeft + dW, iconTop + dH); + mPrevHitArea.set(0, 0, paddingLeft + cellWidth, paddingTop + monthHeight); + } + + final Drawable nextDrawable = mNextDrawable; + if (nextDrawable != null) { + final int dW = nextDrawable.getIntrinsicWidth(); + final int dH = nextDrawable.getIntrinsicHeight(); + final int iconTop = (monthHeight - dH) / 2; + final int iconRight = paddedWidth - (cellWidth - dW) / 2; + + // Button bounds don't include padding, but hit area does. + nextDrawable.setBounds(iconRight - dW, iconTop, iconRight, iconTop + dH); + mNextHitArea.set(paddedRight - cellWidth, 0, w, paddingTop + monthHeight); + } // Invalidate cached accessibility information. mTouchHelper.invalidateRoot(); @@ -585,22 +726,29 @@ class SimpleMonthView extends View { } /** - * Calculates the day of the month at the specified touch position. Returns - * the day of the month or -1 if the position wasn't in a valid day. + * Calculates the day of the month or item identifier at the specified + * touch position. Returns the day of the month or -1 if the position + * wasn't in a valid day. * * @param x the x position of the touch event * @param y the y position of the touch event - * @return the day of the month at (x, y) or -1 if the position wasn't in a - * valid day + * @return the day of the month at (x, y), an item identifier, or -1 if the + * position wasn't in a valid day or item */ - private int getDayAtLocation(float x, float y) { - final int paddedX = (int) (x - getPaddingLeft() + 0.5f); + private int getItemAtLocation(int x, int y) { + if (mNextEnabled && mNextDrawable != null && mNextHitArea.contains(x, y)) { + return ITEM_ID_NEXT; + } else if (mPrevEnabled && mPrevDrawable != null && mPrevHitArea.contains(x, y)) { + return ITEM_ID_PREV; + } + + final int paddedX = x - getPaddingLeft(); if (paddedX < 0 || paddedX >= mPaddedWidth) { return -1; } final int headerHeight = mMonthHeight + mDayOfWeekHeight; - final int paddedY = (int) (y - getPaddingTop() + 0.5f); + final int paddedY = y - getPaddingTop(); if (paddedY < headerHeight || paddedY >= mPaddedHeight) { return -1; } @@ -619,47 +767,97 @@ class SimpleMonthView extends View { /** * Calculates the bounds of the specified day. * - * @param day the day of the month + * @param id the day of the month, or an item identifier * @param outBounds the rect to populate with bounds */ - private boolean getBoundsForDay(int day, Rect outBounds) { - if (day < 1 || day > mDaysInMonth) { + private boolean getBoundsForItem(int id, Rect outBounds) { + if (mNextEnabled && id == ITEM_ID_NEXT) { + if (mNextDrawable != null) { + outBounds.set(mNextHitArea); + return true; + } + } else if (mPrevEnabled && id == ITEM_ID_PREV) { + if (mPrevDrawable != null) { + outBounds.set(mPrevHitArea); + return true; + } + } + + if (id < 1 || id > mDaysInMonth) { return false; } - final int index = day - 1 + findDayOffset(); - final int row = index / DAYS_IN_WEEK; + final int index = id - 1 + findDayOffset(); + + // Compute left edge. final int col = index % DAYS_IN_WEEK; + final int colWidth = mCellWidth; + final int left = getPaddingLeft() + col * colWidth; + // Compute top edge. + final int row = index / DAYS_IN_WEEK; + final int rowHeight = mDayHeight; final int headerHeight = mMonthHeight + mDayOfWeekHeight; - final int paddedY = row * mDayHeight + headerHeight; - final int paddedX = col * mPaddedWidth; - - final int y = paddedY + getPaddingTop(); - final int x = paddedX + getPaddingLeft(); - - final int cellHeight = mDayHeight; - final int cellWidth = mPaddedWidth / DAYS_IN_WEEK; - outBounds.set(x, y, (x + cellWidth), (y + cellHeight)); + final int top = getPaddingTop() + headerHeight + row * rowHeight; + outBounds.set(left, top, left + colWidth, top + rowHeight); return true; } /** + * Called when an item is clicked. + * + * @param id the day number or item identifier + */ + private boolean onItemClicked(int id, boolean animate) { + return onNavigationClicked(id, animate) || onDayClicked(id); + } + + /** * Called when the user clicks on a day. Handles callbacks to the * {@link OnDayClickListener} if one is set. * * @param day the day that was clicked */ - private void onDayClicked(int day) { + private boolean onDayClicked(int day) { + if (day < 0 || day > mDaysInMonth) { + return false; + } + if (mOnDayClickListener != null) { - Calendar date = Calendar.getInstance(); + final Calendar date = Calendar.getInstance(); date.set(mYear, mMonth, day); mOnDayClickListener.onDayClick(this, date); } // This is a no-op if accessibility is turned off. mTouchHelper.sendEventForVirtualView(day, AccessibilityEvent.TYPE_VIEW_CLICKED); + return true; + } + + /** + * Called when the user clicks on a navigation button. Handles callbacks to + * the {@link OnDayClickListener} if one is set. + * + * @param id the item identifier + */ + private boolean onNavigationClicked(int id, boolean animate) { + final int direction; + if (id == ITEM_ID_NEXT) { + direction = 1; + } else if (id == ITEM_ID_PREV) { + direction = -1; + } else { + return false; + } + + if (mOnDayClickListener != null) { + mOnDayClickListener.onNavigationClick(this, direction, animate); + } + + // This is a no-op if accessibility is turned off. + mTouchHelper.sendEventForVirtualView(id, AccessibilityEvent.TYPE_VIEW_CLICKED); + return true; } /** @@ -678,7 +876,7 @@ class SimpleMonthView extends View { @Override protected int getVirtualViewAt(float x, float y) { - final int day = getDayAtLocation(x, y); + final int day = getItemAtLocation((int) (x + 0.5f), (int) (y + 0.5f)); if (day >= 0) { return day; } @@ -687,6 +885,14 @@ class SimpleMonthView extends View { @Override protected void getVisibleVirtualViews(IntArray virtualViewIds) { + if (mNextEnabled && mNextDrawable != null) { + virtualViewIds.add(ITEM_ID_PREV); + } + + if (mPrevEnabled && mPrevDrawable != null) { + virtualViewIds.add(ITEM_ID_NEXT); + } + for (int day = 1; day <= mDaysInMonth; day++) { virtualViewIds.add(day); } @@ -699,7 +905,7 @@ class SimpleMonthView extends View { @Override protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) { - final boolean hasBounds = getBoundsForDay(virtualViewId, mTempRect); + final boolean hasBounds = getBoundsForItem(virtualViewId, mTempRect); if (!hasBounds) { // The day is invalid, kill the node. @@ -710,12 +916,14 @@ class SimpleMonthView extends View { return; } + node.setText(getItemText(virtualViewId)); node.setContentDescription(getItemDescription(virtualViewId)); node.setBoundsInParent(mTempRect); node.addAction(AccessibilityAction.ACTION_CLICK); if (virtualViewId == mActivatedDay) { - node.setSelected(true); + // TODO: This should use activated once that's supported. + node.setChecked(true); } } @@ -725,31 +933,45 @@ class SimpleMonthView extends View { Bundle arguments) { switch (action) { case AccessibilityNodeInfo.ACTION_CLICK: - onDayClicked(virtualViewId); - return true; + return onItemClicked(virtualViewId, false); } return false; } /** - * Generates a description for a given time object. Since this - * description will be spoken, the components are ordered by descending - * specificity as DAY MONTH YEAR. + * Generates a description for a given virtual view. * - * @param day The day to generate a description for - * @return A description of the time object + * @param id the day or item identifier to generate a description for + * @return a description of the virtual view */ - private CharSequence getItemDescription(int day) { - mTempCalendar.set(mYear, mMonth, day); - final CharSequence date = DateFormat.format(DATE_FORMAT, - mTempCalendar.getTimeInMillis()); + private CharSequence getItemDescription(int id) { + if (id == ITEM_ID_NEXT) { + return mNextContentDesc; + } else if (id == ITEM_ID_PREV) { + return mPrevContentDesc; + } else if (id >= 1 && id <= mDaysInMonth) { + mTempCalendar.set(mYear, mMonth, id); + return DateFormat.format(DATE_FORMAT, mTempCalendar.getTimeInMillis()); + } - if (day == mActivatedDay) { - return getContext().getString(R.string.item_is_selected, date); + return ""; + } + + /** + * Generates displayed text for a given virtual view. + * + * @param id the day or item identifier to generate text for + * @return the visible text of the virtual view + */ + private CharSequence getItemText(int id) { + if (id == ITEM_ID_NEXT || id == ITEM_ID_PREV) { + return null; + } else if (id >= 1 && id <= mDaysInMonth) { + return Integer.toString(id); } - return date; + return null; } } @@ -758,5 +980,6 @@ class SimpleMonthView extends View { */ public interface OnDayClickListener { public void onDayClick(SimpleMonthView view, Calendar day); + public void onNavigationClick(SimpleMonthView view, int direction, boolean animate); } } diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java index bb290e7..ae779fe 100644 --- a/core/java/android/widget/Switch.java +++ b/core/java/android/widget/Switch.java @@ -1363,8 +1363,8 @@ public class Switch extends CompoundButton { } @Override - public void onProvideAssistStructure(ViewAssistStructure structure, Bundle extras) { - super.onProvideAssistStructure(structure, extras); + public void onProvideAssistStructure(ViewAssistStructure structure) { + super.onProvideAssistStructure(structure); CharSequence switchText = isChecked() ? mTextOn : mTextOff; if (!TextUtils.isEmpty(switchText)) { CharSequence oldText = structure.getText(); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 718ef93..9caa584 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -543,6 +543,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private float mSpacingMult = 1.0f; private float mSpacingAdd = 0.0f; + private int mBreakStrategy; + private int mMaximum = Integer.MAX_VALUE; private int mMaxMode = LINES; private int mMinimum = 0; @@ -680,6 +682,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener boolean elegant = false; float letterSpacing = 0; String fontFeatureSettings = null; + mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE; final Resources.Theme theme = context.getTheme(); @@ -1133,6 +1136,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case com.android.internal.R.styleable.TextView_fontFeatureSettings: fontFeatureSettings = a.getString(attr); break; + + case com.android.internal.R.styleable.TextView_breakStrategy: + mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE); } } a.recycle(); @@ -2960,6 +2966,35 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * Sets the break strategy for breaking paragraphs into lines. The default value for + * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for + * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the + * text "dancing" when being edited. + * + * @attr ref android.R.styleable#TextView_breakStrategy + * @see #getBreakStrategy() + */ + public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) { + mBreakStrategy = breakStrategy; + if (mLayout != null) { + nullLayouts(); + requestLayout(); + invalidate(); + } + } + + /** + * @return the currently set break strategy. + * + * @attr ref android.R.styleable#TextView_breakStrategy + * @see #setBreakStrategy(int) + */ + @Layout.BreakStrategy + public int getBreakStrategy() { + return mBreakStrategy; + } + + /** * Sets font feature settings. The format is the same as the CSS * font-feature-settings attribute: * http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings @@ -6492,27 +6527,25 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener hintBoring, mIncludePad, mEllipsize, ellipsisWidth); } - } else if (shouldEllipsize) { - mHintLayout = new StaticLayout(mHint, - 0, mHint.length(), - mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult, - mSpacingAdd, mIncludePad, mEllipsize, - ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); - } else { - mHintLayout = new StaticLayout(mHint, mTextPaint, - hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, - mIncludePad); } - } else if (shouldEllipsize) { - mHintLayout = new StaticLayout(mHint, - 0, mHint.length(), - mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult, - mSpacingAdd, mIncludePad, mEllipsize, - ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); - } else { - mHintLayout = new StaticLayout(mHint, mTextPaint, - hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, - mIncludePad); + } + // TODO: code duplication with makeSingleLayout() + if (mHintLayout == null) { + StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0, + mHint.length(), hintWidth) + .setPaint(mTextPaint) + .setAlignment(alignment) + .setTextDir(mTextDir) + .setSpacingMult(mSpacingMult) + .setSpacingAdd(mSpacingAdd) + .setIncludePad(mIncludePad) + .setBreakStrategy(mBreakStrategy); + if (shouldEllipsize) { + builder.setEllipsize(mEllipsize) + .setEllipsizedWidth(ellipsisWidth) + .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); + } + mHintLayout = builder.build(); } } @@ -6544,9 +6577,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener Layout result = null; if (mText instanceof Spannable) { result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth, - alignment, mTextDir, mSpacingMult, - mSpacingAdd, mIncludePad, getKeyListener() == null ? effectiveEllipsize : null, - ellipsisWidth); + alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad, mBreakStrategy, + getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth); } else { if (boring == UNKNOWN_BORING) { boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); @@ -6583,29 +6615,27 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener boring, mIncludePad, effectiveEllipsize, ellipsisWidth); } - } else if (shouldEllipsize) { - result = new StaticLayout(mTransformed, - 0, mTransformed.length(), - mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult, - mSpacingAdd, mIncludePad, effectiveEllipsize, - ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); - } else { - result = new StaticLayout(mTransformed, mTextPaint, - wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, - mIncludePad); } - } else if (shouldEllipsize) { - result = new StaticLayout(mTransformed, - 0, mTransformed.length(), - mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult, - mSpacingAdd, mIncludePad, effectiveEllipsize, - ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); - } else { - result = new StaticLayout(mTransformed, mTextPaint, - wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, - mIncludePad); } } + if (result == null) { + StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed, + 0, mTransformed.length(), wantWidth) + .setPaint(mTextPaint) + .setAlignment(alignment) + .setTextDir(mTextDir) + .setSpacingMult(mSpacingMult) + .setSpacingAdd(mSpacingAdd) + .setIncludePad(mIncludePad) + .setBreakStrategy(mBreakStrategy); + if (shouldEllipsize) { + builder.setEllipsize(effectiveEllipsize) + .setEllipsizedWidth(ellipsisWidth) + .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); + } + // TODO: explore always setting maxLines + result = builder.build(); + } return result; } @@ -8576,8 +8606,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } @Override - public void onProvideAssistStructure(ViewAssistStructure structure, Bundle extras) { - super.onProvideAssistStructure(structure, extras); + public void onProvideAssistStructure(ViewAssistStructure structure) { + super.onProvideAssistStructure(structure); final boolean isPassword = hasPasswordTransformationMethod(); if (!isPassword) { structure.setText(getText(), getSelectionStart(), getSelectionEnd()); diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java index 944b491..986c0f8 100644 --- a/core/java/android/widget/TimePicker.java +++ b/core/java/android/widget/TimePicker.java @@ -16,6 +16,7 @@ package android.widget; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Widget; import android.content.Context; @@ -29,18 +30,13 @@ import com.android.internal.R; import java.util.Locale; /** - * A view for selecting the time of day, in either 24 hour or AM/PM mode. The - * hour, each minute digit, and AM/PM (if applicable) can be conrolled by - * vertical spinners. The hour can be entered by keyboard input. Entering in two - * digit hours can be accomplished by hitting two digits within a timeout of - * about a second (e.g. '1' then '2' to select 12). The minutes can be entered - * by entering single digits. Under AM/PM mode, the user can hit 'a', 'A", 'p' - * or 'P' to pick. For a dialog using this view, see - * {@link android.app.TimePickerDialog}. + * A widget for selecting the time of day, in either 24-hour or AM/PM mode. * <p> - * See the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a> - * guide. - * </p> + * For a dialog using this view, see {@link android.app.TimePickerDialog}. See + * the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a> + * guide for more information. + * + * @attr ref android.R.styleable#TimePicker_timePickerMode */ @Widget public class TimePicker extends FrameLayout { @@ -96,44 +92,105 @@ public class TimePicker extends FrameLayout { } /** - * Set the current hour. + * Sets the currently selected hour using 24-hour time. + * + * @param hour the hour to set, in the range (0-23) + * @see #getHour() + */ + public void setHour(int hour) { + mDelegate.setCurrentHour(hour); + } + + /** + * Returns the currently selected hour using 24-hour time. + * + * @return the currently selected hour, in the range (0-23) + * @see #setHour(int) + */ + public int getHour() { + return mDelegate.getCurrentHour(); + } + + /** + * Sets the currently selected minute.. + * + * @param minute the minute to set, in the range (0-59) + * @see #getMinute() */ - public void setCurrentHour(Integer currentHour) { - mDelegate.setCurrentHour(currentHour); + public void setMinute(int minute) { + mDelegate.setCurrentMinute(minute); } /** - * @return The current hour in the range (0-23). + * Returns the currently selected minute. + * + * @return the currently selected minute, in the range (0-59) + * @see #setMinute(int) */ + public int getMinute() { + return mDelegate.getCurrentMinute(); + } + + /** + * Sets the current hour. + * + * @deprecated Use {@link #setHour(int)} + */ + @Deprecated + public void setCurrentHour(@NonNull Integer currentHour) { + setHour(currentHour); + } + + /** + * @return the current hour in the range (0-23) + * @deprecated Use {@link #getHour()} + */ + @NonNull + @Deprecated public Integer getCurrentHour() { return mDelegate.getCurrentHour(); } /** * Set the current minute (0-59). + * + * @deprecated Use {@link #setMinute(int)} */ - public void setCurrentMinute(Integer currentMinute) { + @Deprecated + public void setCurrentMinute(@NonNull Integer currentMinute) { mDelegate.setCurrentMinute(currentMinute); } /** - * @return The current minute. + * @return the current minute + * @deprecated Use {@link #getMinute()} */ + @NonNull + @Deprecated public Integer getCurrentMinute() { return mDelegate.getCurrentMinute(); } /** - * Set whether in 24 hour or AM/PM mode. + * Sets whether this widget displays time in 24-hour mode or 12-hour mode + * with an AM/PM picker. * - * @param is24HourView True = 24 hour mode. False = AM/PM. + * @param is24HourView {@code true} to display in 24-hour mode, + * {@code false} for 12-hour mode with AM/PM + * @see #is24HourView() */ - public void setIs24HourView(Boolean is24HourView) { + public void setIs24HourView(@NonNull Boolean is24HourView) { + if (is24HourView == null) { + return; + } + mDelegate.setIs24HourView(is24HourView); } /** - * @return true if this is in 24 hour view else false. + * @return {@code true} if this widget displays time in 24-hour mode, + * {@code false} otherwise} + * @see #setIs24HourView(Boolean) */ public boolean is24HourView() { return mDelegate.is24HourView(); @@ -210,13 +267,13 @@ public class TimePicker extends FrameLayout { * for the real behavior. */ interface TimePickerDelegate { - void setCurrentHour(Integer currentHour); - Integer getCurrentHour(); + void setCurrentHour(int currentHour); + int getCurrentHour(); - void setCurrentMinute(Integer currentMinute); - Integer getCurrentMinute(); + void setCurrentMinute(int currentMinute); + int getCurrentMinute(); - void setIs24HourView(Boolean is24HourView); + void setIs24HourView(boolean is24HourView); boolean is24HourView(); void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener); diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java index 9fdd718..2365b48 100644 --- a/core/java/android/widget/TimePickerClockDelegate.java +++ b/core/java/android/widget/TimePickerClockDelegate.java @@ -16,16 +16,22 @@ package android.widget; +import android.annotation.Nullable; import android.content.Context; +import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; +import android.text.SpannableStringBuilder; import android.text.format.DateFormat; import android.text.format.DateUtils; +import android.text.style.TtsSpan; import android.util.AttributeSet; import android.util.Log; +import android.util.StateSet; import android.util.TypedValue; import android.view.HapticFeedbackConstants; import android.view.KeyCharacterMap; @@ -48,7 +54,6 @@ import java.util.Locale; */ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate implements RadialTimePickerView.OnValueSelectedListener { - private static final String TAG = "TimePickerClockDelegate"; // Index used by RadialPickerLayout @@ -61,14 +66,16 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl // Also NOT a real index, just used for keyboard mode. private static final int ENABLE_PICKER_INDEX = 3; + private static final int[] ATTRS_TEXT_COLOR = new int[] { + com.android.internal.R.attr.textColor}; + private static final int[] ATTRS_DISABLED_ALPHA = new int[] { + com.android.internal.R.attr.disabledAlpha}; + // LayoutLib relies on these constants. Change TimePickerClockDelegate_Delegate if // modifying these. static final int AM = 0; static final int PM = 1; - private static final boolean DEFAULT_ENABLED_STATE = true; - private boolean mIsEnabled = DEFAULT_ENABLED_STATE; - private static final int HOURS_IN_HALF_DAY = 12; private final View mHeaderView; @@ -83,8 +90,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl private final String mAmText; private final String mPmText; - private final float mDisabledAlpha; - + private boolean mIsEnabled = true; private boolean mAllowAutoAdvance; private int mInitialHourOfDay; private int mInitialMinute; @@ -134,7 +140,6 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl final View mainView = inflater.inflate(layoutResourceId, delegator); mHeaderView = mainView.findViewById(R.id.time_header); - mHeaderView.setBackground(a.getDrawable(R.styleable.TimePicker_headerBackground)); // Set up hour/minute labels. mHourView = (TextView) mainView.findViewById(R.id.hours); @@ -147,41 +152,57 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl mMinuteView.setAccessibilityDelegate( new ClickActionDelegate(context, R.string.select_minutes)); - final int headerTimeTextAppearance = a.getResourceId( - R.styleable.TimePicker_headerTimeTextAppearance, 0); - if (headerTimeTextAppearance != 0) { - mHourView.setTextAppearance(context, headerTimeTextAppearance); - mSeparatorView.setTextAppearance(context, headerTimeTextAppearance); - mMinuteView.setTextAppearance(context, headerTimeTextAppearance); - } - // Now that we have text appearances out of the way, make sure the hour // and minute views are correctly sized. mHourView.setMinWidth(computeStableWidth(mHourView, 24)); mMinuteView.setMinWidth(computeStableWidth(mMinuteView, 60)); + final SpannableStringBuilder amLabel = new SpannableStringBuilder() + .append(amPmStrings[0], new TtsSpan.VerbatimBuilder(amPmStrings[0]).build(), 0); + // Set up AM/PM labels. mAmPmLayout = mainView.findViewById(R.id.ampm_layout); mAmLabel = (CheckedTextView) mAmPmLayout.findViewById(R.id.am_label); - mAmLabel.setText(amPmStrings[0]); + mAmLabel.setText(obtainVerbatim(amPmStrings[0])); mAmLabel.setOnClickListener(mClickListener); mPmLabel = (CheckedTextView) mAmPmLayout.findViewById(R.id.pm_label); - mPmLabel.setText(amPmStrings[1]); + mPmLabel.setText(obtainVerbatim(amPmStrings[1])); mPmLabel.setOnClickListener(mClickListener); - final int headerAmPmTextAppearance = a.getResourceId( - R.styleable.TimePicker_headerAmPmTextAppearance, 0); - if (headerAmPmTextAppearance != 0) { - mAmLabel.setTextAppearance(context, headerAmPmTextAppearance); - mPmLabel.setTextAppearance(context, headerAmPmTextAppearance); + // For the sake of backwards compatibility, attempt to extract the text + // color from the header time text appearance. If it's set, we'll let + // that override the "real" header text color. + ColorStateList headerTextColor = null; + + @SuppressWarnings("deprecation") + final int timeHeaderTextAppearance = a.getResourceId( + R.styleable.TimePicker_headerTimeTextAppearance, 0); + if (timeHeaderTextAppearance != 0) { + final TypedArray textAppearance = mContext.obtainStyledAttributes(null, + ATTRS_TEXT_COLOR, 0, timeHeaderTextAppearance); + final ColorStateList legacyHeaderTextColor = textAppearance.getColorStateList(0); + headerTextColor = applyLegacyColorFixes(legacyHeaderTextColor); + textAppearance.recycle(); } - a.recycle(); + if (headerTextColor == null) { + headerTextColor = a.getColorStateList(R.styleable.TimePicker_headerTextColor); + } - // Pull disabled alpha from theme. - final TypedValue outValue = new TypedValue(); - context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true); - mDisabledAlpha = outValue.getFloat(); + if (headerTextColor != null) { + mHourView.setTextColor(headerTextColor); + mSeparatorView.setTextColor(headerTextColor); + mMinuteView.setTextColor(headerTextColor); + mAmLabel.setTextColor(headerTextColor); + mPmLabel.setTextColor(headerTextColor); + } + + // Set up header background, if available. + if (a.hasValueOrEmpty(R.styleable.TimePicker_headerBackground)) { + mHeaderView.setBackground(a.getDrawable(R.styleable.TimePicker_headerBackground)); + } + + a.recycle(); mRadialTimePickerView = (RadialTimePickerView) mainView.findViewById( R.id.radial_picker); @@ -204,6 +225,59 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl initialize(currentHour, currentMinute, false /* 12h */, HOUR_INDEX); } + private static final CharSequence obtainVerbatim(String text) { + return new SpannableStringBuilder().append(text, + new TtsSpan.VerbatimBuilder(text).build(), 0); + } + + /** + * The legacy text color might have been poorly defined. Ensures that it + * has an appropriate activated state, using the selected state if one + * exists or modifying the default text color otherwise. + * + * @param color a legacy text color, or {@code null} + * @return a color state list with an appropriate activated state, or + * {@code null} if a valid activated state could not be generated + */ + @Nullable + private ColorStateList applyLegacyColorFixes(@Nullable ColorStateList color) { + if (color == null || color.hasState(R.attr.state_activated)) { + return color; + } + + final int activatedColor; + final int defaultColor; + if (color.hasState(R.attr.state_selected)) { + activatedColor = color.getColorForState(StateSet.get( + StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_SELECTED), 0); + defaultColor = color.getColorForState(StateSet.get( + StateSet.VIEW_STATE_ENABLED), 0); + } else { + activatedColor = color.getDefaultColor(); + + // Generate a non-activated color using the disabled alpha. + final TypedArray ta = mContext.obtainStyledAttributes(ATTRS_DISABLED_ALPHA); + final float disabledAlpha = ta.getFloat(0, 0.30f); + defaultColor = multiplyAlphaComponent(activatedColor, disabledAlpha); + } + + if (activatedColor == 0 || defaultColor == 0) { + // We somehow failed to obtain the colors. + return null; + } + + final int[][] stateSet = new int[][] {{ R.attr.state_activated }, {}}; + final int[] colors = new int[] { activatedColor, defaultColor }; + return new ColorStateList(stateSet, colors); + } + + private int multiplyAlphaComponent(int color, float alphaMod) { + final int srcRgb = color & 0xFFFFFF; + final int srcAlpha = (color >> 24) & 0xFF; + final int dstAlpha = (int) (srcAlpha * alphaMod + 0.5f); + return srcRgb | (dstAlpha << 24); + } + private static class ClickActionDelegate extends AccessibilityDelegate { private final AccessibilityAction mClickAction; @@ -312,7 +386,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl * Set the current hour. */ @Override - public void setCurrentHour(Integer currentHour) { + public void setCurrentHour(int currentHour) { if (mInitialHourOfDay == currentHour) { return; } @@ -329,7 +403,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl * @return The current hour in the range (0-23). */ @Override - public Integer getCurrentHour() { + public int getCurrentHour() { int currentHour = mRadialTimePickerView.getCurrentHour(); if (mIs24HourView) { return currentHour; @@ -348,7 +422,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl * Set the current minute (0-59). */ @Override - public void setCurrentMinute(Integer currentMinute) { + public void setCurrentMinute(int currentMinute) { if (mInitialMinute == currentMinute) { return; } @@ -363,7 +437,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl * @return The current minute. */ @Override - public Integer getCurrentMinute() { + public int getCurrentMinute() { return mRadialTimePickerView.getCurrentMinute(); } @@ -373,7 +447,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl * @param is24HourView True = 24 hour mode. False = AM/PM. */ @Override - public void setIs24HourView(Boolean is24HourView) { + public void setIs24HourView(boolean is24HourView) { if (is24HourView == mIs24HourView) { return; } @@ -604,12 +678,12 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl private void updateAmPmLabelStates(int amOrPm) { final boolean isAm = amOrPm == AM; + mAmLabel.setActivated(isAm); mAmLabel.setChecked(isAm); - mAmLabel.setSelected(isAm); final boolean isPm = amOrPm == PM; + mPmLabel.setActivated(isPm); mPmLabel.setChecked(isPm); - mPmLabel.setSelected(isPm); } /** @@ -769,8 +843,8 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl } } - mHourView.setSelected(index == HOUR_INDEX); - mMinuteView.setSelected(index == MINUTE_INDEX); + mHourView.setActivated(index == HOUR_INDEX); + mMinuteView.setActivated(index == MINUTE_INDEX); } private void setAmOrPm(int amOrPm) { @@ -960,9 +1034,9 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl String minuteStr = (values[1] == -1) ? mDoublePlaceholderText : String.format(minuteFormat, values[1]).replace(' ', mPlaceholderText); mHourView.setText(hourStr); - mHourView.setSelected(false); + mHourView.setActivated(false); mMinuteView.setText(minuteStr); - mMinuteView.setSelected(false); + mMinuteView.setActivated(false); if (!mIs24HourView) { updateAmPmLabelStates(values[2]); } diff --git a/core/java/android/widget/TimePickerSpinnerDelegate.java b/core/java/android/widget/TimePickerSpinnerDelegate.java index 513c55b..df6b0a9 100644 --- a/core/java/android/widget/TimePickerSpinnerDelegate.java +++ b/core/java/android/widget/TimePickerSpinnerDelegate.java @@ -279,13 +279,13 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate { } @Override - public void setCurrentHour(Integer currentHour) { + public void setCurrentHour(int currentHour) { setCurrentHour(currentHour, true); } - private void setCurrentHour(Integer currentHour, boolean notifyTimeChanged) { + private void setCurrentHour(int currentHour, boolean notifyTimeChanged) { // why was Integer used in the first place? - if (currentHour == null || currentHour == getCurrentHour()) { + if (currentHour == getCurrentHour()) { return; } if (!is24HourView()) { @@ -310,7 +310,7 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate { } @Override - public Integer getCurrentHour() { + public int getCurrentHour() { int currentHour = mHourSpinner.getValue(); if (is24HourView()) { return currentHour; @@ -322,7 +322,7 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate { } @Override - public void setCurrentMinute(Integer currentMinute) { + public void setCurrentMinute(int currentMinute) { if (currentMinute == getCurrentMinute()) { return; } @@ -331,12 +331,12 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate { } @Override - public Integer getCurrentMinute() { + public int getCurrentMinute() { return mMinuteSpinner.getValue(); } @Override - public void setIs24HourView(Boolean is24HourView) { + public void setIs24HourView(boolean is24HourView) { if (mIs24HourView == is24HourView) { return; } diff --git a/core/java/android/widget/YearPickerView.java b/core/java/android/widget/YearPickerView.java index 7bd502e..7182414 100644 --- a/core/java/android/widget/YearPickerView.java +++ b/core/java/android/widget/YearPickerView.java @@ -111,16 +111,12 @@ class YearPickerView extends ListView { mAdapter.setRange(min, max); } - public void setYearTextAppearance(int resId) { - mAdapter.setItemTextAppearance(resId); - } - - public void setYearActivatedTextAppearance(int resId) { - mAdapter.setItemActivatedTextAppearance(resId); - } - private static class YearAdapter extends BaseAdapter { private static final int ITEM_LAYOUT = R.layout.year_label_text_view; + private static final int ITEM_TEXT_APPEARANCE = + R.style.TextAppearance_Material_DatePicker_List_YearLabel; + private static final int ITEM_TEXT_ACTIVATED_APPEARANCE = + R.style.TextAppearance_Material_DatePicker_List_YearLabel_Activated; private final LayoutInflater mInflater; @@ -128,9 +124,6 @@ class YearPickerView extends ListView { private int mMinYear; private int mCount; - private int mItemTextAppearanceResId; - private int mItemActivatedTextAppearanceResId; - public YearAdapter(Context context) { mInflater = LayoutInflater.from(context); } @@ -155,16 +148,6 @@ class YearPickerView extends ListView { return false; } - public void setItemTextAppearance(int resId) { - mItemTextAppearanceResId = resId; - notifyDataSetChanged(); - } - - public void setItemActivatedTextAppearance(int resId) { - mItemActivatedTextAppearanceResId = resId; - notifyDataSetChanged(); - } - @Override public int getCount() { return mCount; @@ -203,10 +186,10 @@ class YearPickerView extends ListView { final boolean activated = mActivatedYear == year; final int textAppearanceResId; - if (activated && mItemActivatedTextAppearanceResId != 0) { - textAppearanceResId = mItemActivatedTextAppearanceResId; + if (activated && ITEM_TEXT_ACTIVATED_APPEARANCE != 0) { + textAppearanceResId = ITEM_TEXT_ACTIVATED_APPEARANCE; } else { - textAppearanceResId = mItemTextAppearanceResId; + textAppearanceResId = ITEM_TEXT_APPEARANCE; } final TextView v = (TextView) convertView; |
