diff options
Diffstat (limited to 'core/java/android')
83 files changed, 4147 insertions, 1335 deletions
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index b644dd1..850fe48 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -284,16 +284,6 @@ public abstract class AccessibilityService extends Service { public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 16; /** - * The user has performed a two finger tap gesture on the touch screen. - */ - public static final int GESTURE_TWO_FINGER_TAP = 17; - - /** - * The user has performed a two finger long press gesture on the touch screen. - */ - public static final int GESTURE_TWO_FINGER_LONG_PRESS = 18; - - /** * The {@link Intent} that must be declared as handled by the service. */ public static final String SERVICE_INTERFACE = @@ -377,14 +367,12 @@ public abstract class AccessibilityService extends Service { /** * Called by the system when the user performs a specific gesture on the - * touch screen. If the gesture is not handled in this callback the system - * may provide default handing. Therefore, one should return true from this - * function if overriding of default behavior is desired. + * touch screen. * - * <strong>Note:</strong> To receive gestures an accessibility service - * must declare that it can handle such by specifying the - * <code><{@link android.R.styleable#AccessibilityService_canHandleGestures - * canHandleGestures}></code> attribute. + * <strong>Note:</strong> To receive gestures an accessibility service must + * request that the device is in touch exploration mode by setting the + * {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_INCLUDE_NOT_IMPORTANT_VIEWS} + * flag. * * @param gestureId The unique id of the performed gesture. * @@ -406,13 +394,8 @@ public abstract class AccessibilityService extends Service { * @see #GESTURE_SWIPE_RIGHT_AND_UP * @see #GESTURE_SWIPE_RIGHT_AND_LEFT * @see #GESTURE_SWIPE_RIGHT_AND_DOWN - * @see #GESTURE_CLOCKWISE_CIRCLE - * @see #GESTURE_COUNTER_CLOCKWISE_CIRCLE - * @see #GESTURE_TWO_FINGER_TAP - * @see #GESTURE_TWO_FINGER_LONG_PRESS */ protected boolean onGesture(int gestureId) { - // TODO: Describe the default gesture processing in the javaDoc once it is finalized. return false; } diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 7e6786b..10ea0fe 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -131,7 +131,19 @@ public class AccessibilityServiceInfo implements Parcelable { * elements. * </p> */ - public static final int INCLUDE_NOT_IMPORTANT_VIEWS = 0x0000002; + public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x0000002; + + /** + * This flag requests that the system gets into touch exploration mode. + * In this mode a single finger moving on the screen behaves as a mouse + * pointer hovering over the user interface. The system will also detect + * certain gestures performed on the touch screen and notify this service. + * The system will enable touch exploration mode if there is at least one + * accessibility service that has this flag set. Hence, clearing this + * flag does not guarantee that the device will not be in touch exploration + * mode since there may be another enabled service that requested it. + */ + public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE= 0x0000004; /** * The event types an {@link AccessibilityService} is interested in. @@ -198,7 +210,8 @@ public class AccessibilityServiceInfo implements Parcelable { * <strong>Can be dynamically set at runtime.</strong> * </p> * @see #DEFAULT - * @see #INCLUDE_NOT_IMPORTANT_VIEWS + * @see #FLAG_INCLUDE_NOT_IMPORTANT_VIEWS + * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE */ public int flags; @@ -224,11 +237,6 @@ public class AccessibilityServiceInfo implements Parcelable { private boolean mCanRetrieveWindowContent; /** - * Flag whether this accessibility service can handle gestures. - */ - private boolean mCanHandleGestures; - - /** * Resource id of the description of the accessibility service. */ private int mDescriptionResId; @@ -308,8 +316,6 @@ public class AccessibilityServiceInfo implements Parcelable { mCanRetrieveWindowContent = asAttributes.getBoolean( com.android.internal.R.styleable.AccessibilityService_canRetrieveWindowContent, false); - mCanHandleGestures = asAttributes.getBoolean( - com.android.internal.R.styleable.AccessibilityService_canHandleGestures, false); TypedValue peekedValue = asAttributes.peekValue( com.android.internal.R.styleable.AccessibilityService_description); if (peekedValue != null) { @@ -392,18 +398,6 @@ public class AccessibilityServiceInfo implements Parcelable { } /** - * Whether this service can handle gestures. - * <p> - * <strong>Statically set from - * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> - * </p> - * @return True if the service can handle gestures. - */ - public boolean getCanHandleGestures() { - return mCanHandleGestures; - } - - /** * Gets the non-localized description of the accessibility service. * <p> * <strong>Statically set from @@ -614,8 +608,10 @@ public class AccessibilityServiceInfo implements Parcelable { switch (flag) { case DEFAULT: return "DEFAULT"; - case INCLUDE_NOT_IMPORTANT_VIEWS: - return "REGARD_VIEWS_NOT_IMPORTANT_FOR_ACCESSIBILITY"; + case FLAG_INCLUDE_NOT_IMPORTANT_VIEWS: + return "FLAG_INCLUDE_NOT_IMPORTANT_VIEWS"; + case FLAG_REQUEST_TOUCH_EXPLORATION_MODE: + return "FLAG_REQUEST_TOUCH_EXPLORATION_MODE"; default: return null; } diff --git a/core/java/android/accessibilityservice/UiTestAutomationBridge.java b/core/java/android/accessibilityservice/UiTestAutomationBridge.java index 4d4bfeb..6837386 100644 --- a/core/java/android/accessibilityservice/UiTestAutomationBridge.java +++ b/core/java/android/accessibilityservice/UiTestAutomationBridge.java @@ -83,7 +83,7 @@ public class UiTestAutomationBridge { * @return The event. */ public AccessibilityEvent getLastAccessibilityEvent() { - return mLastEvent; + return mLastEvent; } /** @@ -142,7 +142,7 @@ public class UiTestAutomationBridge { @Override public void onInterrupt() { - UiTestAutomationBridge.this.onInterrupt(); + UiTestAutomationBridge.this.onInterrupt(); } @Override @@ -189,6 +189,7 @@ public class UiTestAutomationBridge { final AccessibilityServiceInfo info = new AccessibilityServiceInfo(); info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK; info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC; + info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; try { manager.registerUiTestAutomationService(mListener, info); diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index 326f27c..f3a442a 100755 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -910,7 +910,7 @@ public class ValueAnimator extends Animator { animationHandler.mPendingAnimations.add(this); if (mStartDelay == 0) { // This sets the initial value of the animation, prior to actually starting it running - setCurrentPlayTime(getCurrentPlayTime()); + setCurrentPlayTime(0); mPlayingState = STOPPED; mRunning = true; notifyStartListeners(); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 29d96fe..ac55abe 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -2493,7 +2493,7 @@ public class Activity extends ContextThemeWrapper if (featureId == Window.FEATURE_OPTIONS_PANEL && menu != null) { boolean goforit = onPrepareOptionsMenu(menu); goforit |= mFragments.dispatchPrepareOptionsMenu(menu); - return goforit && menu.hasVisibleItems(); + return goforit; } return true; } @@ -2540,11 +2540,10 @@ public class Activity extends ContextThemeWrapper if (item.getItemId() == android.R.id.home && mActionBar != null && (mActionBar.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0) { if (mParent == null) { - onNavigateUp(); + return onNavigateUp(); } else { - mParent.onNavigateUpFromChild(this); + return mParent.onNavigateUpFromChild(this); } - return true; } return false; diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 7746ca9..4e61c3c 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1078,13 +1078,20 @@ public class ActivityManager { */ public static class MemoryInfo implements Parcelable { /** - * The total available memory on the system. This number should not + * The available memory on the system. This number should not * be considered absolute: due to the nature of the kernel, a significant * portion of this memory is actually in use and needed for the overall * system to run well. */ public long availMem; - + + /** + * The total memory accessible by the kernel. This is basically the + * RAM size of the device, not including below-kernel fixed allocations + * like DMA buffers, RAM for the baseband CPU, etc. + */ + public long totalMem; + /** * The threshold of {@link #availMem} at which we consider memory to be * low and start killing background services and other non-extraneous @@ -1116,6 +1123,7 @@ public class ActivityManager { public void writeToParcel(Parcel dest, int flags) { dest.writeLong(availMem); + dest.writeLong(totalMem); dest.writeLong(threshold); dest.writeInt(lowMemory ? 1 : 0); dest.writeLong(hiddenAppThreshold); @@ -1126,6 +1134,7 @@ public class ActivityManager { public void readFromParcel(Parcel source) { availMem = source.readLong(); + totalMem = source.readLong(); threshold = source.readLong(); lowMemory = source.readInt() != 0; hiddenAppThreshold = source.readLong(); diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index b730581..523a78d 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -98,6 +98,8 @@ public class ActivityOptions { public static final int ANIM_SCALE_UP = 2; /** @hide */ public static final int ANIM_THUMBNAIL = 3; + /** @hide */ + public static final int ANIM_THUMBNAIL_DELAYED = 4; private String mPackageName; private int mAnimationType = ANIM_NONE; @@ -147,12 +149,17 @@ public class ActivityOptions { * activity is scaled from a small originating area of the screen to * its final full representation. * + * <p>If the Intent this is being used with has not set its + * {@link android.content.Intent#setSourceBounds Intent.setSourceBounds}, + * those bounds will be filled in for you based on the initial + * bounds passed in here. + * * @param source The View that the new activity is animating from. This * defines the coordinate space for <var>startX</var> and <var>startY</var>. * @param startX The x starting location of the new activity, relative to <var>source</var>. * @param startY The y starting location of the activity, relative to <var>source</var>. * @param startWidth The initial width of the new activity. - * @param startWidth The initial height of the new activity. + * @param startHeight The initial height of the new activity. * @return Returns a new ActivityOptions object that you can use to * supply these options as the options Bundle when starting an activity. */ @@ -175,6 +182,11 @@ public class ActivityOptions { * is scaled from a given position to the new activity window that is * being started. * + * <p>If the Intent this is being used with has not set its + * {@link android.content.Intent#setSourceBounds Intent.setSourceBounds}, + * those bounds will be filled in for you based on the initial + * thumbnail location and size provided here. + * * @param source The View that this thumbnail is animating from. This * defines the coordinate space for <var>startX</var> and <var>startY</var>. * @param thumbnail The bitmap that will be shown as the initial thumbnail @@ -209,9 +221,38 @@ public class ActivityOptions { */ public static ActivityOptions makeThumbnailScaleUpAnimation(View source, Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener) { + return makeThumbnailScaleUpAnimation(source, thumbnail, startX, startY, listener, false); + } + + /** + * Create an ActivityOptions specifying an animation where a thumbnail + * is scaled from a given position to the new activity window that is + * being started. Before the animation, there is a short delay. + * + * @param source The View that this thumbnail is animating from. This + * defines the coordinate space for <var>startX</var> and <var>startY</var>. + * @param thumbnail The bitmap that will be shown as the initial thumbnail + * of the animation. + * @param startX The x starting location of the bitmap, relative to <var>source</var>. + * @param startY The y starting location of the bitmap, relative to <var>source</var>. + * @param listener Optional OnAnimationStartedListener to find out when the + * requested animation has started running. If for some reason the animation + * is not executed, the callback will happen immediately. + * @return Returns a new ActivityOptions object that you can use to + * supply these options as the options Bundle when starting an activity. + * @hide + */ + public static ActivityOptions makeDelayedThumbnailScaleUpAnimation(View source, + Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener) { + return makeThumbnailScaleUpAnimation(source, thumbnail, startX, startY, listener, true); + } + + private static ActivityOptions makeThumbnailScaleUpAnimation(View source, + Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener, + boolean delayed) { ActivityOptions opts = new ActivityOptions(); opts.mPackageName = source.getContext().getPackageName(); - opts.mAnimationType = ANIM_THUMBNAIL; + opts.mAnimationType = delayed ? ANIM_THUMBNAIL_DELAYED : ANIM_THUMBNAIL; opts.mThumbnail = thumbnail; int[] pts = new int[2]; source.getLocationOnScreen(pts); @@ -248,7 +289,8 @@ public class ActivityOptions { mStartY = opts.getInt(KEY_ANIM_START_Y, 0); mStartWidth = opts.getInt(KEY_ANIM_START_WIDTH, 0); mStartHeight = opts.getInt(KEY_ANIM_START_HEIGHT, 0); - } else if (mAnimationType == ANIM_THUMBNAIL) { + } else if (mAnimationType == ANIM_THUMBNAIL || + mAnimationType == ANIM_THUMBNAIL_DELAYED) { mThumbnail = (Bitmap)opts.getParcelable(KEY_ANIM_THUMBNAIL); mStartX = opts.getInt(KEY_ANIM_START_X, 0); mStartY = opts.getInt(KEY_ANIM_START_Y, 0); @@ -349,6 +391,7 @@ public class ActivityOptions { mStartHeight = otherOptions.mStartHeight; break; case ANIM_THUMBNAIL: + case ANIM_THUMBNAIL_DELAYED: mAnimationType = otherOptions.mAnimationType; mThumbnail = otherOptions.mThumbnail; mStartX = otherOptions.mStartX; @@ -391,6 +434,7 @@ public class ActivityOptions { b.putInt(KEY_ANIM_START_HEIGHT, mStartHeight); break; case ANIM_THUMBNAIL: + case ANIM_THUMBNAIL_DELAYED: b.putInt(KEY_ANIM_TYPE, mAnimationType); b.putParcelable(KEY_ANIM_THUMBNAIL, mThumbnail); b.putInt(KEY_ANIM_START_X, mStartX); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index b55ee26..e2ebeba 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -136,7 +136,7 @@ public final class ActivityThread { /** @hide */ public static final boolean DEBUG_BROADCAST = false; private static final boolean DEBUG_RESULTS = false; - private static final boolean DEBUG_BACKUP = true; + private static final boolean DEBUG_BACKUP = false; private static final boolean DEBUG_CONFIGURATION = false; private static final boolean DEBUG_SERVICE = false; private static final boolean DEBUG_MEMORY_TRIM = false; @@ -1172,10 +1172,10 @@ public final class ActivityThread { case DUMP_PROVIDER: return "DUMP_PROVIDER"; } } - return "(unknown)"; + return Integer.toString(code); } public void handleMessage(Message msg) { - if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + msg.what); + if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what)); switch (msg.what) { case LAUNCH_ACTIVITY: { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); @@ -1378,7 +1378,7 @@ public final class ActivityThread { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; } - if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + msg.what); + if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what)); } private void maybeSnapshot() { @@ -2639,6 +2639,7 @@ public final class ActivityThread { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity " + r.activityInfo.name + " with newConfig " + r.newConfig); performConfigurationChanged(r.activity, r.newConfig); + freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.newConfig)); r.newConfig = null; } if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" @@ -2955,6 +2956,7 @@ public final class ActivityThread { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Updating activity vis " + r.activityInfo.name + " with new config " + r.newConfig); performConfigurationChanged(r.activity, r.newConfig); + freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.newConfig)); r.newConfig = null; } } else { @@ -3669,6 +3671,7 @@ public final class ActivityThread { final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) { ArrayList<ComponentCallbacks2> callbacks = null; + int configDiff = 0; synchronized (mPackages) { if (mPendingConfiguration != null) { @@ -3693,6 +3696,7 @@ public final class ActivityThread { if (!mConfiguration.isOtherSeqNewer(config) && compat == null) { return; } + configDiff = mConfiguration.diff(config); mConfiguration.updateFrom(config); config = applyCompatConfiguration(); callbacks = collectComponentCallbacksLocked(false, config); @@ -3701,6 +3705,8 @@ public final class ActivityThread { // Cleanup hardware accelerated stuff WindowManagerImpl.getDefault().trimLocalMemory(); + freeTextLayoutCachesIfNeeded(configDiff); + if (callbacks != null) { final int N = callbacks.size(); for (int i=0; i<N; i++) { @@ -3709,6 +3715,17 @@ public final class ActivityThread { } } + final void freeTextLayoutCachesIfNeeded(int configDiff) { + if (configDiff != 0) { + // Ask text layout engine to free its caches if there is a locale change + boolean hasLocaleConfigChange = ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0); + if (hasLocaleConfigChange) { + Canvas.freeTextLayoutCaches(); + if (DEBUG_CONFIGURATION) Slog.v(TAG, "Cleared TextLayout Caches"); + } + } + } + final void handleActivityConfigurationChanged(IBinder token) { ActivityClientRecord r = mActivities.get(token); if (r == null || r.activity == null) { @@ -3719,6 +3736,8 @@ public final class ActivityThread { + r.activityInfo.name); performConfigurationChanged(r.activity, mCompatConfiguration); + + freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(mCompatConfiguration)); } final void handleProfilerControl(boolean start, ProfilerControlData pcd, int profileType) { @@ -3821,6 +3840,9 @@ public final class ActivityThread { // Ask graphics to free up as much as possible (font/image caches) Canvas.freeCaches(); + // Ask text layout engine to free also as much as possible + Canvas.freeTextLayoutCaches(); + BinderInternal.forceGc("mem"); } @@ -4256,6 +4278,14 @@ public final class ActivityThread { } } + public final IContentProvider acquireUnstableProvider(Context c, String name) { + return acquireProvider(c, name); + } + + public final boolean releaseUnstableProvider(IContentProvider provider) { + return releaseProvider(provider); + } + final void completeRemoveProvider(IContentProvider provider) { IBinder jBinder = provider.asBinder(); String remoteProviderName = null; diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 0510de1..191a696 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -24,6 +24,7 @@ import android.content.IntentSender; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ComponentInfo; +import android.content.pm.ContainerEncryptionParams; import android.content.pm.FeatureInfo; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageDeleteObserver; @@ -973,10 +974,10 @@ final class ApplicationPackageManager extends PackageManager { @Override public void installPackageWithVerification(Uri packageURI, IPackageInstallObserver observer, int flags, String installerPackageName, Uri verificationURI, - ManifestDigest manifestDigest) { + ManifestDigest manifestDigest, ContainerEncryptionParams encryptionParams) { try { mPM.installPackageWithVerification(packageURI, observer, flags, installerPackageName, - verificationURI, manifestDigest); + verificationURI, manifestDigest, encryptionParams); } catch (RemoteException e) { // Should never happen! } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 64a05a8..299e408 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -387,7 +387,7 @@ class ContextImpl extends Context { public Object createService(ContextImpl ctx) { IBinder b = ServiceManager.getService(NSD_SERVICE); INsdManager service = INsdManager.Stub.asInterface(b); - return new NsdManager(service); + return new NsdManager(ctx.getOuterContext(), service); }}); // Note: this was previously cached in a static variable, but @@ -1692,6 +1692,16 @@ class ContextImpl extends Context { return mMainThread.releaseProvider(provider); } + @Override + protected IContentProvider acquireUnstableProvider(Context c, String name) { + return mMainThread.acquireUnstableProvider(c, name); + } + + @Override + public boolean releaseUnstableProvider(IContentProvider icp) { + return mMainThread.releaseUnstableProvider(icp); + } + private final ActivityThread mMainThread; } } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 0d76877..0c47069 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -390,21 +390,25 @@ public class Notification implements Parcelable public int priority; /** + * @hide * Notification type: incoming call (voice or video) or similar synchronous communication request. */ public static final String KIND_CALL = "android.call"; /** + * @hide * Notification type: incoming direct message (SMS, instant message, etc.). */ public static final String KIND_MESSAGE = "android.message"; /** + * @hide * Notification type: asynchronous bulk message (email). */ public static final String KIND_EMAIL = "android.email"; /** + * @hide * Notification type: calendar event. */ public static final String KIND_EVENT = "android.event"; @@ -415,6 +419,7 @@ public class Notification implements Parcelable public static final String KIND_PROMO = "android.promo"; /** + * @hide * If this notification matches of one or more special types (see the <code>KIND_*</code> * constants), add them here, best match first. */ @@ -796,6 +801,12 @@ public class Notification implements Parcelable if (this.icon != 0) { contentView.setImageViewResource(R.id.icon, this.icon); } + if (priority < PRIORITY_LOW) { + contentView.setInt(R.id.icon, + "setBackgroundResource", R.drawable.notification_template_icon_low_bg); + contentView.setInt(R.id.status_bar_latest_event_content, + "setBackgroundResource", R.drawable.notification_bg_low); + } if (contentTitle != null) { contentView.setTextViewText(R.id.title, contentTitle); } @@ -971,8 +982,14 @@ public class Notification implements Parcelable } /** - * Show the {@link Notification#when} field as a countdown (or count-up) timer instead of a timestamp. + * Show the {@link Notification#when} field as a stopwatch. + * + * Instead of presenting <code>when</code> as a timestamp, the notification will show an + * automatically updating display of the minutes and seconds since <code>when</code>. * + * Useful when showing an elapsed time (like an ongoing phone call). + * + * @see android.widget.Chronometer * @see Notification#when */ public Builder setUsesChronometer(boolean b) { @@ -1297,6 +1314,8 @@ public class Notification implements Parcelable } /** + * @hide + * * Add a kind (category) to this notification. Optional. * * @see Notification#kind @@ -1364,6 +1383,12 @@ public class Notification implements Parcelable contentView.setImageViewBitmap(R.id.icon, mLargeIcon); smallIconImageViewId = R.id.right_icon; } + if (mPriority < PRIORITY_LOW) { + contentView.setInt(R.id.icon, + "setBackgroundResource", R.drawable.notification_template_icon_low_bg); + contentView.setInt(R.id.status_bar_latest_event_content, + "setBackgroundResource", R.drawable.notification_bg_low); + } if (mSmallIcon != 0) { contentView.setImageViewResource(smallIconImageViewId, mSmallIcon); contentView.setViewVisibility(smallIconImageViewId, View.VISIBLE); @@ -1436,6 +1461,7 @@ public class Notification implements Parcelable // Log.d("Notification", "has actions: " + mContentText); big.setViewVisibility(R.id.actions, View.VISIBLE); if (N>3) N=3; + big.removeAllViews(R.id.actions); for (int i=0; i<N; i++) { final RemoteViews button = generateActionButton(mActions.get(i)); //Log.d("Notification", "adding action " + i + ": " + mActions.get(i).title); @@ -1477,7 +1503,16 @@ public class Notification implements Parcelable RemoteViews button = new RemoteViews(mContext.getPackageName(), R.layout.notification_action); button.setTextViewCompoundDrawables(R.id.action0, action.icon, 0, 0, 0); button.setTextViewText(R.id.action0, action.title); - button.setOnClickPendingIntent(R.id.action0, action.actionIntent); + if (action.actionIntent != null) { + button.setOnClickPendingIntent(R.id.action0, action.actionIntent); + //button.setBoolean(R.id.action0, "setEnabled", true); + button.setFloat(R.id.button0, "setAlpha", 1.0f); + button.setBoolean(R.id.button0, "setClickable", true); + } else { + //button.setBoolean(R.id.action0, "setEnabled", false); + button.setFloat(R.id.button0, "setAlpha", 0.5f); + button.setBoolean(R.id.button0, "setClickable", false); + } button.setContentDescription(R.id.action0, action.title); return button; } diff --git a/core/java/android/app/TaskStackBuilder.java b/core/java/android/app/TaskStackBuilder.java index e546f6c..14c5736 100644 --- a/core/java/android/app/TaskStackBuilder.java +++ b/core/java/android/app/TaskStackBuilder.java @@ -196,18 +196,12 @@ public class TaskStackBuilder { try { ActivityInfo info = pm.getActivityInfo(sourceActivityName, 0); String parentActivity = info.parentActivityName; - Intent parent = new Intent().setComponent( - new ComponentName(info.packageName, parentActivity)); - while (parent != null) { + while (parentActivity != null) { + Intent parent = new Intent().setComponent( + new ComponentName(info.packageName, parentActivity)); mIntents.add(insertAt, parent); info = pm.getActivityInfo(parent.getComponent(), 0); parentActivity = info.parentActivityName; - if (parentActivity != null) { - parent = new Intent().setComponent( - new ComponentName(info.packageName, parentActivity)); - } else { - parent = null; - } } } catch (NameNotFoundException e) { Log.e(TAG, "Bad ComponentName while traversing activity parent metadata"); diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index c9bacba..01b68d4 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -208,8 +208,10 @@ public class AppWidgetHostView extends FrameLayout { } /** - * Provide guidance about the size of this widget to the AppWidgetManager. This information - * gets embedded into the AppWidgetExtras and causes a callback to the AppWidgetProvider. + * Provide guidance about the size of this widget to the AppWidgetManager. The widths and + * heights should correspond to the full area the AppWidgetHostView is given. Padding added by + * the framework will be accounted for automatically. This information gets embedded into the + * AppWidget options and causes a callback to the AppWidgetProvider. * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle) * * @param options The bundle of options, in addition to the size information, @@ -225,10 +227,20 @@ public class AppWidgetHostView extends FrameLayout { if (options == null) { options = new Bundle(); } - options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, minWidth); - options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, minHeight); - options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, maxWidth); - options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, maxHeight); + + Rect padding = new Rect(); + if (mInfo != null) { + padding = getDefaultPaddingForWidget(mContext, mInfo.provider, padding); + } + float density = getResources().getDisplayMetrics().density; + + int xPaddingDips = (int) ((padding.left + padding.right) / density); + int yPaddingDips = (int) ((padding.top + padding.bottom) / density); + + options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, minWidth - xPaddingDips); + options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, minHeight - yPaddingDips); + options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, maxWidth - xPaddingDips); + options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, maxHeight - yPaddingDips); updateAppWidgetOptions(options); } diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java index da51952..f9025d9 100644 --- a/core/java/android/content/AsyncTaskLoader.java +++ b/core/java/android/content/AsyncTaskLoader.java @@ -18,6 +18,7 @@ package android.content; import android.os.AsyncTask; import android.os.Handler; +import android.os.OperationCanceledException; import android.os.SystemClock; import android.util.Slog; import android.util.TimeUtils; diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index 1206056..b22179e 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -29,6 +29,9 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.ICancellationSignal; +import android.os.OperationCanceledException; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java index 3ac5e07..423f1f6 100644 --- a/core/java/android/content/ContentProviderClient.java +++ b/core/java/android/content/ContentProviderClient.java @@ -19,6 +19,8 @@ package android.content; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.ICancellationSignal; import android.os.RemoteException; import android.os.ParcelFileDescriptor; import android.content.res.AssetFileDescriptor; @@ -35,13 +37,16 @@ import java.util.ArrayList; public class ContentProviderClient { private final IContentProvider mContentProvider; private final ContentResolver mContentResolver; + private final boolean mStable; /** * @hide */ - ContentProviderClient(ContentResolver contentResolver, IContentProvider contentProvider) { + ContentProviderClient(ContentResolver contentResolver, + IContentProvider contentProvider, boolean stable) { mContentProvider = contentProvider; mContentResolver = contentResolver; + mStable = stable; } /** See {@link ContentProvider#query ContentProvider.query} */ @@ -139,7 +144,11 @@ public class ContentProviderClient { * @return true if this was release, false if it was already released */ public boolean release() { - return mContentResolver.releaseProvider(mContentProvider); + if (mStable) { + return mContentResolver.releaseProvider(mContentProvider); + } else { + return mContentResolver.releaseUnstableProvider(mContentProvider); + } } /** diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java index 4b31552..550a1c9 100644 --- a/core/java/android/content/ContentProviderNative.java +++ b/core/java/android/content/ContentProviderNative.java @@ -30,6 +30,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.RemoteException; import android.os.IBinder; +import android.os.ICancellationSignal; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 722fdc6..f509fd8 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -33,7 +33,10 @@ import android.database.CursorWrapper; import android.database.IContentObserver; import android.net.Uri; import android.os.Bundle; +import android.os.CancellationSignal; import android.os.IBinder; +import android.os.ICancellationSignal; +import android.os.OperationCanceledException; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; @@ -195,6 +198,10 @@ public abstract class ContentResolver { } /** @hide */ public abstract boolean releaseProvider(IContentProvider icp); + /** @hide */ + protected abstract IContentProvider acquireUnstableProvider(Context c, String name); + /** @hide */ + public abstract boolean releaseUnstableProvider(IContentProvider icp); /** * Return the MIME type of the given content URL. @@ -585,34 +592,48 @@ public abstract class ContentResolver { if ("r".equals(mode)) { return openTypedAssetFileDescriptor(uri, "*/*", null); } else { - IContentProvider provider = acquireProvider(uri); - if (provider == null) { - throw new FileNotFoundException("No content provider: " + uri); - } - try { - AssetFileDescriptor fd = provider.openAssetFile(uri, mode); - if(fd == null) { - // The provider will be released by the finally{} clause - return null; + int n = 0; + while (true) { + n++; + IContentProvider provider = acquireUnstableProvider(uri); + if (provider == null) { + throw new FileNotFoundException("No content provider: " + uri); } - ParcelFileDescriptor pfd = new ParcelFileDescriptorInner( - fd.getParcelFileDescriptor(), provider); - - // Success! Don't release the provider when exiting, let - // ParcelFileDescriptorInner do that when it is closed. - provider = null; - - return new AssetFileDescriptor(pfd, fd.getStartOffset(), - fd.getDeclaredLength()); - } catch (RemoteException e) { - // Somewhat pointless, as Activity Manager will kill this - // process shortly anyway if the depdendent ContentProvider dies. - throw new FileNotFoundException("Dead content provider: " + uri); - } catch (FileNotFoundException e) { - throw e; - } finally { - if (provider != null) { - releaseProvider(provider); + try { + AssetFileDescriptor fd = provider.openAssetFile(uri, mode); + if (fd == null) { + // The provider will be released by the finally{} clause + return null; + } + ParcelFileDescriptor pfd = new ParcelFileDescriptorInner( + fd.getParcelFileDescriptor(), provider); + + // Success! Don't release the provider when exiting, let + // ParcelFileDescriptorInner do that when it is closed. + provider = null; + + return new AssetFileDescriptor(pfd, fd.getStartOffset(), + fd.getDeclaredLength()); + } catch (RemoteException e) { + // The provider died for some reason. Since we are + // acquiring it unstable, its process could have gotten + // killed and need to be restarted. We'll retry a couple + // times and if still can't succeed then fail. + if (n <= 2) { + try { + Thread.sleep(100); + } catch (InterruptedException e1) { + } + continue; + } + // Whatever, whatever, we'll go away. + throw new FileNotFoundException("Dead content provider: " + uri); + } catch (FileNotFoundException e) { + throw e; + } finally { + if (provider != null) { + releaseUnstableProvider(provider); + } } } } @@ -649,32 +670,48 @@ public abstract class ContentResolver { */ public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri, String mimeType, Bundle opts) throws FileNotFoundException { - IContentProvider provider = acquireProvider(uri); - if (provider == null) { - throw new FileNotFoundException("No content provider: " + uri); - } - try { - AssetFileDescriptor fd = provider.openTypedAssetFile(uri, mimeType, opts); - if (fd == null) { - // The provider will be released by the finally{} clause - return null; + int n = 0; + while (true) { + n++; + IContentProvider provider = acquireUnstableProvider(uri); + if (provider == null) { + throw new FileNotFoundException("No content provider: " + uri); } - ParcelFileDescriptor pfd = new ParcelFileDescriptorInner( - fd.getParcelFileDescriptor(), provider); + try { + AssetFileDescriptor fd = provider.openTypedAssetFile(uri, mimeType, opts); + if (fd == null) { + // The provider will be released by the finally{} clause + return null; + } + ParcelFileDescriptor pfd = new ParcelFileDescriptorInner( + fd.getParcelFileDescriptor(), provider); - // Success! Don't release the provider when exiting, let - // ParcelFileDescriptorInner do that when it is closed. - provider = null; + // Success! Don't release the provider when exiting, let + // ParcelFileDescriptorInner do that when it is closed. + provider = null; - return new AssetFileDescriptor(pfd, fd.getStartOffset(), - fd.getDeclaredLength()); - } catch (RemoteException e) { - throw new FileNotFoundException("Dead content provider: " + uri); - } catch (FileNotFoundException e) { - throw e; - } finally { - if (provider != null) { - releaseProvider(provider); + return new AssetFileDescriptor(pfd, fd.getStartOffset(), + fd.getDeclaredLength()); + } catch (RemoteException e) { + // The provider died for some reason. Since we are + // acquiring it unstable, its process could have gotten + // killed and need to be restarted. We'll retry a couple + // times and if still can't succeed then fail. + if (n <= 2) { + try { + Thread.sleep(100); + } catch (InterruptedException e1) { + } + continue; + } + // Whatever, whatever, we'll go away. + throw new FileNotFoundException("Dead content provider: " + uri); + } catch (FileNotFoundException e) { + throw e; + } finally { + if (provider != null) { + releaseUnstableProvider(provider); + } } } } @@ -1000,6 +1037,34 @@ public abstract class ContentResolver { } /** + * Returns the content provider for the given content URI. + * + * @param uri The URI to a content provider + * @return The ContentProvider for the given URI, or null if no content provider is found. + * @hide + */ + public final IContentProvider acquireUnstableProvider(Uri uri) { + if (!SCHEME_CONTENT.equals(uri.getScheme())) { + return null; + } + String auth = uri.getAuthority(); + if (auth != null) { + return acquireUnstableProvider(mContext, uri.getAuthority()); + } + return null; + } + + /** + * @hide + */ + public final IContentProvider acquireUnstableProvider(String name) { + if (name == null) { + return null; + } + return acquireProvider(mContext, name); + } + + /** * Returns a {@link ContentProviderClient} that is associated with the {@link ContentProvider} * that services the content at uri, starting the provider if necessary. Returns * null if there is no provider associated wih the uri. The caller must indicate that they are @@ -1013,7 +1078,7 @@ public abstract class ContentResolver { public final ContentProviderClient acquireContentProviderClient(Uri uri) { IContentProvider provider = acquireProvider(uri); if (provider != null) { - return new ContentProviderClient(this, provider); + return new ContentProviderClient(this, provider, true); } return null; @@ -1033,7 +1098,47 @@ public abstract class ContentResolver { public final ContentProviderClient acquireContentProviderClient(String name) { IContentProvider provider = acquireProvider(name); if (provider != null) { - return new ContentProviderClient(this, provider); + return new ContentProviderClient(this, provider, true); + } + + return null; + } + + /** + * Like {@link #acquireContentProviderClient(Uri)}, but for use when you do + * not trust the stability of the target content provider. This turns off + * the mechanism in the platform clean up processes that are dependent on + * a content provider if that content provider's process goes away. Normally + * you can safely assume that once you have acquired a provider, you can freely + * use it as needed and it won't disappear, even if your process is in the + * background. If using this method, you need to take care to deal with any + * failures when communicating with the provider, and be sure to close it + * so that it can be re-opened later. + */ + public final ContentProviderClient acquireUnstableContentProviderClient(Uri uri) { + IContentProvider provider = acquireProvider(uri); + if (provider != null) { + return new ContentProviderClient(this, provider, false); + } + + return null; + } + + /** + * Like {@link #acquireContentProviderClient(String)}, but for use when you do + * not trust the stability of the target content provider. This turns off + * the mechanism in the platform clean up processes that are dependent on + * a content provider if that content provider's process goes away. Normally + * you can safely assume that once you have acquired a provider, you can freely + * use it as needed and it won't disappear, even if your process is in the + * background. If using this method, you need to take care to deal with any + * failures when communicating with the provider, and be sure to close it + * so that it can be re-opened later. + */ + public final ContentProviderClient acquireUnstableContentProviderClient(String name) { + IContentProvider provider = acquireProvider(name); + if (provider != null) { + return new ContentProviderClient(this, provider, false); } return null; @@ -1687,7 +1792,7 @@ public abstract class ContentResolver { public void close() throws IOException { if(!mReleaseProviderFlag) { super.close(); - ContentResolver.this.releaseProvider(mContentProvider); + ContentResolver.this.releaseUnstableProvider(mContentProvider); mReleaseProviderFlag = true; } } diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java index aed3728..9f7a104 100644 --- a/core/java/android/content/CursorLoader.java +++ b/core/java/android/content/CursorLoader.java @@ -19,6 +19,8 @@ package android.content; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; +import android.os.CancellationSignal; +import android.os.OperationCanceledException; import java.io.FileDescriptor; import java.io.PrintWriter; diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java index 16478b7..eeba1e0 100644 --- a/core/java/android/content/IContentProvider.java +++ b/core/java/android/content/IContentProvider.java @@ -21,6 +21,7 @@ import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; +import android.os.ICancellationSignal; import android.os.IInterface; import android.os.ParcelFileDescriptor; import android.os.RemoteException; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 4ed6f25..c791e47 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -4573,7 +4573,7 @@ public class Intent implements Parcelable, Cloneable { * <p><em>Note: scheme matching in the Android framework is * case-sensitive, unlike the formal RFC. As a result, * you should always write your Uri with a lower case scheme, - * or use {@link Uri#normalize} or + * or use {@link Uri#normalizeScheme} or * {@link #setDataAndNormalize} * to ensure that the scheme is converted to lower case.</em> * @@ -4599,7 +4599,7 @@ public class Intent implements Parcelable, Cloneable { * previously set (for example, by {@link #setType}). * * <p>The data Uri is normalized using - * {@link android.net.Uri#normalize} before it is set, + * {@link android.net.Uri#normalizeScheme} before it is set, * so really this is just a convenience method for * <pre> * setData(data.normalize()) @@ -4612,10 +4612,10 @@ public class Intent implements Parcelable, Cloneable { * * @see #getData * @see #setType - * @see android.net.Uri#normalize + * @see android.net.Uri#normalizeScheme */ public Intent setDataAndNormalize(Uri data) { - return setData(data.normalize()); + return setData(data.normalizeScheme()); } /** @@ -4687,7 +4687,7 @@ public class Intent implements Parcelable, Cloneable { * <p><em>Note: MIME type and Uri scheme matching in the * Android framework is case-sensitive, unlike the formal RFC definitions. * As a result, you should always write these elements with lower case letters, - * or use {@link #normalizeMimeType} or {@link android.net.Uri#normalize} or + * or use {@link #normalizeMimeType} or {@link android.net.Uri#normalizeScheme} or * {@link #setDataAndTypeAndNormalize} * to ensure that they are converted to lower case.</em> * @@ -4700,7 +4700,7 @@ public class Intent implements Parcelable, Cloneable { * @see #setType * @see #setData * @see #normalizeMimeType - * @see android.net.Uri#normalize + * @see android.net.Uri#normalizeScheme * @see #setDataAndTypeAndNormalize */ public Intent setDataAndType(Uri data, String type) { @@ -4716,7 +4716,7 @@ public class Intent implements Parcelable, Cloneable { * data with your own type given here. * * <p>The data Uri and the MIME type are normalize using - * {@link android.net.Uri#normalize} and {@link #normalizeMimeType} + * {@link android.net.Uri#normalizeScheme} and {@link #normalizeMimeType} * before they are set, so really this is just a convenience method for * <pre> * setDataAndType(data.normalize(), Intent.normalizeMimeType(type)) @@ -4732,10 +4732,10 @@ public class Intent implements Parcelable, Cloneable { * @see #setData * @see #setDataAndType * @see #normalizeMimeType - * @see android.net.Uri#normalize + * @see android.net.Uri#normalizeScheme */ public Intent setDataAndTypeAndNormalize(Uri data, String type) { - return setDataAndType(data.normalize(), normalizeMimeType(type)); + return setDataAndType(data.normalizeScheme(), normalizeMimeType(type)); } /** diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java index 6c7e940..226e107 100644 --- a/core/java/android/content/SyncStorageEngine.java +++ b/core/java/android/content/SyncStorageEngine.java @@ -197,6 +197,29 @@ public class SyncStorageEngine extends Handler { long delayUntil; final ArrayList<Pair<Bundle, Long>> periodicSyncs; + /** + * Copy constructor for making deep-ish copies. Only the bundles stored + * in periodic syncs can make unexpected changes. + * + * @param toCopy AuthorityInfo to be copied. + */ + AuthorityInfo(AuthorityInfo toCopy) { + account = toCopy.account; + userId = toCopy.userId; + authority = toCopy.authority; + ident = toCopy.ident; + enabled = toCopy.enabled; + syncable = toCopy.syncable; + backoffTime = toCopy.backoffTime; + backoffDelay = toCopy.backoffDelay; + delayUntil = toCopy.delayUntil; + periodicSyncs = new ArrayList<Pair<Bundle, Long>>(); + for (Pair<Bundle, Long> sync : toCopy.periodicSyncs) { + // Still not a perfect copy, because we are just copying the mappings. + periodicSyncs.add(Pair.create(new Bundle(sync.first), sync.second)); + } + } + AuthorityInfo(Account account, int userId, String authority, int ident) { this.account = account; this.userId = userId; @@ -1212,7 +1235,8 @@ public class SyncStorageEngine extends Handler { final int N = mAuthorities.size(); ArrayList<AuthorityInfo> infos = new ArrayList<AuthorityInfo>(N); for (int i=0; i<N; i++) { - infos.add(mAuthorities.valueAt(i)); + // Make deep copy because AuthorityInfo syncs are liable to change. + infos.add(new AuthorityInfo(mAuthorities.valueAt(i))); } return infos; } diff --git a/core/java/android/net/nsd/NetworkServiceInfo.java b/core/java/android/content/pm/ContainerEncryptionParams.aidl index 34d83d1..c13d946 100644 --- a/core/java/android/net/nsd/NetworkServiceInfo.java +++ b/core/java/android/content/pm/ContainerEncryptionParams.aidl @@ -1,11 +1,11 @@ /* - * Copyright (C) 2012 The Android Open Source Project + * Copyright 2012, 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 + * 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, @@ -14,19 +14,6 @@ * limitations under the License. */ -package android.net.nsd; +package android.content.pm; -/** - * Interface for a network service. - * - * {@hide} - */ -public interface NetworkServiceInfo { - - String getServiceName(); - void setServiceName(String s); - - String getServiceType(); - void setServiceType(String s); - -} +parcelable ContainerEncryptionParams; diff --git a/core/java/android/content/pm/ContainerEncryptionParams.java b/core/java/android/content/pm/ContainerEncryptionParams.java new file mode 100644 index 0000000..88112a7 --- /dev/null +++ b/core/java/android/content/pm/ContainerEncryptionParams.java @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2012 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 android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.Slog; + +import java.security.InvalidAlgorithmParameterException; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; + +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; + +/** + * Represents encryption parameters used to read a container. + * + * @hide + */ +public class ContainerEncryptionParams implements Parcelable { + protected static final String TAG = "ContainerEncryptionParams"; + + /** What we print out first when toString() is called. */ + private static final String TO_STRING_PREFIX = "ContainerEncryptionParams{"; + + /** + * Parameter type for parceling that indicates the next parameters are + * IvParameters. + */ + private static final int ENC_PARAMS_IV_PARAMETERS = 1; + + /** Parameter type for paceling that indicates there are no MAC parameters. */ + private static final int MAC_PARAMS_NONE = 1; + + /** The encryption algorithm used. */ + private final String mEncryptionAlgorithm; + + /** The parameter spec to be used for encryption. */ + private final IvParameterSpec mEncryptionSpec; + + /** Secret key to be used for decryption. */ + private final SecretKey mEncryptionKey; + + /** Algorithm name for the MAC to be used. */ + private final String mMacAlgorithm; + + /** The parameter spec to be used for the MAC tag authentication. */ + private final AlgorithmParameterSpec mMacSpec; + + /** Secret key to be used for MAC tag authentication. */ + private final SecretKey mMacKey; + + /** MAC tag authenticating the data in the container. */ + private final byte[] mMacTag; + + /** Offset into file where authenticated (e.g., MAC protected) data begins. */ + private final long mAuthenticatedDataStart; + + /** Offset into file where encrypted data begins. */ + private final long mEncryptedDataStart; + + /** + * Offset into file for the end of encrypted data (and, by extension, + * authenticated data) in file. + */ + private final long mDataEnd; + + public ContainerEncryptionParams(String encryptionAlgorithm, + AlgorithmParameterSpec encryptionSpec, SecretKey encryptionKey) + throws InvalidAlgorithmParameterException { + this(encryptionAlgorithm, encryptionSpec, encryptionKey, null, null, null, null, -1, -1, + -1); + } + + /** + * Creates container encryption specifications for installing from encrypted + * containers. + * + * @param encryptionAlgorithm encryption algorithm to use; format matches + * JCE + * @param encryptionSpec algorithm parameter specification + * @param encryptionKey key used for decryption + * @param macAlgorithm MAC algorithm to use; format matches JCE + * @param macSpec algorithm parameters specification, may be {@code null} + * @param macKey key used for authentication (i.e., for the MAC tag) + * @param macTag message authentication code (MAC) tag for the authenticated + * data + * @param authenticatedDataStart offset of start of authenticated data in + * stream + * @param encryptedDataStart offset of start of encrypted data in stream + * @param dataEnd offset of the end of both the authenticated and encrypted + * data + * @throws InvalidAlgorithmParameterException + */ + public ContainerEncryptionParams(String encryptionAlgorithm, + AlgorithmParameterSpec encryptionSpec, SecretKey encryptionKey, String macAlgorithm, + AlgorithmParameterSpec macSpec, SecretKey macKey, byte[] macTag, + long authenticatedDataStart, long encryptedDataStart, long dataEnd) + throws InvalidAlgorithmParameterException { + if (TextUtils.isEmpty(encryptionAlgorithm)) { + throw new NullPointerException("algorithm == null"); + } else if (encryptionSpec == null) { + throw new NullPointerException("encryptionSpec == null"); + } else if (encryptionKey == null) { + throw new NullPointerException("encryptionKey == null"); + } + + if (!TextUtils.isEmpty(macAlgorithm)) { + if (macKey == null) { + throw new NullPointerException("macKey == null"); + } + } + + if (!(encryptionSpec instanceof IvParameterSpec)) { + throw new InvalidAlgorithmParameterException( + "Unknown parameter spec class; must be IvParameters"); + } + + mEncryptionAlgorithm = encryptionAlgorithm; + mEncryptionSpec = (IvParameterSpec) encryptionSpec; + mEncryptionKey = encryptionKey; + + mMacAlgorithm = macAlgorithm; + mMacSpec = macSpec; + mMacKey = macKey; + mMacTag = macTag; + + mAuthenticatedDataStart = authenticatedDataStart; + mEncryptedDataStart = encryptedDataStart; + mDataEnd = dataEnd; + } + + public String getEncryptionAlgorithm() { + return mEncryptionAlgorithm; + } + + public AlgorithmParameterSpec getEncryptionSpec() { + return mEncryptionSpec; + } + + public SecretKey getEncryptionKey() { + return mEncryptionKey; + } + + public String getMacAlgorithm() { + return mMacAlgorithm; + } + + public AlgorithmParameterSpec getMacSpec() { + return mMacSpec; + } + + public SecretKey getMacKey() { + return mMacKey; + } + + public byte[] getMacTag() { + return mMacTag; + } + + public long getAuthenticatedDataStart() { + return mAuthenticatedDataStart; + } + + public long getEncryptedDataStart() { + return mEncryptedDataStart; + } + + public long getDataEnd() { + return mDataEnd; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof ContainerEncryptionParams)) { + return false; + } + + final ContainerEncryptionParams other = (ContainerEncryptionParams) o; + + // Primitive comparison + if ((mAuthenticatedDataStart != other.mAuthenticatedDataStart) + || (mEncryptedDataStart != other.mEncryptedDataStart) + || (mDataEnd != other.mDataEnd)) { + return false; + } + + // String comparison + if (!mEncryptionAlgorithm.equals(other.mEncryptionAlgorithm) + || !mMacAlgorithm.equals(other.mMacAlgorithm)) { + return false; + } + + // Object comparison + if (!isSecretKeyEqual(mEncryptionKey, other.mEncryptionKey) + || !isSecretKeyEqual(mMacKey, other.mMacKey)) { + return false; + } + + if (!Arrays.equals(mEncryptionSpec.getIV(), other.mEncryptionSpec.getIV()) + || !Arrays.equals(mMacTag, other.mMacTag) || (mMacSpec != other.mMacSpec)) { + return false; + } + + return true; + } + + private static final boolean isSecretKeyEqual(SecretKey key1, SecretKey key2) { + final String keyFormat = key1.getFormat(); + final String otherKeyFormat = key2.getFormat(); + + if (keyFormat == null) { + if (keyFormat != otherKeyFormat) { + return false; + } + + if (key1.getEncoded() != key2.getEncoded()) { + return false; + } + } else { + if (!keyFormat.equals(key2.getFormat())) { + return false; + } + + if (!Arrays.equals(key1.getEncoded(), key2.getEncoded())) { + return false; + } + } + + return true; + } + + @Override + public int hashCode() { + int hash = 3; + + hash += 5 * mEncryptionAlgorithm.hashCode(); + hash += 7 * Arrays.hashCode(mEncryptionSpec.getIV()); + hash += 11 * mEncryptionKey.hashCode(); + hash += 13 * mMacAlgorithm.hashCode(); + hash += 17 * mMacKey.hashCode(); + hash += 19 * Arrays.hashCode(mMacTag); + hash += 23 * mAuthenticatedDataStart; + hash += 29 * mEncryptedDataStart; + hash += 31 * mDataEnd; + + return hash; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(TO_STRING_PREFIX); + + sb.append("mEncryptionAlgorithm=\""); + sb.append(mEncryptionAlgorithm); + sb.append("\","); + sb.append("mEncryptionSpec="); + sb.append(mEncryptionSpec.toString()); + sb.append("mEncryptionKey="); + sb.append(mEncryptionKey.toString()); + + sb.append("mMacAlgorithm=\""); + sb.append(mMacAlgorithm); + sb.append("\","); + sb.append("mMacSpec="); + sb.append(mMacSpec.toString()); + sb.append("mMacKey="); + sb.append(mMacKey.toString()); + + sb.append(",mAuthenticatedDataStart="); + sb.append(mAuthenticatedDataStart); + sb.append(",mEncryptedDataStart="); + sb.append(mEncryptedDataStart); + sb.append(",mDataEnd="); + sb.append(mDataEnd); + sb.append('}'); + + return sb.toString(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mEncryptionAlgorithm); + dest.writeInt(ENC_PARAMS_IV_PARAMETERS); + dest.writeByteArray(mEncryptionSpec.getIV()); + dest.writeSerializable(mEncryptionKey); + + dest.writeString(mMacAlgorithm); + dest.writeInt(MAC_PARAMS_NONE); + dest.writeByteArray(new byte[0]); + dest.writeSerializable(mMacKey); + + dest.writeByteArray(mMacTag); + + dest.writeLong(mAuthenticatedDataStart); + dest.writeLong(mEncryptedDataStart); + dest.writeLong(mDataEnd); + } + + private ContainerEncryptionParams(Parcel source) throws InvalidAlgorithmParameterException { + mEncryptionAlgorithm = source.readString(); + final int encParamType = source.readInt(); + final byte[] encParamsEncoded = source.createByteArray(); + mEncryptionKey = (SecretKey) source.readSerializable(); + + mMacAlgorithm = source.readString(); + final int macParamType = source.readInt(); + source.createByteArray(); // byte[] macParamsEncoded + mMacKey = (SecretKey) source.readSerializable(); + + mMacTag = source.createByteArray(); + + mAuthenticatedDataStart = source.readLong(); + mEncryptedDataStart = source.readLong(); + mDataEnd = source.readLong(); + + switch (encParamType) { + case ENC_PARAMS_IV_PARAMETERS: + mEncryptionSpec = new IvParameterSpec(encParamsEncoded); + break; + default: + throw new InvalidAlgorithmParameterException("Unknown parameter type " + + encParamType); + } + + switch (macParamType) { + case MAC_PARAMS_NONE: + mMacSpec = null; + break; + default: + throw new InvalidAlgorithmParameterException("Unknown parameter type " + + macParamType); + } + + if (mEncryptionKey == null) { + throw new NullPointerException("encryptionKey == null"); + } + } + + public static final Parcelable.Creator<ContainerEncryptionParams> CREATOR = + new Parcelable.Creator<ContainerEncryptionParams>() { + public ContainerEncryptionParams createFromParcel(Parcel source) { + try { + return new ContainerEncryptionParams(source); + } catch (InvalidAlgorithmParameterException e) { + Slog.e(TAG, "Invalid algorithm parameters specified", e); + return null; + } + } + + public ContainerEncryptionParams[] newArray(int size) { + return new ContainerEncryptionParams[size]; + } + }; +}
\ No newline at end of file diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 9b8454a..70c0c48 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -22,6 +22,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.ContainerEncryptionParams; import android.content.pm.FeatureInfo; import android.content.pm.IPackageInstallObserver; import android.content.pm.IPackageDeleteObserver; @@ -362,7 +363,7 @@ interface IPackageManager { void installPackageWithVerification(in Uri packageURI, in IPackageInstallObserver observer, int flags, in String installerPackageName, in Uri verificationURI, - in ManifestDigest manifestDigest); + in ManifestDigest manifestDigest, in ContainerEncryptionParams encryptionParams); void verifyPendingInstall(int id, int verificationCode); diff --git a/core/java/android/content/pm/LimitedLengthInputStream.java b/core/java/android/content/pm/LimitedLengthInputStream.java new file mode 100644 index 0000000..e787277 --- /dev/null +++ b/core/java/android/content/pm/LimitedLengthInputStream.java @@ -0,0 +1,94 @@ +package android.content.pm; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +/** + * A class that limits the amount of data that is read from an InputStream. When + * the specified length is reached, the stream returns an EOF even if the + * underlying stream still has more data. + * + * @hide + */ +public class LimitedLengthInputStream extends FilterInputStream { + /** + * The end of the stream where we don't want to allow more data to be read. + */ + private final long mEnd; + + /** + * Current offset in the stream. + */ + private long mOffset; + + /** + * @param in underlying stream to wrap + * @param offset offset into stream where data starts + * @param length length of data at offset + * @throws IOException if an error occurred with the underlying stream + */ + public LimitedLengthInputStream(InputStream in, long offset, long length) throws IOException { + super(in); + + if (in == null) { + throw new IOException("in == null"); + } + + if (offset < 0) { + throw new IOException("offset < 0"); + } + + if (length < 0) { + throw new IOException("length < 0"); + } + + if (length > Long.MAX_VALUE - offset) { + throw new IOException("offset + length > Long.MAX_VALUE"); + } + + mEnd = offset + length; + + skip(offset); + mOffset = offset; + } + + @Override + public synchronized int read() throws IOException { + if (mOffset >= mEnd) { + return -1; + } + + mOffset++; + return super.read(); + } + + @Override + public int read(byte[] buffer, int offset, int byteCount) throws IOException { + if (mOffset >= mEnd) { + return -1; + } + + final int arrayLength = buffer.length; + Arrays.checkOffsetAndCount(arrayLength, offset, byteCount); + + if (mOffset > Long.MAX_VALUE - byteCount) { + throw new IOException("offset out of bounds: " + mOffset + " + " + byteCount); + } + + if (mOffset + byteCount > mEnd) { + byteCount = (int) (mEnd - mOffset); + } + + final int numRead = super.read(buffer, offset, byteCount); + mOffset += numRead; + + return numRead; + } + + @Override + public int read(byte[] buffer) throws IOException { + return read(buffer, 0, buffer.length); + } +} diff --git a/core/java/android/content/pm/MacAuthenticatedInputStream.java b/core/java/android/content/pm/MacAuthenticatedInputStream.java new file mode 100644 index 0000000..11f4b94 --- /dev/null +++ b/core/java/android/content/pm/MacAuthenticatedInputStream.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2012 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 java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +import javax.crypto.Mac; + +/** + * An input stream filter that applies a MAC to the data passing through it. At + * the end of the data that should be authenticated, the tag can be calculated. + * After that, the stream should not be used. + * + * @hide + */ +public class MacAuthenticatedInputStream extends FilterInputStream { + private final Mac mMac; + + public MacAuthenticatedInputStream(InputStream in, Mac mac) { + super(in); + + mMac = mac; + } + + public boolean isTagEqual(byte[] tag) { + final byte[] actualTag = mMac.doFinal(); + + if (tag == null || actualTag == null || tag.length != actualTag.length) { + return false; + } + + /* + * Attempt to prevent timing attacks by doing the same amount of work + * whether the first byte matches or not. Do not change this to a loop + * that exits early when a byte does not match. + */ + int value = 0; + for (int i = 0; i < tag.length; i++) { + value |= tag[i] ^ actualTag[i]; + } + + return value == 0; + } + + @Override + public int read() throws IOException { + final int b = super.read(); + if (b >= 0) { + mMac.update((byte) b); + } + return b; + } + + @Override + public int read(byte[] buffer, int offset, int count) throws IOException { + int numRead = super.read(buffer, offset, count); + if (numRead > 0) { + mMac.update(buffer, offset, numRead); + } + return numRead; + } +} diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index c3ce1cf..a48924e 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -28,7 +28,6 @@ import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; import android.net.Uri; -import android.os.Build; import android.os.Environment; import android.util.AndroidException; import android.util.DisplayMetrics; @@ -2199,12 +2198,19 @@ public abstract class PackageManager { * is performing the installation. This identifies which market * the package came from. * @param verificationURI The location of the supplementary verification - * file. This can be a 'file:' or a 'content:' URI. + * file. This can be a 'file:' or a 'content:' URI. May be + * {@code null}. + * @param manifestDigest an object that holds the digest of the package + * which can be used to verify ownership. May be {@code null}. + * @param encryptionParams if the package to be installed is encrypted, + * these parameters describing the encryption and authentication + * used. May be {@code null}. * @hide */ public abstract void installPackageWithVerification(Uri packageURI, IPackageInstallObserver observer, int flags, String installerPackageName, - Uri verificationURI, ManifestDigest manifestDigest); + Uri verificationURI, ManifestDigest manifestDigest, + ContainerEncryptionParams encryptionParams); /** * Allows a package listening to the diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java index 9893133..7d46710 100644 --- a/core/java/android/content/res/AssetFileDescriptor.java +++ b/core/java/android/content/res/AssetFileDescriptor.java @@ -52,6 +52,9 @@ public class AssetFileDescriptor implements Parcelable { */ public AssetFileDescriptor(ParcelFileDescriptor fd, long startOffset, long length) { + if (fd == null) { + throw new IllegalArgumentException("fd must not be null"); + } if (length < 0 && startOffset != 0) { throw new IllegalArgumentException( "startOffset must be 0 when using UNKNOWN_LENGTH"); diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java index dd6692c..fb04817 100644 --- a/core/java/android/database/AbstractCursor.java +++ b/core/java/android/database/AbstractCursor.java @@ -44,14 +44,20 @@ public abstract class AbstractCursor implements CrossProcessCursor { /** * This must be set to the index of the row ID column by any * subclass that wishes to support updates. + * + * @deprecated This field should not be used. */ + @Deprecated protected int mRowIdColumnIndex; /** * If {@link #mRowIdColumnIndex} is not -1 this contains contains the value of * the column at {@link #mRowIdColumnIndex} for the current row this cursor is * pointing at. + * + * @deprecated This field should not be used. */ + @Deprecated protected Long mCurrentRowID; protected boolean mClosed; @@ -62,8 +68,8 @@ public abstract class AbstractCursor implements CrossProcessCursor { private ContentObserver mSelfObserver; private boolean mSelfObserverRegistered; - private DataSetObservable mDataSetObservable = new DataSetObservable(); - private ContentObservable mContentObservable = new ContentObservable(); + private final DataSetObservable mDataSetObservable = new DataSetObservable(); + private final ContentObservable mContentObservable = new ContentObservable(); private Bundle mExtras = Bundle.EMPTY; diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java index 40a54cf..a6af5c2 100644 --- a/core/java/android/database/DatabaseUtils.java +++ b/core/java/android/database/DatabaseUtils.java @@ -21,7 +21,6 @@ import org.apache.commons.codec.binary.Hex; import android.content.ContentValues; import android.content.Context; import android.content.OperationApplicationException; -import android.content.OperationCanceledException; import android.database.sqlite.SQLiteAbortException; import android.database.sqlite.SQLiteConstraintException; import android.database.sqlite.SQLiteDatabase; @@ -31,6 +30,7 @@ import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteFullException; import android.database.sqlite.SQLiteProgram; import android.database.sqlite.SQLiteStatement; +import android.os.OperationCanceledException; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.text.TextUtils; diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java index acdc488..6f7c1f3 100644 --- a/core/java/android/database/sqlite/SQLiteConnection.java +++ b/core/java/android/database/sqlite/SQLiteConnection.java @@ -19,12 +19,12 @@ package android.database.sqlite; import dalvik.system.BlockGuard; import dalvik.system.CloseGuard; -import android.content.CancellationSignal; -import android.content.OperationCanceledException; import android.database.Cursor; import android.database.CursorWindow; import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDebug.DbStats; +import android.os.CancellationSignal; +import android.os.OperationCanceledException; import android.os.ParcelFileDescriptor; import android.util.Log; import android.util.LruCache; diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java index a175662..3a1714c 100644 --- a/core/java/android/database/sqlite/SQLiteConnectionPool.java +++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java @@ -18,9 +18,9 @@ package android.database.sqlite; import dalvik.system.CloseGuard; -import android.content.CancellationSignal; -import android.content.OperationCanceledException; import android.database.sqlite.SQLiteDebug.DbStats; +import android.os.CancellationSignal; +import android.os.OperationCanceledException; import android.os.SystemClock; import android.util.Log; import android.util.PrefixPrinter; diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 7bd0c8d..e2d44f2 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -16,16 +16,16 @@ package android.database.sqlite; -import android.content.CancellationSignal; import android.content.ContentValues; -import android.content.OperationCanceledException; import android.database.Cursor; import android.database.DatabaseErrorHandler; import android.database.DatabaseUtils; import android.database.DefaultDatabaseErrorHandler; import android.database.SQLException; import android.database.sqlite.SQLiteDebug.DbStats; +import android.os.CancellationSignal; import android.os.Looper; +import android.os.OperationCanceledException; import android.text.TextUtils; import android.util.EventLog; import android.util.Log; diff --git a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java index 294edc4..797430a 100644 --- a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java +++ b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java @@ -16,9 +16,9 @@ package android.database.sqlite; -import android.content.CancellationSignal; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase.CursorFactory; +import android.os.CancellationSignal; /** * A cursor driver that uses the given query directly. diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java index e9b06c6..26e8c31 100644 --- a/core/java/android/database/sqlite/SQLiteProgram.java +++ b/core/java/android/database/sqlite/SQLiteProgram.java @@ -16,8 +16,8 @@ package android.database.sqlite; -import android.content.CancellationSignal; import android.database.DatabaseUtils; +import android.os.CancellationSignal; import java.util.Arrays; diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java index 30e77b5..62bcc20 100644 --- a/core/java/android/database/sqlite/SQLiteQuery.java +++ b/core/java/android/database/sqlite/SQLiteQuery.java @@ -16,9 +16,9 @@ package android.database.sqlite; -import android.content.CancellationSignal; -import android.content.OperationCanceledException; import android.database.CursorWindow; +import android.os.CancellationSignal; +import android.os.OperationCanceledException; import android.util.Log; /** diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java index 6f84b5e..91884ab 100644 --- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java +++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java @@ -16,10 +16,10 @@ package android.database.sqlite; -import android.content.CancellationSignal; -import android.content.OperationCanceledException; import android.database.Cursor; import android.database.DatabaseUtils; +import android.os.CancellationSignal; +import android.os.OperationCanceledException; import android.provider.BaseColumns; import android.text.TextUtils; import android.util.Log; diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java index 9410243..beb5b3a 100644 --- a/core/java/android/database/sqlite/SQLiteSession.java +++ b/core/java/android/database/sqlite/SQLiteSession.java @@ -16,10 +16,10 @@ package android.database.sqlite; -import android.content.CancellationSignal; -import android.content.OperationCanceledException; import android.database.CursorWindow; import android.database.DatabaseUtils; +import android.os.CancellationSignal; +import android.os.OperationCanceledException; import android.os.ParcelFileDescriptor; /** diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 332f40a..33dea6c 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -402,7 +402,7 @@ public class InputMethodService extends AbstractInputMethodService { mShowInputFlags = 0; mShowInputRequested = false; mShowInputForced = false; - hideWindow(); + doHideWindow(); if (resultReceiver != null) { resultReceiver.send(wasVis != isInputViewShown() ? InputMethodManager.RESULT_HIDDEN @@ -737,7 +737,7 @@ public class InputMethodService extends AbstractInputMethodService { onDisplayCompletions(completions); } } else { - hideWindow(); + doHideWindow(); } } else if (mCandidatesVisibility == View.VISIBLE) { // If the candidates are currently visible, make sure the @@ -745,7 +745,7 @@ public class InputMethodService extends AbstractInputMethodService { showWindow(false); } else { // Otherwise hide the window. - hideWindow(); + doHideWindow(); } // If user uses hard keyboard, IME button should always be shown. boolean showing = onEvaluateInputViewShown(); @@ -1096,7 +1096,7 @@ public class InputMethodService extends AbstractInputMethodService { if (shown) { showWindow(false); } else { - hideWindow(); + doHideWindow(); } } } @@ -1449,9 +1449,13 @@ public class InputMethodService extends AbstractInputMethodService { mCandidatesViewStarted = false; } + private void doHideWindow() { + mImm.setImeWindowStatus(mToken, 0, mBackDisposition); + hideWindow(); + } + public void hideWindow() { finishViews(); - mImm.setImeWindowStatus(mToken, 0, mBackDisposition); if (mWindowVisible) { mWindow.hide(); mWindowVisible = false; @@ -1703,7 +1707,7 @@ public class InputMethodService extends AbstractInputMethodService { // If we have the window visible for some other reason -- // most likely to show candidates -- then just get rid // of it. This really shouldn't happen, but just in case... - if (doIt) hideWindow(); + if (doIt) doHideWindow(); } return true; } diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index defe7aa..3b990e3 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -1718,9 +1718,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { } /** - * Return a normalized representation of this Uri. - * - * <p>A normalized Uri has a lowercase scheme component. + * Return an equivalent URI with a lowercase scheme component. * This aligns the Uri with Android best practices for * intent filtering. * @@ -1740,7 +1738,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { * @see {@link android.content.Intent#setData} * @see {@link #setNormalizedData} */ - public Uri normalize() { + public Uri normalizeScheme() { String scheme = getScheme(); if (scheme == null) return this; // give up String lowerScheme = scheme.toLowerCase(Locale.US); diff --git a/core/java/android/net/nsd/DnsSdTxtRecord.java b/core/java/android/net/nsd/DnsSdTxtRecord.java index ccb9a91..2f20d44 100644 --- a/core/java/android/net/nsd/DnsSdTxtRecord.java +++ b/core/java/android/net/nsd/DnsSdTxtRecord.java @@ -36,6 +36,7 @@ import java.util.Arrays; * * The DnsSdTxtRecord object stores the entire TXT data as a single byte array, traversing it * as need be to implement its various methods. + * @hide * */ public class DnsSdTxtRecord implements Parcelable { diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java index 77e97e1..08ba728 100644 --- a/core/java/android/net/nsd/NsdManager.java +++ b/core/java/android/net/nsd/NsdManager.java @@ -22,12 +22,16 @@ import android.content.Context; import android.os.Binder; import android.os.IBinder; import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.Messenger; import android.text.TextUtils; import android.util.Log; +import android.util.SparseArray; + +import java.util.concurrent.CountDownLatch; import com.android.internal.util.AsyncChannel; import com.android.internal.util.Protocol; @@ -39,88 +43,73 @@ import com.android.internal.util.Protocol; * B. Another example use case is an application discovering printers on the network. * * <p> The API currently supports DNS based service discovery and discovery is currently - * limited to a local network over Multicast DNS. In future, it will be extended to - * support wide area discovery and other service discovery mechanisms. - * DNS service discovery is described at http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt + * limited to a local network over Multicast DNS. DNS service discovery is described at + * http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt * * <p> The API is asynchronous and responses to requests from an application are on listener - * callbacks provided by the application. The application must invoke {@link #initialize} before - * doing any other operation. + * callbacks on a seperate thread. * * <p> There are three main operations the API supports - registration, discovery and resolution. * <pre> * Application start * | - * | <---------------------------------------------- - * initialize() | - * | | - * | Wait until channel connects | - * | before doing any operation | - * | | - * onChannelConnected() __________ | - * | | | - * | | | - * | onServiceRegistered() | | - * Register any local services / | | - * to be advertised with \ | | If application needs to - * registerService() onFailure() | | do any further operations - * | | | again, it needs to - * | | | initialize() connection - * discoverServices() | | to framework again - * | | | - * Maintain a list to track | | - * discovered services | | - * | | | - * |---------> |-> onChannelDisconnected() - * | | | - * | onServiceFound() | - * | | | - * | add service to list | - * | | | - * |<---------- | - * | | - * |---------> | - * | | | - * | onServiceLost() | - * | | | - * | remove service from list | - * | | | - * |<---------- | - * | | - * | | - * | Connect to a service | - * | from list ? | - * | | - * resolveService() | - * | | - * onServiceResolved() | - * | | - * Establish connection to service | - * with the host and port information | - * | | - * | ___________| - * deinitialize() - * when done with all operations - * or before quit + * | + * | onServiceRegistered() + * Register any local services / + * to be advertised with \ + * registerService() onRegistrationFailed() + * | + * | + * discoverServices() + * | + * Maintain a list to track + * discovered services + * | + * |---------> + * | | + * | onServiceFound() + * | | + * | add service to list + * | | + * |<---------- + * | + * |---------> + * | | + * | onServiceLost() + * | | + * | remove service from list + * | | + * |<---------- + * | + * | + * | Connect to a service + * | from list ? + * | + * resolveService() + * | + * onServiceResolved() + * | + * Establish connection to service + * with the host and port information * * </pre> * An application that needs to advertise itself over a network for other applications to * discover it can do so with a call to {@link #registerService}. If Example is a http based * application that can provide HTML data to peer services, it can register a name "Example" * with service type "_http._tcp". A successful registration is notified with a callback to - * {@link DnsSdRegisterListener#onServiceRegistered} and a failure to register is notified - * over {@link DnsSdRegisterListener#onFailure} + * {@link RegistrationListener#onServiceRegistered} and a failure to register is notified + * over {@link RegistrationListener#onRegistrationFailed} * * <p> A peer application looking for http services can initiate a discovery for "_http._tcp" * with a call to {@link #discoverServices}. A service found is notified with a callback - * to {@link DnsSdDiscoveryListener#onServiceFound} and a service lost is notified on - * {@link DnsSdDiscoveryListener#onServiceLost}. + * to {@link DiscoveryListener#onServiceFound} and a service lost is notified on + * {@link DiscoveryListener#onServiceLost}. * * <p> Once the peer application discovers the "Example" http srevice, and needs to receive data * from the "Example" application, it can initiate a resolve with {@link #resolveService} to * resolve the host and port details for the purpose of establishing a connection. A successful - * resolve is notified on {@link DnsSdResolveListener#onServiceResolved} and a failure is notified - * on {@link DnsSdResolveListener#onFailure}. + * resolve is notified on {@link ResolveListener#onServiceResolved} and a failure is notified + * on {@link ResolveListener#onResolveFailed}. * * Applications can reserve for a service type at * http://www.iana.org/form/ports-service. Existing services can be found at @@ -129,9 +118,9 @@ import com.android.internal.util.Protocol; * Get an instance of this class by calling {@link android.content.Context#getSystemService(String) * Context.getSystemService(Context.NSD_SERVICE)}. * - * {@see DnsSdServiceInfo} + * {@see NsdServiceInfo} */ -public class NsdManager { +public final class NsdManager { private static final String TAG = "NsdManager"; INsdManager mService; @@ -204,13 +193,6 @@ public class NsdManager { public static final int UNREGISTER_SERVICE_SUCCEEDED = BASE + 14; /** @hide */ - public static final int UPDATE_SERVICE = BASE + 15; - /** @hide */ - public static final int UPDATE_SERVICE_FAILED = BASE + 16; - /** @hide */ - public static final int UPDATE_SERVICE_SUCCEEDED = BASE + 17; - - /** @hide */ public static final int RESOLVE_SERVICE = BASE + 18; /** @hide */ public static final int RESOLVE_SERVICE_FAILED = BASE + 19; @@ -218,17 +200,27 @@ public class NsdManager { public static final int RESOLVE_SERVICE_SUCCEEDED = BASE + 20; /** @hide */ - public static final int STOP_RESOLVE = BASE + 21; - /** @hide */ - public static final int STOP_RESOLVE_FAILED = BASE + 22; - /** @hide */ - public static final int STOP_RESOLVE_SUCCEEDED = BASE + 23; - - /** @hide */ public static final int ENABLE = BASE + 24; /** @hide */ public static final int DISABLE = BASE + 25; + /** @hide */ + public static final int NATIVE_DAEMON_EVENT = BASE + 26; + + /** Dns based service discovery protocol */ + public static final int PROTOCOL_DNS_SD = 0x0001; + + private Context mContext; + + private static final int INVALID_LISTENER_KEY = 0; + private int mListenerKey = 1; + private final SparseArray mListenerMap = new SparseArray(); + private final SparseArray<NsdServiceInfo> mServiceMap = new SparseArray<NsdServiceInfo>(); + private final Object mMapLock = new Object(); + + private final AsyncChannel mAsyncChannel = new AsyncChannel(); + private ServiceHandler mHandler; + private final CountDownLatch mConnected = new CountDownLatch(1); /** * Create a new Nsd instance. Applications use @@ -238,271 +230,213 @@ public class NsdManager { * @hide - hide this because it takes in a parameter of type INsdManager, which * is a system private class. */ - public NsdManager(INsdManager service) { + public NsdManager(Context context, INsdManager service) { mService = service; + mContext = context; + init(); } /** - * Passed with onFailure() calls. + * Failures are passed with {@link RegistrationListener#onRegistrationFailed}, + * {@link RegistrationListener#onUnregistrationFailed}, + * {@link DiscoveryListener#onStartDiscoveryFailed}, + * {@link DiscoveryListener#onStopDiscoveryFailed} or {@link ResolveListener#onResolveFailed}. + * * Indicates that the operation failed due to an internal error. */ - public static final int ERROR = 0; + public static final int FAILURE_INTERNAL_ERROR = 0; /** - * Passed with onFailure() calls. - * Indicates that the operation failed because service discovery - * is unsupported on the device. - */ - public static final int UNSUPPORTED = 1; - - /** - * Passed with onFailure() calls. - * Indicates that the operation failed because the framework is - * busy and unable to service the request. - */ - public static final int BUSY = 2; - - /** - * Passed with onFailure() calls. * Indicates that the operation failed because it is already active. */ - public static final int ALREADY_ACTIVE = 3; + public static final int FAILURE_ALREADY_ACTIVE = 3; /** - * Passed with onFailure() calls. - * Indicates that the operation failed because maximum limit on - * service registrations has reached. + * Indicates that the operation failed because the maximum outstanding + * requests from the applications have reached. */ - public static final int MAX_REGS_REACHED = 4; - - /** Interface for callback invocation when framework channel is connected or lost */ - public interface ChannelListener { - /** - * The channel to the framework is connected. - * Application can initiate calls into the framework using the channel instance passed. - */ - public void onChannelConnected(Channel c); - /** - * The channel to the framework has been disconnected. - * Application could try re-initializing using {@link #initialize} - */ - public void onChannelDisconnected(); - } + public static final int FAILURE_MAX_LIMIT = 4; - /** Generic interface for callback invocation for a success or failure */ - public interface ActionListener { + /** Interface for callback invocation for service discovery */ + public interface DiscoveryListener { - public void onFailure(int errorCode); + public void onStartDiscoveryFailed(String serviceType, int errorCode); - public void onSuccess(); - } + public void onStopDiscoveryFailed(String serviceType, int errorCode); - /** Interface for callback invocation for service discovery */ - public interface DnsSdDiscoveryListener { + public void onDiscoveryStarted(String serviceType); - public void onFailure(int errorCode); + public void onDiscoveryStopped(String serviceType); - public void onStarted(String serviceType); + public void onServiceFound(NsdServiceInfo serviceInfo); - public void onServiceFound(DnsSdServiceInfo serviceInfo); - - public void onServiceLost(DnsSdServiceInfo serviceInfo); + public void onServiceLost(NsdServiceInfo serviceInfo); } /** Interface for callback invocation for service registration */ - public interface DnsSdRegisterListener { - - public void onFailure(int errorCode); + public interface RegistrationListener { - public void onServiceRegistered(int registeredId, DnsSdServiceInfo serviceInfo); - } + public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode); - /** @hide */ - public interface DnsSdUpdateRegistrationListener { + public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode); - public void onFailure(int errorCode); + public void onServiceRegistered(NsdServiceInfo serviceInfo); - public void onServiceUpdated(int registeredId, DnsSdTxtRecord txtRecord); + public void onServiceUnregistered(NsdServiceInfo serviceInfo); } /** Interface for callback invocation for service resolution */ - public interface DnsSdResolveListener { + public interface ResolveListener { - public void onFailure(int errorCode); + public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode); - public void onServiceResolved(DnsSdServiceInfo serviceInfo); + public void onServiceResolved(NsdServiceInfo serviceInfo); } - /** - * A channel that connects the application to the NetworkService framework. - * Most service operations require a Channel as an argument. An instance of Channel is obtained - * by doing a call on {@link #initialize} - */ - public static class Channel { - Channel(Looper looper, ChannelListener l) { - mAsyncChannel = new AsyncChannel(); - mHandler = new ServiceHandler(looper); - mChannelListener = l; + private class ServiceHandler extends Handler { + ServiceHandler(Looper looper) { + super(looper); } - private ChannelListener mChannelListener; - private DnsSdDiscoveryListener mDnsSdDiscoveryListener; - private ActionListener mDnsSdStopDiscoveryListener; - private DnsSdRegisterListener mDnsSdRegisterListener; - private ActionListener mDnsSdUnregisterListener; - private DnsSdUpdateRegistrationListener mDnsSdUpdateListener; - private DnsSdResolveListener mDnsSdResolveListener; - private ActionListener mDnsSdStopResolveListener; - - private AsyncChannel mAsyncChannel; - private ServiceHandler mHandler; - class ServiceHandler extends Handler { - ServiceHandler(Looper looper) { - super(looper); - } - @Override - public void handleMessage(Message message) { - switch (message.what) { - case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: - mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); - break; - case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED: - if (mChannelListener != null) { - mChannelListener.onChannelConnected(Channel.this); - } - break; - case AsyncChannel.CMD_CHANNEL_DISCONNECTED: - if (mChannelListener != null) { - mChannelListener.onChannelDisconnected(); - mChannelListener = null; - } - break; - case DISCOVER_SERVICES_STARTED: - if (mDnsSdDiscoveryListener != null) { - mDnsSdDiscoveryListener.onStarted((String) message.obj); - } - break; - case DISCOVER_SERVICES_FAILED: - if (mDnsSdDiscoveryListener != null) { - mDnsSdDiscoveryListener.onFailure(message.arg1); - } - break; - case SERVICE_FOUND: - if (mDnsSdDiscoveryListener != null) { - mDnsSdDiscoveryListener.onServiceFound( - (DnsSdServiceInfo) message.obj); - } - break; - case SERVICE_LOST: - if (mDnsSdDiscoveryListener != null) { - mDnsSdDiscoveryListener.onServiceLost( - (DnsSdServiceInfo) message.obj); - } - break; - case STOP_DISCOVERY_FAILED: - if (mDnsSdStopDiscoveryListener != null) { - mDnsSdStopDiscoveryListener.onFailure(message.arg1); - } - break; - case STOP_DISCOVERY_SUCCEEDED: - if (mDnsSdStopDiscoveryListener != null) { - mDnsSdStopDiscoveryListener.onSuccess(); - } - break; - case REGISTER_SERVICE_FAILED: - if (mDnsSdRegisterListener != null) { - mDnsSdRegisterListener.onFailure(message.arg1); - } - break; - case REGISTER_SERVICE_SUCCEEDED: - if (mDnsSdRegisterListener != null) { - mDnsSdRegisterListener.onServiceRegistered(message.arg1, - (DnsSdServiceInfo) message.obj); - } - break; - case UNREGISTER_SERVICE_FAILED: - if (mDnsSdUnregisterListener != null) { - mDnsSdUnregisterListener.onFailure(message.arg1); - } - break; - case UNREGISTER_SERVICE_SUCCEEDED: - if (mDnsSdUnregisterListener != null) { - mDnsSdUnregisterListener.onSuccess(); - } - break; - case UPDATE_SERVICE_FAILED: - if (mDnsSdUpdateListener != null) { - mDnsSdUpdateListener.onFailure(message.arg1); - } - break; - case UPDATE_SERVICE_SUCCEEDED: - if (mDnsSdUpdateListener != null) { - mDnsSdUpdateListener.onServiceUpdated(message.arg1, - (DnsSdTxtRecord) message.obj); - } - break; - case RESOLVE_SERVICE_FAILED: - if (mDnsSdResolveListener != null) { - mDnsSdResolveListener.onFailure(message.arg1); - } - break; - case RESOLVE_SERVICE_SUCCEEDED: - if (mDnsSdResolveListener != null) { - mDnsSdResolveListener.onServiceResolved( - (DnsSdServiceInfo) message.obj); - } - break; - case STOP_RESOLVE_FAILED: - if (mDnsSdStopResolveListener!= null) { - mDnsSdStopResolveListener.onFailure(message.arg1); - } - break; - case STOP_RESOLVE_SUCCEEDED: - if (mDnsSdStopResolveListener != null) { - mDnsSdStopResolveListener.onSuccess(); - } - break; - default: - Log.d(TAG, "Ignored " + message); - break; - } + @Override + public void handleMessage(Message message) { + Object listener = getListener(message.arg2); + boolean listenerRemove = true; + switch (message.what) { + case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: + mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); + mConnected.countDown(); + break; + case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED: + // Ignore + break; + case AsyncChannel.CMD_CHANNEL_DISCONNECTED: + Log.e(TAG, "Channel lost"); + break; + case DISCOVER_SERVICES_STARTED: + String s = ((NsdServiceInfo) message.obj).getServiceType(); + ((DiscoveryListener) listener).onDiscoveryStarted(s); + // Keep listener until stop discovery + listenerRemove = false; + break; + case DISCOVER_SERVICES_FAILED: + ((DiscoveryListener) listener).onStartDiscoveryFailed( + getNsdService(message.arg2).getServiceType(), message.arg1); + break; + case SERVICE_FOUND: + ((DiscoveryListener) listener).onServiceFound((NsdServiceInfo) message.obj); + // Keep listener until stop discovery + listenerRemove = false; + break; + case SERVICE_LOST: + ((DiscoveryListener) listener).onServiceLost((NsdServiceInfo) message.obj); + // Keep listener until stop discovery + listenerRemove = false; + break; + case STOP_DISCOVERY_FAILED: + ((DiscoveryListener) listener).onStopDiscoveryFailed( + getNsdService(message.arg2).getServiceType(), message.arg1); + break; + case STOP_DISCOVERY_SUCCEEDED: + ((DiscoveryListener) listener).onDiscoveryStopped( + getNsdService(message.arg2).getServiceType()); + break; + case REGISTER_SERVICE_FAILED: + ((RegistrationListener) listener).onRegistrationFailed( + getNsdService(message.arg2), message.arg1); + break; + case REGISTER_SERVICE_SUCCEEDED: + ((RegistrationListener) listener).onServiceRegistered( + (NsdServiceInfo) message.obj); + // Keep listener until unregister + listenerRemove = false; + break; + case UNREGISTER_SERVICE_FAILED: + ((RegistrationListener) listener).onUnregistrationFailed( + getNsdService(message.arg2), message.arg1); + break; + case UNREGISTER_SERVICE_SUCCEEDED: + ((RegistrationListener) listener).onServiceUnregistered( + getNsdService(message.arg2)); + break; + case RESOLVE_SERVICE_FAILED: + ((ResolveListener) listener).onResolveFailed( + getNsdService(message.arg2), message.arg1); + break; + case RESOLVE_SERVICE_SUCCEEDED: + ((ResolveListener) listener).onServiceResolved((NsdServiceInfo) message.obj); + break; + default: + Log.d(TAG, "Ignored " + message); + break; + } + if (listenerRemove) { + removeListener(message.arg2); } } - } + } - private static void checkChannel(Channel c) { - if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + private int putListener(Object listener, NsdServiceInfo s) { + if (listener == null) return INVALID_LISTENER_KEY; + int key; + synchronized (mMapLock) { + do { + key = mListenerKey++; + } while (key == INVALID_LISTENER_KEY); + mListenerMap.put(key, listener); + mServiceMap.put(key, s); + } + return key; } - /** - * Registers the application with the service discovery framework. This function - * must be the first to be called before any other operations are performed. No service - * discovery operations must be performed until the ChannelListener callback notifies - * that the channel is connected - * - * @param srcContext is the context of the source - * @param srcLooper is the Looper on which the callbacks are receivied - * @param listener for callback at loss of framework communication. Cannot be null. - */ - public void initialize(Context srcContext, Looper srcLooper, ChannelListener listener) { - Messenger messenger = getMessenger(); - if (messenger == null) throw new RuntimeException("Failed to initialize"); - if (listener == null) throw new IllegalArgumentException("ChannelListener cannot be null"); + private Object getListener(int key) { + if (key == INVALID_LISTENER_KEY) return null; + synchronized (mMapLock) { + return mListenerMap.get(key); + } + } + + private NsdServiceInfo getNsdService(int key) { + synchronized (mMapLock) { + return mServiceMap.get(key); + } + } + + private void removeListener(int key) { + if (key == INVALID_LISTENER_KEY) return; + synchronized (mMapLock) { + mListenerMap.remove(key); + mServiceMap.remove(key); + } + } - Channel c = new Channel(srcLooper, listener); - c.mAsyncChannel.connect(srcContext, c.mHandler, messenger); + private int getListenerKey(Object listener) { + synchronized (mMapLock) { + int valueIndex = mListenerMap.indexOfValue(listener); + if (valueIndex != -1) { + return mListenerMap.keyAt(valueIndex); + } + } + return INVALID_LISTENER_KEY; } + /** - * Disconnects application from service discovery framework. No further operations - * will succeed until a {@link #initialize} is called again. - * - * @param c channel initialized with {@link #initialize} + * Initialize AsyncChannel */ - public void deinitialize(Channel c) { - checkChannel(c); - c.mAsyncChannel.disconnect(); + private void init() { + final Messenger messenger = getMessenger(); + if (messenger == null) throw new RuntimeException("Failed to initialize"); + HandlerThread t = new HandlerThread("NsdManager"); + t.start(); + mHandler = new ServiceHandler(t.getLooper()); + mAsyncChannel.connect(mContext, mHandler, messenger); + try { + mConnected.await(); + } catch (InterruptedException e) { + Log.e(TAG, "interrupted wait at init"); + } } /** @@ -510,45 +444,51 @@ public class NsdManager { * * <p> The function call immediately returns after sending a request to register service * to the framework. The application is notified of a success to initiate - * discovery through the callback {@link DnsSdRegisterListener#onServiceRegistered} or a failure - * through {@link DnsSdRegisterListener#onFailure}. + * discovery through the callback {@link RegistrationListener#onServiceRegistered} or a failure + * through {@link RegistrationListener#onRegistrationFailed}. * - * @param c is the channel created at {@link #initialize} - * @param serviceType The service type being advertised. - * @param port on which the service is listenering for incoming connections - * @param listener for success or failure callback. Can be null. + * @param serviceInfo The service being registered + * @param protocolType The service discovery protocol + * @param listener The listener notifies of a successful registration and is used to + * unregister this service through a call on {@link #unregisterService}. Cannot be null. */ - public void registerService(Channel c, String serviceName, String serviceType, int port, - DnsSdRegisterListener listener) { - checkChannel(c); - if (TextUtils.isEmpty(serviceName) || TextUtils.isEmpty(serviceType)) { + public void registerService(NsdServiceInfo serviceInfo, int protocolType, + RegistrationListener listener) { + if (TextUtils.isEmpty(serviceInfo.getServiceName()) || + TextUtils.isEmpty(serviceInfo.getServiceType())) { throw new IllegalArgumentException("Service name or type cannot be empty"); } - if (port <= 0) { + if (serviceInfo.getPort() <= 0) { throw new IllegalArgumentException("Invalid port number"); } - DnsSdServiceInfo serviceInfo = new DnsSdServiceInfo(serviceName, serviceType, null); - serviceInfo.setPort(port); - c.mDnsSdRegisterListener = listener; - c.mAsyncChannel.sendMessage(REGISTER_SERVICE, serviceInfo); + if (listener == null) { + throw new IllegalArgumentException("listener cannot be null"); + } + if (protocolType != PROTOCOL_DNS_SD) { + throw new IllegalArgumentException("Unsupported protocol"); + } + mAsyncChannel.sendMessage(REGISTER_SERVICE, 0, putListener(listener, serviceInfo), + serviceInfo); } /** - * Unregister a service registered through {@link #registerService} - * @param c is the channel created at {@link #initialize} - * @param registeredId is obtained at {@link DnsSdRegisterListener#onServiceRegistered} - * @param listener provides callbacks for success or failure. Can be null. + * Unregister a service registered through {@link #registerService}. A successful + * unregister is notified to the application with a call to + * {@link RegistrationListener#onServiceUnregistered}. + * + * @param listener This should be the listener object that was passed to + * {@link #registerService}. It identifies the service that should be unregistered + * and notifies of a successful unregistration. */ - public void unregisterService(Channel c, int registeredId, ActionListener listener) { - checkChannel(c); - c.mDnsSdUnregisterListener = listener; - c.mAsyncChannel.sendMessage(UNREGISTER_SERVICE, registeredId); - } - - /** @hide */ - public void updateService(Channel c, int registeredId, DnsSdTxtRecord txtRecord) { - checkChannel(c); - c.mAsyncChannel.sendMessage(UPDATE_SERVICE, registeredId, 0, txtRecord); + public void unregisterService(RegistrationListener listener) { + int id = getListenerKey(listener); + if (id == INVALID_LISTENER_KEY) { + throw new IllegalArgumentException("listener not registered"); + } + if (listener == null) { + throw new IllegalArgumentException("listener cannot be null"); + } + mAsyncChannel.sendMessage(UNREGISTER_SERVICE, 0, id); } /** @@ -558,51 +498,61 @@ public class NsdManager { * * <p> The function call immediately returns after sending a request to start service * discovery to the framework. The application is notified of a success to initiate - * discovery through the callback {@link DnsSdDiscoveryListener#onStarted} or a failure - * through {@link DnsSdDiscoveryListener#onFailure}. + * discovery through the callback {@link DiscoveryListener#onDiscoveryStarted} or a failure + * through {@link DiscoveryListener#onStartDiscoveryFailed}. * * <p> Upon successful start, application is notified when a service is found with - * {@link DnsSdDiscoveryListener#onServiceFound} or when a service is lost with - * {@link DnsSdDiscoveryListener#onServiceLost}. + * {@link DiscoveryListener#onServiceFound} or when a service is lost with + * {@link DiscoveryListener#onServiceLost}. * * <p> Upon failure to start, service discovery is not active and application does * not need to invoke {@link #stopServiceDiscovery} * - * @param c is the channel created at {@link #initialize} * @param serviceType The service type being discovered. Examples include "_http._tcp" for * http services or "_ipp._tcp" for printers - * @param listener provides callbacks when service is found or lost. Cannot be null. + * @param protocolType The service discovery protocol + * @param listener The listener notifies of a successful discovery and is used + * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}. + * Cannot be null. */ - public void discoverServices(Channel c, String serviceType, DnsSdDiscoveryListener listener) { - checkChannel(c); + public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) { if (listener == null) { - throw new IllegalStateException("Discovery listener needs to be set first"); + throw new IllegalArgumentException("listener cannot be null"); } if (TextUtils.isEmpty(serviceType)) { - throw new IllegalStateException("Service type cannot be empty"); + throw new IllegalArgumentException("Service type cannot be empty"); } - DnsSdServiceInfo s = new DnsSdServiceInfo(); + + if (protocolType != PROTOCOL_DNS_SD) { + throw new IllegalArgumentException("Unsupported protocol"); + } + + NsdServiceInfo s = new NsdServiceInfo(); s.setServiceType(serviceType); - c.mDnsSdDiscoveryListener = listener; - c.mAsyncChannel.sendMessage(DISCOVER_SERVICES, s); + mAsyncChannel.sendMessage(DISCOVER_SERVICES, 0, putListener(listener, s), s); } /** * Stop service discovery initiated with {@link #discoverServices}. An active service - * discovery is notified to the application with {@link DnsSdDiscoveryListener#onStarted} - * and it stays active until the application invokes a stop service discovery. + * discovery is notified to the application with {@link DiscoveryListener#onDiscoveryStarted} + * and it stays active until the application invokes a stop service discovery. A successful + * stop is notified to with a call to {@link DiscoveryListener#onDiscoveryStopped}. * - * <p> Upon failure to start service discovery notified through - * {@link DnsSdDiscoveryListener#onFailure} service discovery is not active and - * application does not need to stop it. + * <p> Upon failure to stop service discovery, application is notified through + * {@link DiscoveryListener#onStopDiscoveryFailed}. * - * @param c is the channel created at {@link #initialize} - * @param listener notifies success or failure. Can be null. + * @param listener This should be the listener object that was passed to {@link #discoverServices}. + * It identifies the discovery that should be stopped and notifies of a successful stop. */ - public void stopServiceDiscovery(Channel c, ActionListener listener) { - checkChannel(c); - c.mDnsSdStopDiscoveryListener = listener; - c.mAsyncChannel.sendMessage(STOP_DISCOVERY); + public void stopServiceDiscovery(DiscoveryListener listener) { + int id = getListenerKey(listener); + if (id == INVALID_LISTENER_KEY) { + throw new IllegalArgumentException("service discovery not active on listener"); + } + if (listener == null) { + throw new IllegalArgumentException("listener cannot be null"); + } + mAsyncChannel.sendMessage(STOP_DISCOVERY, 0, id); } /** @@ -610,30 +560,19 @@ public class NsdManager { * establishing a connection to fetch the IP and port details on which to setup * the connection. * - * @param c is the channel created at {@link #initialize} - * @param serviceName of the the service - * @param serviceType of the service + * @param serviceInfo service to be resolved * @param listener to receive callback upon success or failure. Cannot be null. */ - public void resolveService(Channel c, String serviceName, String serviceType, - DnsSdResolveListener listener) { - checkChannel(c); - if (TextUtils.isEmpty(serviceName) || TextUtils.isEmpty(serviceType)) { + public void resolveService(NsdServiceInfo serviceInfo, ResolveListener listener) { + if (TextUtils.isEmpty(serviceInfo.getServiceName()) || + TextUtils.isEmpty(serviceInfo.getServiceType())) { throw new IllegalArgumentException("Service name or type cannot be empty"); } - if (listener == null) throw new - IllegalStateException("Resolve listener cannot be null"); - c.mDnsSdResolveListener = listener; - DnsSdServiceInfo serviceInfo = new DnsSdServiceInfo(serviceName, serviceType, null); - c.mAsyncChannel.sendMessage(RESOLVE_SERVICE, serviceInfo); - } - - /** @hide */ - public void stopServiceResolve(Channel c) { - checkChannel(c); - if (c.mDnsSdResolveListener == null) throw new - IllegalStateException("Resolve listener needs to be set first"); - c.mAsyncChannel.sendMessage(STOP_RESOLVE); + if (listener == null) { + throw new IllegalArgumentException("listener cannot be null"); + } + mAsyncChannel.sendMessage(RESOLVE_SERVICE, 0, putListener(listener, serviceInfo), + serviceInfo); } /** Internal use only @hide */ diff --git a/core/java/android/net/nsd/DnsSdServiceInfo.java b/core/java/android/net/nsd/NsdServiceInfo.java index 66abd3a..205a21d 100644 --- a/core/java/android/net/nsd/DnsSdServiceInfo.java +++ b/core/java/android/net/nsd/NsdServiceInfo.java @@ -25,7 +25,7 @@ import java.net.InetAddress; * A class representing service information for network service discovery * {@see NsdManager} */ -public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable { +public final class NsdServiceInfo implements Parcelable { private String mServiceName; @@ -37,36 +37,32 @@ public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable { private int mPort; - public DnsSdServiceInfo() { + public NsdServiceInfo() { } /** @hide */ - public DnsSdServiceInfo(String sn, String rt, DnsSdTxtRecord tr) { + public NsdServiceInfo(String sn, String rt, DnsSdTxtRecord tr) { mServiceName = sn; mServiceType = rt; mTxtRecord = tr; } /** Get the service name */ - @Override public String getServiceName() { return mServiceName; } /** Set the service name */ - @Override public void setServiceName(String s) { mServiceName = s; } /** Get the service type */ - @Override public String getServiceType() { return mServiceType; } /** Set the service type */ - @Override public void setServiceType(String s) { mServiceType = s; } @@ -132,10 +128,10 @@ public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable { } /** Implement the Parcelable interface */ - public static final Creator<DnsSdServiceInfo> CREATOR = - new Creator<DnsSdServiceInfo>() { - public DnsSdServiceInfo createFromParcel(Parcel in) { - DnsSdServiceInfo info = new DnsSdServiceInfo(); + public static final Creator<NsdServiceInfo> CREATOR = + new Creator<NsdServiceInfo>() { + public NsdServiceInfo createFromParcel(Parcel in) { + NsdServiceInfo info = new NsdServiceInfo(); info.mServiceName = in.readString(); info.mServiceType = in.readString(); info.mTxtRecord = in.readParcelable(null); @@ -150,8 +146,8 @@ public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable { return info; } - public DnsSdServiceInfo[] newArray(int size) { - return new DnsSdServiceInfo[size]; + public NsdServiceInfo[] newArray(int size) { + return new NsdServiceInfo[size]; } }; } diff --git a/core/java/android/nfc/NdefRecord.java b/core/java/android/nfc/NdefRecord.java index 0e9e8f4..8872335 100644 --- a/core/java/android/nfc/NdefRecord.java +++ b/core/java/android/nfc/NdefRecord.java @@ -321,7 +321,7 @@ public final class NdefRecord implements Parcelable { * and {@link #RTD_URI}. This is the most efficient encoding * of a URI into NDEF.<p> * The uri parameter will be normalized with - * {@link Uri#normalize} to set the scheme to lower case to + * {@link Uri#normalizeScheme} to set the scheme to lower case to * follow Android best practices for intent filtering. * However the unchecked exception * {@link IllegalArgumentException} may be thrown if the uri @@ -338,7 +338,7 @@ public final class NdefRecord implements Parcelable { public static NdefRecord createUri(Uri uri) { if (uri == null) throw new NullPointerException("uri is null"); - uri = uri.normalize(); + uri = uri.normalizeScheme(); String uriString = uri.toString(); if (uriString.length() == 0) throw new IllegalArgumentException("uri is empty"); @@ -364,7 +364,7 @@ public final class NdefRecord implements Parcelable { * and {@link #RTD_URI}. This is the most efficient encoding * of a URI into NDEF.<p> * The uriString parameter will be normalized with - * {@link Uri#normalize} to set the scheme to lower case to + * {@link Uri#normalizeScheme} to set the scheme to lower case to * follow Android best practices for intent filtering. * However the unchecked exception * {@link IllegalArgumentException} may be thrown if the uriString @@ -665,7 +665,7 @@ public final class NdefRecord implements Parcelable { * actually valid: it always attempts to create and return a URI if * this record appears to be a URI record by the above rules.<p> * The returned URI will be normalized to have a lower case scheme - * using {@link Uri#normalize}.<p> + * using {@link Uri#normalizeScheme}.<p> * * @return URI, or null if this is not a URI record */ @@ -688,13 +688,13 @@ public final class NdefRecord implements Parcelable { } } catch (FormatException e) { } } else if (Arrays.equals(mType, RTD_URI)) { - return parseWktUri().normalize(); + return parseWktUri().normalizeScheme(); } break; case TNF_ABSOLUTE_URI: Uri uri = Uri.parse(new String(mType, Charsets.UTF_8)); - return uri.normalize(); + return uri.normalizeScheme(); case TNF_EXTERNAL_TYPE: if (inSmartPoster) { diff --git a/core/java/android/content/CancellationSignal.java b/core/java/android/os/CancellationSignal.java index dcaeeb7..dcba9b7 100644 --- a/core/java/android/content/CancellationSignal.java +++ b/core/java/android/os/CancellationSignal.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package android.content; +package android.os; -import android.os.RemoteException; +import android.os.ICancellationSignal; +import android.os.ICancellationSignal.Stub; /** * Provides the ability to cancel an operation in progress. diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java index 0586d9e..b7bc45f 100644 --- a/core/java/android/os/IBinder.java +++ b/core/java/android/os/IBinder.java @@ -140,6 +140,9 @@ public interface IBinder { */ int LIKE_TRANSACTION = ('_'<<24)|('L'<<16)|('I'<<8)|'K'; + /** @hide */ + int SYSPROPS_TRANSACTION = ('_'<<24)|('S'<<16)|('P'<<8)|'R'; + /** * Flag to {@link #transact}: this is a one-way call, meaning that the * caller returns immediately, without waiting for a result from the diff --git a/core/java/android/content/ICancellationSignal.aidl b/core/java/android/os/ICancellationSignal.aidl index cf1c5d3..d92464c 100644 --- a/core/java/android/content/ICancellationSignal.aidl +++ b/core/java/android/os/ICancellationSignal.aidl @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content; +package android.os; /** * @hide diff --git a/core/java/android/content/OperationCanceledException.java b/core/java/android/os/OperationCanceledException.java index d783a07..b0cd663 100644 --- a/core/java/android/content/OperationCanceledException.java +++ b/core/java/android/os/OperationCanceledException.java @@ -14,7 +14,8 @@ * limitations under the License. */ -package android.content; +package android.os; + /** * An exception type that is thrown when an operation in progress is canceled. diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 1df53e8..18fd3cb 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -897,6 +897,9 @@ public class Process { public static final native long getFreeMemory(); /** @hide */ + public static final native long getTotalMemory(); + + /** @hide */ public static final native void readProcLines(String path, String[] reqFields, long[] outSizes); diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java index 43b5128..be24426 100644 --- a/core/java/android/os/ServiceManagerNative.java +++ b/core/java/android/os/ServiceManagerNative.java @@ -16,6 +16,8 @@ package android.os; +import java.util.ArrayList; + /** * Native implementation of the service manager. Most clients will only @@ -151,14 +153,32 @@ class ServiceManagerProxy implements IServiceManager { } public String[] listServices() throws RemoteException { - Parcel data = Parcel.obtain(); - Parcel reply = Parcel.obtain(); - data.writeInterfaceToken(IServiceManager.descriptor); - mRemote.transact(LIST_SERVICES_TRANSACTION, data, reply, 0); - String[] list = reply.readStringArray(); - reply.recycle(); - data.recycle(); - return list; + ArrayList<String> services = new ArrayList<String>(); + int n = 0; + while (true) { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IServiceManager.descriptor); + data.writeInt(n); + n++; + try { + boolean res = mRemote.transact(LIST_SERVICES_TRANSACTION, data, reply, 0); + if (!res) { + break; + } + } catch (RuntimeException e) { + // The result code that is returned by the C++ code can + // cause the call to throw an exception back instead of + // returning a nice result... so eat it here and go on. + break; + } + services.add(reply.readString()); + reply.recycle(); + data.recycle(); + } + String[] array = new String[services.size()]; + services.toArray(array); + return array; } public void setPermissionController(IPermissionController controller) diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java index 619bf8d..156600e 100644 --- a/core/java/android/os/SystemProperties.java +++ b/core/java/android/os/SystemProperties.java @@ -16,6 +16,10 @@ package android.os; +import java.util.ArrayList; + +import android.util.Log; + /** * Gives access to the system properties store. The system properties @@ -28,12 +32,15 @@ public class SystemProperties public static final int PROP_NAME_MAX = 31; public static final int PROP_VALUE_MAX = 91; + private static final ArrayList<Runnable> sChangeCallbacks = new ArrayList<Runnable>(); + private static native String native_get(String key); private static native String native_get(String key, String def); private static native int native_get_int(String key, int def); private static native long native_get_long(String key, long def); private static native boolean native_get_boolean(String key, boolean def); private static native void native_set(String key, String def); + private static native void native_add_change_callback(); /** * Get the value for the given key. @@ -124,4 +131,26 @@ public class SystemProperties } native_set(key, val); } + + public static void addChangeCallback(Runnable callback) { + synchronized (sChangeCallbacks) { + if (sChangeCallbacks.size() == 0) { + native_add_change_callback(); + } + sChangeCallbacks.add(callback); + } + } + + static void callChangeCallbacks() { + synchronized (sChangeCallbacks) { + //Log.i("foo", "Calling " + sChangeCallbacks.size() + " change callbacks!"); + if (sChangeCallbacks.size() == 0) { + return; + } + ArrayList<Runnable> callbacks = new ArrayList<Runnable>(sChangeCallbacks); + for (int i=0; i<callbacks.size(); i++) { + callbacks.get(i).run(); + } + } + } } diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java index 05acd63..911183d 100644 --- a/core/java/android/os/Trace.java +++ b/core/java/android/os/Trace.java @@ -37,14 +37,31 @@ public final class Trace { public static final long TRACE_TAG_WINDOW_MANAGER = 1L << 5; public static final long TRACE_TAG_ACTIVITY_MANAGER = 1L << 6; public static final long TRACE_TAG_SYNC_MANAGER = 1L << 7; + public static final long TRACE_TAG_AUDIO = 1L << 8; - private static final long sEnabledTags = nativeGetEnabledTags(); + public static final int TRACE_FLAGS_START_BIT = 1; + public static final String[] TRACE_TAGS = { + "Graphics", "Input", "View", "WebView", "Window Manager", + "Activity Manager", "Sync Manager", "Audio" + }; + + public static final String PROPERTY_TRACE_TAG_ENABLEFLAGS = "debug.atrace.tags.enableflags"; + + private static long sEnabledTags = nativeGetEnabledTags(); private static native long nativeGetEnabledTags(); private static native void nativeTraceCounter(long tag, String name, int value); private static native void nativeTraceBegin(long tag, String name); private static native void nativeTraceEnd(long tag); + static { + SystemProperties.addChangeCallback(new Runnable() { + @Override public void run() { + sEnabledTags = nativeGetEnabledTags(); + } + }); + } + private Trace() { } diff --git a/core/java/android/preference/MultiCheckPreference.java b/core/java/android/preference/MultiCheckPreference.java new file mode 100644 index 0000000..6953075 --- /dev/null +++ b/core/java/android/preference/MultiCheckPreference.java @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2012 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.preference; + +import java.util.Arrays; + +import android.app.AlertDialog.Builder; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.TypedArray; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; + +/** + * @hide + * A {@link Preference} that displays a list of entries as + * a dialog which allow the user to toggle each individually on and off. + * + * @attr ref android.R.styleable#ListPreference_entries + * @attr ref android.R.styleable#ListPreference_entryValues + */ +public class MultiCheckPreference extends DialogPreference { + private CharSequence[] mEntries; + private String[] mEntryValues; + private boolean[] mSetValues; + private boolean[] mOrigValues; + private String mSummary; + + public MultiCheckPreference(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.ListPreference, 0, 0); + mEntries = a.getTextArray(com.android.internal.R.styleable.ListPreference_entries); + if (mEntries != null) { + setEntries(mEntries); + } + setEntryValuesCS(a.getTextArray( + com.android.internal.R.styleable.ListPreference_entryValues)); + a.recycle(); + + /* Retrieve the Preference summary attribute since it's private + * in the Preference class. + */ + a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.Preference, 0, 0); + mSummary = a.getString(com.android.internal.R.styleable.Preference_summary); + a.recycle(); + } + + public MultiCheckPreference(Context context) { + this(context, null); + } + + /** + * Sets the human-readable entries to be shown in the list. This will be + * shown in subsequent dialogs. + * <p> + * Each entry must have a corresponding index in + * {@link #setEntryValues(CharSequence[])}. + * + * @param entries The entries. + * @see #setEntryValues(CharSequence[]) + */ + public void setEntries(CharSequence[] entries) { + mEntries = entries; + mSetValues = new boolean[entries.length]; + mOrigValues = new boolean[entries.length]; + } + + /** + * @see #setEntries(CharSequence[]) + * @param entriesResId The entries array as a resource. + */ + public void setEntries(int entriesResId) { + setEntries(getContext().getResources().getTextArray(entriesResId)); + } + + /** + * The list of entries to be shown in the list in subsequent dialogs. + * + * @return The list as an array. + */ + public CharSequence[] getEntries() { + return mEntries; + } + + /** + * The array to find the value to save for a preference when an entry from + * entries is selected. If a user clicks on the second item in entries, the + * second item in this array will be saved to the preference. + * + * @param entryValues The array to be used as values to save for the preference. + */ + public void setEntryValues(String[] entryValues) { + mEntryValues = entryValues; + Arrays.fill(mSetValues, false); + Arrays.fill(mOrigValues, false); + } + + /** + * @see #setEntryValues(CharSequence[]) + * @param entryValuesResId The entry values array as a resource. + */ + public void setEntryValues(int entryValuesResId) { + setEntryValuesCS(getContext().getResources().getTextArray(entryValuesResId)); + } + + private void setEntryValuesCS(CharSequence[] values) { + setValues(null); + if (values != null) { + mEntryValues = new String[values.length]; + for (int i=0; i<values.length; i++) { + mEntryValues[i] = values[i].toString(); + } + } + } + + /** + * Returns the array of values to be saved for the preference. + * + * @return The array of values. + */ + public String[] getEntryValues() { + return mEntryValues; + } + + /** + * Get the boolean state of a given value. + */ + public boolean getValue(int index) { + return mSetValues[index]; + } + + /** + * Set the boolean state of a given value. + */ + public void setValue(int index, boolean state) { + mSetValues[index] = state; + } + + /** + * Sets the current values. + */ + public void setValues(boolean[] values) { + if (mSetValues != null) { + Arrays.fill(mSetValues, false); + Arrays.fill(mOrigValues, false); + if (values != null) { + System.arraycopy(values, 0, mSetValues, 0, + values.length < mSetValues.length ? values.length : mSetValues.length); + } + } + } + + /** + * Returns the summary of this ListPreference. If the summary + * has a {@linkplain java.lang.String#format String formatting} + * marker in it (i.e. "%s" or "%1$s"), then the current entry + * value will be substituted in its place. + * + * @return the summary with appropriate string substitution + */ + @Override + public CharSequence getSummary() { + if (mSummary == null) { + return super.getSummary(); + } else { + return mSummary; + } + } + + /** + * Sets the summary for this Preference with a CharSequence. + * If the summary has a + * {@linkplain java.lang.String#format String formatting} + * marker in it (i.e. "%s" or "%1$s"), then the current entry + * value will be substituted in its place when it's retrieved. + * + * @param summary The summary for the preference. + */ + @Override + public void setSummary(CharSequence summary) { + super.setSummary(summary); + if (summary == null && mSummary != null) { + mSummary = null; + } else if (summary != null && !summary.equals(mSummary)) { + mSummary = summary.toString(); + } + } + + /** + * Returns the currently selected values. + */ + public boolean[] getValues() { + return mSetValues; + } + + /** + * Returns the index of the given value (in the entry values array). + * + * @param value The value whose index should be returned. + * @return The index of the value, or -1 if not found. + */ + public int findIndexOfValue(String value) { + if (value != null && mEntryValues != null) { + for (int i = mEntryValues.length - 1; i >= 0; i--) { + if (mEntryValues[i].equals(value)) { + return i; + } + } + } + return -1; + } + + @Override + protected void onPrepareDialogBuilder(Builder builder) { + super.onPrepareDialogBuilder(builder); + + if (mEntries == null || mEntryValues == null) { + throw new IllegalStateException( + "ListPreference requires an entries array and an entryValues array."); + } + + mOrigValues = Arrays.copyOf(mSetValues, mSetValues.length); + builder.setMultiChoiceItems(mEntries, mSetValues, + new DialogInterface.OnMultiChoiceClickListener() { + @Override + public void onClick(DialogInterface dialog, int which, boolean isChecked) { + mSetValues[which] = isChecked; + } + }); + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + super.onDialogClosed(positiveResult); + + if (positiveResult) { + if (callChangeListener(getValues())) { + return; + } + } + System.arraycopy(mOrigValues, 0, mSetValues, 0, mSetValues.length); + } + + @Override + protected Object onGetDefaultValue(TypedArray a, int index) { + return a.getString(index); + } + + @Override + protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { + } + + @Override + protected Parcelable onSaveInstanceState() { + final Parcelable superState = super.onSaveInstanceState(); + if (isPersistent()) { + // No need to save instance state since it's persistent + return superState; + } + + final SavedState myState = new SavedState(superState); + myState.values = getValues(); + return myState; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + if (state == null || !state.getClass().equals(SavedState.class)) { + // Didn't save state for us in onSaveInstanceState + super.onRestoreInstanceState(state); + return; + } + + SavedState myState = (SavedState) state; + super.onRestoreInstanceState(myState.getSuperState()); + setValues(myState.values); + } + + private static class SavedState extends BaseSavedState { + boolean[] values; + + public SavedState(Parcel source) { + super(source); + values = source.createBooleanArray(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeBooleanArray(values); + } + + public SavedState(Parcelable superState) { + super(superState); + } + + public static final Parcelable.Creator<SavedState> CREATOR = + new Parcelable.Creator<SavedState>() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 2c49bd2..497e66e8 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1410,6 +1410,7 @@ public final class Settings { /** * Adjustment to auto-brightness to make it generally more (>0.0 <1.0) * or less (<0.0 >-1.0) bright. + * @hide */ public static final String SCREEN_AUTO_BRIGHTNESS_ADJ = "screen_auto_brightness_adj"; @@ -1557,6 +1558,9 @@ public final class Settings { * will likely be removed in a future release with support for * audio/vibe feedback profiles. * + * Not used anymore. On devices with vibrator, the user explicitly selects + * silent or vibrate mode. + * Kept for use by legacy database upgrade code in DatabaseHelper. * @hide */ public static final String VIBRATE_IN_SILENT = "vibrate_in_silent"; @@ -1746,6 +1750,20 @@ public final class Settings { public static final String USER_ROTATION = "user_rotation"; /** + * Whether the phone vibrates when it is ringing due to an incoming call. This will + * be used by Phone and Setting apps; it shouldn't affect other apps. + * The value is boolean (1 or 0). + * + * Note: this is not same as "vibrate on ring", which had been available until ICS. + * It was about AudioManager's setting and thus affected all the applications which + * relied on the setting, while this is purely about the vibration setting for incoming + * calls. + * + * @hide + */ + public static final String VIBRATE_WHEN_RINGING = "vibrate_when_ringing"; + + /** * Whether the audible DTMF tones are played by the dialer when dialing. The value is * boolean (1 or 0). */ @@ -1983,7 +2001,6 @@ public final class Settings { SCREEN_BRIGHTNESS, SCREEN_BRIGHTNESS_MODE, SCREEN_AUTO_BRIGHTNESS_ADJ, - VIBRATE_ON, VIBRATE_INPUT_DEVICES, MODE_RINGER, MODE_RINGER_STREAMS_AFFECTED, @@ -2002,7 +2019,6 @@ public final class Settings { VOLUME_ALARM + APPEND_FOR_LAST_AUDIBLE, VOLUME_NOTIFICATION + APPEND_FOR_LAST_AUDIBLE, VOLUME_BLUETOOTH_SCO + APPEND_FOR_LAST_AUDIBLE, - VIBRATE_IN_SILENT, TEXT_AUTO_REPLACE, TEXT_AUTO_CAPS, TEXT_AUTO_PUNCTUATE, @@ -2029,6 +2045,7 @@ public final class Settings { SIP_CALL_OPTIONS, SIP_RECEIVE_CALLS, POINTER_SPEED, + VIBRATE_WHEN_RINGING }; // Settings moved to Settings.Secure diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 6387148..d42757d 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -126,6 +126,16 @@ final class AccessibilityInteractionController { } } + private boolean isShown(View view) { + // The first two checks are made also made by isShown() which + // however traverses the tree up to the parent to catch that. + // Therefore, we do some fail fast check to minimize the up + // tree traversal. + return (view.mAttachInfo != null + && view.mAttachInfo.mWindowVisibility == View.VISIBLE + && view.isShown()); + } + public void findAccessibilityNodeInfoByAccessibilityIdClientThread( long accessibilityNodeId, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, @@ -174,7 +184,7 @@ final class AccessibilityInteractionController { } else { root = findViewByAccessibilityId(accessibilityViewId); } - if (root != null && root.isDisplayedOnScreen()) { + if (root != null && isShown(root)) { mPrefetcher.prefetchAccessibilityNodeInfos(root, virtualDescendantId, flags, infos); } } finally { @@ -236,7 +246,7 @@ final class AccessibilityInteractionController { } if (root != null) { View target = root.findViewById(viewId); - if (target != null && target.isDisplayedOnScreen()) { + if (target != null && isShown(target)) { info = target.createAccessibilityNodeInfo(); } } @@ -298,7 +308,7 @@ final class AccessibilityInteractionController { } else { root = mViewRootImpl.mView; } - if (root != null && root.isDisplayedOnScreen()) { + if (root != null && isShown(root)) { AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider(); if (provider != null) { infos = provider.findAccessibilityNodeInfosByText(text, @@ -315,7 +325,7 @@ final class AccessibilityInteractionController { final int viewCount = foundViews.size(); for (int i = 0; i < viewCount; i++) { View foundView = foundViews.get(i); - if (foundView.isDisplayedOnScreen()) { + if (isShown(foundView)) { provider = foundView.getAccessibilityNodeProvider(); if (provider != null) { List<AccessibilityNodeInfo> infosFromProvider = @@ -390,7 +400,7 @@ final class AccessibilityInteractionController { } else { root = mViewRootImpl.mView; } - if (root != null && root.isDisplayedOnScreen()) { + if (root != null && isShown(root)) { switch (focusType) { case AccessibilityNodeInfo.FOCUS_ACCESSIBILITY: { View host = mViewRootImpl.mAccessibilityFocusedHost; @@ -403,7 +413,7 @@ final class AccessibilityInteractionController { // focus instead fetching all provider nodes to do the search here. AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider(); if (provider != null) { - focused = provider.findAccessibilitiyFocus(virtualDescendantId); + focused = provider.findAccessibilityFocus(virtualDescendantId); } else if (virtualDescendantId == View.NO_ID) { focused = host.createAccessibilityNodeInfo(); } @@ -411,7 +421,7 @@ final class AccessibilityInteractionController { case AccessibilityNodeInfo.FOCUS_INPUT: { // Input focus cannot go to virtual views. View target = root.findFocus(); - if (target != null && target.isDisplayedOnScreen()) { + if (target != null && isShown(target)) { focused = target.createAccessibilityNodeInfo(); } } break; @@ -477,7 +487,7 @@ final class AccessibilityInteractionController { } else { root = mViewRootImpl.mView; } - if (root != null && root.isDisplayedOnScreen()) { + if (root != null && isShown(root)) { if ((direction & View.FOCUS_ACCESSIBILITY) == View.FOCUS_ACCESSIBILITY) { AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider(); if (provider != null) { @@ -565,7 +575,7 @@ final class AccessibilityInteractionController { } else { target = mViewRootImpl.mView; } - if (target != null && target.isDisplayedOnScreen()) { + if (target != null && isShown(target)) { AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider(); if (provider != null) { succeeded = provider.performAction(virtualDescendantId, action, @@ -590,7 +600,7 @@ final class AccessibilityInteractionController { return null; } View foundView = root.findViewByAccessibilityId(accessibilityId); - if (foundView != null && !foundView.isDisplayedOnScreen()) { + if (foundView != null && !isShown(foundView)) { return null; } return foundView; @@ -670,7 +680,7 @@ final class AccessibilityInteractionController { } View child = children.getChildAt(i); if (child.getAccessibilityViewId() != current.getAccessibilityViewId() - && child.isDisplayedOnScreen()) { + && isShown(child)) { AccessibilityNodeInfo info = null; AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider(); if (provider == null) { @@ -706,7 +716,7 @@ final class AccessibilityInteractionController { return; } View child = children.getChildAt(i); - if (child.isDisplayedOnScreen()) { + if (isShown(child)) { AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider(); if (provider == null) { AccessibilityNodeInfo info = child.createAccessibilityNodeInfo(); diff --git a/core/java/android/view/AccessibilityIterators.java b/core/java/android/view/AccessibilityIterators.java new file mode 100644 index 0000000..386c866 --- /dev/null +++ b/core/java/android/view/AccessibilityIterators.java @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2012 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.content.ComponentCallbacks; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; + +import java.text.BreakIterator; +import java.util.Locale; + +/** + * This class contains the implementation of text segment iterators + * for accessibility support. + * + * Note: Such iterators are needed in the view package since we want + * to be able to iterator over content description of any view. + * + * @hide + */ +public final class AccessibilityIterators { + + /** + * @hide + */ + public static interface TextSegmentIterator { + public int[] following(int current); + public int[] preceding(int current); + } + + /** + * @hide + */ + public static abstract class AbstractTextSegmentIterator implements TextSegmentIterator { + protected static final int DONE = -1; + + protected String mText; + + private final int[] mSegment = new int[2]; + + public void initialize(String text) { + mText = text; + } + + protected int[] getRange(int start, int end) { + if (start < 0 || end < 0 || start == end) { + return null; + } + mSegment[0] = start; + mSegment[1] = end; + return mSegment; + } + } + + static class CharacterTextSegmentIterator extends AbstractTextSegmentIterator + implements ComponentCallbacks { + private static CharacterTextSegmentIterator sInstance; + + private final Context mAppContext; + + protected BreakIterator mImpl; + + public static CharacterTextSegmentIterator getInstance(Context context) { + if (sInstance == null) { + sInstance = new CharacterTextSegmentIterator(context); + } + return sInstance; + } + + private CharacterTextSegmentIterator(Context context) { + mAppContext = context.getApplicationContext(); + Locale locale = mAppContext.getResources().getConfiguration().locale; + onLocaleChanged(locale); + ViewRootImpl.addConfigCallback(this); + } + + @Override + public void initialize(String text) { + super.initialize(text); + mImpl.setText(text); + } + + @Override + public int[] following(int offset) { + final int textLegth = mText.length(); + if (textLegth <= 0) { + return null; + } + if (offset >= textLegth) { + return null; + } + int start = -1; + if (offset < 0) { + offset = 0; + if (mImpl.isBoundary(offset)) { + start = offset; + } + } + if (start < 0) { + start = mImpl.following(offset); + } + if (start < 0) { + return null; + } + final int end = mImpl.following(start); + return getRange(start, end); + } + + @Override + public int[] preceding(int offset) { + final int textLegth = mText.length(); + if (textLegth <= 0) { + return null; + } + if (offset <= 0) { + return null; + } + int end = -1; + if (offset > mText.length()) { + offset = mText.length(); + if (mImpl.isBoundary(offset)) { + end = offset; + } + } + if (end < 0) { + end = mImpl.preceding(offset); + } + if (end < 0) { + return null; + } + final int start = mImpl.preceding(end); + return getRange(start, end); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + Configuration oldConfig = mAppContext.getResources().getConfiguration(); + final int changed = oldConfig.diff(newConfig); + if ((changed & ActivityInfo.CONFIG_LOCALE) != 0) { + Locale locale = newConfig.locale; + onLocaleChanged(locale); + } + } + + @Override + public void onLowMemory() { + /* ignore */ + } + + protected void onLocaleChanged(Locale locale) { + mImpl = BreakIterator.getCharacterInstance(locale); + } + } + + static class WordTextSegmentIterator extends CharacterTextSegmentIterator { + private static WordTextSegmentIterator sInstance; + + public static WordTextSegmentIterator getInstance(Context context) { + if (sInstance == null) { + sInstance = new WordTextSegmentIterator(context); + } + return sInstance; + } + + private WordTextSegmentIterator(Context context) { + super(context); + } + + @Override + protected void onLocaleChanged(Locale locale) { + mImpl = BreakIterator.getWordInstance(locale); + } + + @Override + public int[] following(int offset) { + final int textLegth = mText.length(); + if (textLegth <= 0) { + return null; + } + if (offset >= mText.length()) { + return null; + } + int start = -1; + if (offset < 0) { + offset = 0; + if (mImpl.isBoundary(offset) && isLetterOrDigit(offset)) { + start = offset; + } + } + if (start < 0) { + while ((offset = mImpl.following(offset)) != DONE) { + if (isLetterOrDigit(offset)) { + start = offset; + break; + } + } + } + if (start < 0) { + return null; + } + final int end = mImpl.following(start); + return getRange(start, end); + } + + @Override + public int[] preceding(int offset) { + final int textLegth = mText.length(); + if (textLegth <= 0) { + return null; + } + if (offset <= 0) { + return null; + } + int end = -1; + if (offset > mText.length()) { + offset = mText.length(); + if (mImpl.isBoundary(offset) && offset > 0 && isLetterOrDigit(offset - 1)) { + end = offset; + } + } + if (end < 0) { + while ((offset = mImpl.preceding(offset)) != DONE) { + if (offset > 0 && isLetterOrDigit(offset - 1)) { + end = offset; + break; + } + } + } + if (end < 0) { + return null; + } + final int start = mImpl.preceding(end); + return getRange(start, end); + } + + private boolean isLetterOrDigit(int index) { + if (index >= 0 && index < mText.length()) { + final int codePoint = mText.codePointAt(index); + return Character.isLetterOrDigit(codePoint); + } + return false; + } + } + + static class ParagraphTextSegmentIterator extends AbstractTextSegmentIterator { + private static ParagraphTextSegmentIterator sInstance; + + public static ParagraphTextSegmentIterator getInstance() { + if (sInstance == null) { + sInstance = new ParagraphTextSegmentIterator(); + } + return sInstance; + } + + @Override + public int[] following(int offset) { + final int textLength = mText.length(); + if (textLength <= 0) { + return null; + } + if (offset >= textLength) { + return null; + } + int start = -1; + if (offset < 0) { + start = 0; + } else { + for (int i = offset + 1; i < textLength; i++) { + if (mText.charAt(i) == '\n') { + start = i; + break; + } + } + } + while (start < textLength && mText.charAt(start) == '\n') { + start++; + } + if (start < 0) { + return null; + } + int end = start; + for (int i = end + 1; i < textLength; i++) { + end = i; + if (mText.charAt(i) == '\n') { + break; + } + } + while (end < textLength && mText.charAt(end) == '\n') { + end++; + } + return getRange(start, end); + } + + @Override + public int[] preceding(int offset) { + final int textLength = mText.length(); + if (textLength <= 0) { + return null; + } + if (offset <= 0) { + return null; + } + int end = -1; + if (offset > mText.length()) { + end = mText.length(); + } else { + if (offset > 0 && mText.charAt(offset - 1) == '\n') { + offset--; + } + for (int i = offset - 1; i >= 0; i--) { + if (i > 0 && mText.charAt(i - 1) == '\n') { + end = i; + break; + } + } + } + if (end <= 0) { + return null; + } + int start = end; + while (start > 0 && mText.charAt(start - 1) == '\n') { + start--; + } + if (start == 0 && mText.charAt(start) == '\n') { + return null; + } + for (int i = start - 1; i >= 0; i--) { + start = i; + if (start > 0 && mText.charAt(i - 1) == '\n') { + break; + } + } + start = Math.max(0, start); + return getRange(start, end); + } + } +} diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index b319cd5..825f351 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -92,6 +92,7 @@ public final class Choreographer { private boolean mFrameScheduled; private boolean mCallbacksRunning; private long mLastFrameTimeNanos; + private long mFrameIntervalNanos; /** * Callback type: Input callback. Runs first. @@ -116,6 +117,8 @@ public final class Choreographer { mHandler = new FrameHandler(looper); mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null; mLastFrameTimeNanos = Long.MIN_VALUE; + mFrameIntervalNanos = (long)(1000000000 / + new Display(Display.DEFAULT_DISPLAY, null).getRefreshRate()); mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1]; for (int i = 0; i <= CALLBACK_LAST; i++) { @@ -343,17 +346,37 @@ public final class Choreographer { } void doFrame(long timestampNanos, int frame) { + final long startNanos; synchronized (mLock) { if (!mFrameScheduled) { return; // no work to do } - mFrameScheduled = false; - mLastFrameTimeNanos = timestampNanos; - } - final long startNanos; - if (DEBUG) { startNanos = System.nanoTime(); + final long jitterNanos = startNanos - timestampNanos; + if (jitterNanos >= mFrameIntervalNanos) { + final long lastFrameOffset = jitterNanos % mFrameIntervalNanos; + if (DEBUG) { + Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms " + + "which is more than the frame interval of " + + (mFrameIntervalNanos * 0.000001f) + " ms! " + + "Setting frame time to " + (lastFrameOffset * 0.000001f) + + " ms in the past."); + } + timestampNanos = startNanos - lastFrameOffset; + } + + if (timestampNanos < mLastFrameTimeNanos) { + if (DEBUG) { + Log.d(TAG, "Frame time appears to be going backwards. May be due to a " + + "previously skipped frame. Waiting for next vsync"); + } + scheduleVsyncLocked(); + return; + } + + mFrameScheduled = false; + mLastFrameTimeNanos = timestampNanos; } doCallbacks(Choreographer.CALLBACK_INPUT); diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index e1f01db..c5a687a 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -84,7 +84,7 @@ interface IWindowManager void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth, int startHeight); void overridePendingAppTransitionThumb(in Bitmap srcThumb, int startX, int startY, - IRemoteCallback startedCallback); + IRemoteCallback startedCallback, boolean delayed); void executeAppTransition(); void setAppStartingWindow(IBinder token, String pkg, int theme, in CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java index 5b371eb..2cb724f 100644 --- a/core/java/android/view/KeyCharacterMap.java +++ b/core/java/android/view/KeyCharacterMap.java @@ -386,19 +386,19 @@ public class KeyCharacterMap implements Parcelable { * * @param keyCode The key code. * @param metaState The meta key modifier state. - * @param outFallbackAction The fallback action object to populate. - * @return True if a fallback action was found, false otherwise. + * @return The fallback action, or null if none. Remember to recycle the fallback action. * * @hide */ - public boolean getFallbackAction(int keyCode, int metaState, - FallbackAction outFallbackAction) { - if (outFallbackAction == null) { - throw new IllegalArgumentException("fallbackAction must not be null"); - } - + public FallbackAction getFallbackAction(int keyCode, int metaState) { + FallbackAction action = FallbackAction.obtain(); metaState = KeyEvent.normalizeMetaState(metaState); - return nativeGetFallbackAction(mPtr, keyCode, metaState, outFallbackAction); + if (nativeGetFallbackAction(mPtr, keyCode, metaState, action)) { + action.metaState = KeyEvent.normalizeMetaState(action.metaState); + return action; + } + action.recycle(); + return null; } /** @@ -727,7 +727,44 @@ public class KeyCharacterMap implements Parcelable { * @hide */ public static final class FallbackAction { + private static final int MAX_RECYCLED = 10; + private static final Object sRecycleLock = new Object(); + private static FallbackAction sRecycleBin; + private static int sRecycledCount; + + private FallbackAction next; + public int keyCode; public int metaState; + + private FallbackAction() { + } + + public static FallbackAction obtain() { + final FallbackAction target; + synchronized (sRecycleLock) { + if (sRecycleBin == null) { + target = new FallbackAction(); + } else { + target = sRecycleBin; + sRecycleBin = target.next; + sRecycledCount--; + target.next = null; + } + } + return target; + } + + public void recycle() { + synchronized (sRecycleLock) { + if (sRecycledCount < MAX_RECYCLED) { + next = sRecycleBin; + sRecycleBin = this; + sRecycledCount += 1; + } else { + next = null; + } + } + } } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 5299d58..bf7d037 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -47,7 +47,6 @@ import android.os.Parcelable; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; -import android.text.TextUtils; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.LocaleUtil; @@ -60,6 +59,10 @@ import android.util.Property; import android.util.SparseArray; import android.util.TypedValue; import android.view.ContextMenu.ContextMenuInfo; +import android.view.AccessibilityIterators.TextSegmentIterator; +import android.view.AccessibilityIterators.CharacterTextSegmentIterator; +import android.view.AccessibilityIterators.WordTextSegmentIterator; +import android.view.AccessibilityIterators.ParagraphTextSegmentIterator; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityEventSource; import android.view.accessibility.AccessibilityManager; @@ -1524,7 +1527,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED - | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED; + | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED + | AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY; /** * Temporary Rect currently for use in setBackground(). This will probably @@ -1590,6 +1594,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal int mAccessibilityViewId = NO_ID; /** + * @hide + */ + private int mAccessibilityCursorPosition = -1; + + /** * The view's tag. * {@hide} * @@ -4657,6 +4666,51 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** + * Gets the location of this view in screen coordintates. + * + * @param outRect The output location + */ + private void getBoundsOnScreen(Rect outRect) { + if (mAttachInfo == null) { + return; + } + + RectF position = mAttachInfo.mTmpTransformRect; + position.set(0, 0, mRight - mLeft, mBottom - mTop); + + if (!hasIdentityMatrix()) { + getMatrix().mapRect(position); + } + + position.offset(mLeft, mTop); + + ViewParent parent = mParent; + while (parent instanceof View) { + View parentView = (View) parent; + + position.offset(-parentView.mScrollX, -parentView.mScrollY); + + if (!parentView.hasIdentityMatrix()) { + parentView.getMatrix().mapRect(position); + } + + position.offset(parentView.mLeft, parentView.mTop); + + parent = parentView.mParent; + } + + if (parent instanceof ViewRootImpl) { + ViewRootImpl viewRootImpl = (ViewRootImpl) parent; + position.offset(0, -viewRootImpl.mCurScrollY); + } + + position.offset(mAttachInfo.mWindowLeft, mAttachInfo.mWindowTop); + + outRect.set((int) (position.left + 0.5f), (int) (position.top + 0.5f), + (int) (position.right + 0.5f), (int) (position.bottom + 0.5f)); + } + + /** * @see #onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo) * * Note: Called from the default {@link AccessibilityDelegate}. @@ -4666,8 +4720,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal getDrawingRect(bounds); info.setBoundsInParent(bounds); - getGlobalVisibleRect(bounds); - bounds.offset(mAttachInfo.mWindowLeft, mAttachInfo.mWindowTop); + getBoundsOnScreen(bounds); info.setBoundsInScreen(bounds); if ((mPrivateFlags & IS_ROOT_NAMESPACE) == 0) { @@ -4677,6 +4730,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } + info.setVisibleToUser(isVisibleToUser()); + info.setPackageName(mContext.getPackageName()); info.setClassName(View.class.getName()); info.setContentDescription(getContentDescription()); @@ -4703,8 +4758,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } - info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS); - info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS); + if (!isAccessibilityFocused()) { + info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS); + } else { + info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS); + } if (isClickable()) { info.addAction(AccessibilityNodeInfo.ACTION_CLICK); @@ -4714,20 +4772,23 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK); } - if (getContentDescription() != null) { + if (mContentDescription != null && mContentDescription.length() > 0) { info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER - | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD); + | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD + | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH); } } /** - * Computes whether this view is visible on the screen. + * Computes whether this view is visible to the user. Such a view is + * attached, visible, all its predecessors are visible, it is not clipped + * entirely by its predecessors, and has an alpha greater than zero. * * @return Whether the view is visible on the screen. */ - boolean isDisplayedOnScreen() { + private boolean isVisibleToUser() { // The first two checks are made also made by isShown() which // however traverses the tree up to the parent to catch that. // Therefore, we do some fail fast check to minimize the up @@ -5148,6 +5209,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * call to continue to your children, you must be sure to call the super * implementation. * + * <p>Here is a sample layout that makes use of fitting system windows + * to have controls for a video view placed inside of the window decorations + * that it hides and shows. This can be used with code like the second + * sample (video player) shown in {@link #setSystemUiVisibility(int)}. + * + * {@sample development/samples/ApiDemos/res/layout/video_player.xml complete} + * * @param insets Current content insets of the window. Prior to * {@link android.os.Build.VERSION_CODES#JELLY_BEAN} you must not modify * the insets or else you and Android will be unhappy. @@ -5190,7 +5258,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** - * Check for the FITS_SYSTEM_WINDOWS flag. If this method returns true, this view + * Check for state of {@link #setFitsSystemWindows(boolean). If this method + * returns true, this view * will account for system screen decorations such as the status bar and inset its * content. This allows the view to be positioned in absolute screen coordinates * and remain visible to the user. @@ -5199,10 +5268,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @attr ref android.R.styleable#View_fitsSystemWindows */ - public boolean fitsSystemWindows() { + public boolean getFitsSystemWindows() { return (mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS; } + /** @hide */ + public boolean fitsSystemWindows() { + return getFitsSystemWindows(); + } + /** * Ask that a new dispatch of {@link #fitSystemWindows(Rect)} be performed. */ @@ -5949,7 +6023,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal outViews.add(this); } } else if ((flags & FIND_VIEWS_WITH_CONTENT_DESCRIPTION) != 0 - && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mContentDescription)) { + && (searched != null && searched.length() > 0) + && (mContentDescription != null && mContentDescription.length() > 0)) { String searchedLowerCase = searched.toString().toLowerCase(); String contentDescriptionLowerCase = mContentDescription.toString().toLowerCase(); if (contentDescriptionLowerCase.contains(searchedLowerCase)) { @@ -6050,6 +6125,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal invalidate(); sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); notifyAccessibilityStateChanged(); + + // Clear the text navigation state. + setAccessibilityCursorPosition(-1); + // Try to move accessibility focus to the input focus. View rootView = getRootView(); if (rootView != null) { @@ -6377,9 +6456,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal boolean includeForAccessibility() { if (mAttachInfo != null) { if (!mAttachInfo.mIncludeNotImportantViews) { - return isImportantForAccessibility() && isDisplayedOnScreen(); + return isImportantForAccessibility(); } else { - return isDisplayedOnScreen(); + return true; } } return false; @@ -6445,11 +6524,31 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Performs the specified accessibility action on the view. For * possible accessibility actions look at {@link AccessibilityNodeInfo}. + * <p> + * If an {@link AccessibilityDelegate} has been specified via calling + * {@link #setAccessibilityDelegate(AccessibilityDelegate)} its + * {@link AccessibilityDelegate#performAccessibilityAction(View, int, Bundle)} + * is responsible for handling this call. + * </p> * * @param action The action to perform. + * @param arguments Optional action arguments. * @return Whether the action was performed. */ - public boolean performAccessibilityAction(int action, Bundle args) { + public boolean performAccessibilityAction(int action, Bundle arguments) { + if (mAccessibilityDelegate != null) { + return mAccessibilityDelegate.performAccessibilityAction(this, action, arguments); + } else { + return performAccessibilityActionInternal(action, arguments); + } + } + + /** + * @see #performAccessibilityAction(int, Bundle) + * + * Note: Called from the default {@link AccessibilityDelegate}. + */ + boolean performAccessibilityActionInternal(int action, Bundle arguments) { switch (action) { case AccessibilityNodeInfo.ACTION_CLICK: { if (isClickable()) { @@ -6498,14 +6597,155 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal return true; } } break; + case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: { + if (arguments != null) { + final int granularity = arguments.getInt( + AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT); + return nextAtGranularity(granularity); + } + } break; + case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: { + if (arguments != null) { + final int granularity = arguments.getInt( + AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT); + return previousAtGranularity(granularity); + } + } break; } return false; } + private boolean nextAtGranularity(int granularity) { + CharSequence text = getIterableTextForAccessibility(); + if (text != null && text.length() > 0) { + return false; + } + TextSegmentIterator iterator = getIteratorForGranularity(granularity); + if (iterator == null) { + return false; + } + final int current = getAccessibilityCursorPosition(); + final int[] range = iterator.following(current); + if (range == null) { + setAccessibilityCursorPosition(-1); + return false; + } + final int start = range[0]; + final int end = range[1]; + setAccessibilityCursorPosition(start); + sendViewTextTraversedAtGranularityEvent( + AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, + granularity, start, end); + return true; + } + + private boolean previousAtGranularity(int granularity) { + CharSequence text = getIterableTextForAccessibility(); + if (text != null && text.length() > 0) { + return false; + } + TextSegmentIterator iterator = getIteratorForGranularity(granularity); + if (iterator == null) { + return false; + } + final int selectionStart = getAccessibilityCursorPosition(); + final int current = selectionStart >= 0 ? selectionStart : text.length() + 1; + final int[] range = iterator.preceding(current); + if (range == null) { + setAccessibilityCursorPosition(-1); + return false; + } + final int start = range[0]; + final int end = range[1]; + setAccessibilityCursorPosition(end); + sendViewTextTraversedAtGranularityEvent( + AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, + granularity, start, end); + return true; + } + + /** + * Gets the text reported for accessibility purposes. + * + * @return The accessibility text. + * + * @hide + */ + public CharSequence getIterableTextForAccessibility() { + return mContentDescription; + } + + /** + * @hide + */ + public int getAccessibilityCursorPosition() { + return mAccessibilityCursorPosition; + } + + /** + * @hide + */ + public void setAccessibilityCursorPosition(int position) { + mAccessibilityCursorPosition = position; + } + + private void sendViewTextTraversedAtGranularityEvent(int action, int granularity, + int fromIndex, int toIndex) { + if (mParent == null) { + return; + } + AccessibilityEvent event = AccessibilityEvent.obtain( + AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY); + onInitializeAccessibilityEvent(event); + onPopulateAccessibilityEvent(event); + event.setFromIndex(fromIndex); + event.setToIndex(toIndex); + event.setAction(action); + event.setMovementGranularity(granularity); + mParent.requestSendAccessibilityEvent(this, event); + } + + /** + * @hide + */ + public TextSegmentIterator getIteratorForGranularity(int granularity) { + switch (granularity) { + case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER: { + CharSequence text = getIterableTextForAccessibility(); + if (text != null && text.length() > 0) { + CharacterTextSegmentIterator iterator = + CharacterTextSegmentIterator.getInstance(mContext); + iterator.initialize(text.toString()); + return iterator; + } + } break; + case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD: { + CharSequence text = getIterableTextForAccessibility(); + if (text != null && text.length() > 0) { + WordTextSegmentIterator iterator = + WordTextSegmentIterator.getInstance(mContext); + iterator.initialize(text.toString()); + return iterator; + } + } break; + case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH: { + CharSequence text = getIterableTextForAccessibility(); + if (text != null && text.length() > 0) { + ParagraphTextSegmentIterator iterator = + ParagraphTextSegmentIterator.getInstance(); + iterator.initialize(text.toString()); + return iterator; + } + } break; + } + return null; + } + /** * @hide */ public void dispatchStartTemporaryDetach() { + clearAccessibilityFocus(); onStartTemporaryDetach(); } @@ -10869,22 +11109,30 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if ((mPrivateFlags & REQUEST_TRANSPARENT_REGIONS) != 0) { mParent.requestTransparentRegion(this); } + if ((mPrivateFlags & AWAKEN_SCROLL_BARS_ON_ATTACH) != 0) { initialAwakenScrollBars(); mPrivateFlags &= ~AWAKEN_SCROLL_BARS_ON_ATTACH; } + jumpDrawablesToCurrentState(); + // Order is important here: LayoutDirection MUST be resolved before Padding // and TextDirection resolveLayoutDirection(); resolvePadding(); resolveTextDirection(); resolveTextAlignment(); + clearAccessibilityFocus(); if (isFocused()) { InputMethodManager imm = InputMethodManager.peekInstance(); imm.focusIn(this); } + + if (mAttachInfo != null && mDisplayList != null) { + mAttachInfo.mViewRootImpl.dequeueDisplayList(mDisplayList); + } } /** @@ -11105,7 +11353,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mAttachInfo != null) { if (mDisplayList != null) { - mAttachInfo.mViewRootImpl.invalidateDisplayList(mDisplayList); + mAttachInfo.mViewRootImpl.enqueueDisplayList(mDisplayList); } mAttachInfo.mViewRootImpl.cancelInvalidate(this); } else { @@ -11120,7 +11368,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal resetResolvedLayoutDirection(); resetResolvedTextAlignment(); resetAccessibilityStateChanged(); - clearAccessibilityFocus(); } /** @@ -11800,7 +12047,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal boolean caching = false; final HardwareCanvas canvas = displayList.start(); - int restoreCount = 0; int width = mRight - mLeft; int height = mBottom - mTop; @@ -12433,10 +12679,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal return more; } - void setDisplayListProperties() { - setDisplayListProperties(mDisplayList); - } - /** * This method is called by getDisplayList() when a display list is created or re-rendered. * It sets or resets the current value of all properties on that display list (resetting is @@ -15147,7 +15389,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * playing the application would like to go into a complete full-screen mode, * to use as much of the display as possible for the video. When in this state * the user can not interact with the application; the system intercepts - * touching on the screen to pop the UI out of full screen mode. + * touching on the screen to pop the UI out of full screen mode. See + * {@link #fitSystemWindows(Rect)} for a sample layout that goes with this code. * * {@sample development/samples/ApiDemos/src/com/example/android/apis/view/VideoPlayerActivity.java * content} @@ -15229,11 +15472,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } - void updateLocalSystemUiVisibility(int localValue, int localChanges) { + boolean updateLocalSystemUiVisibility(int localValue, int localChanges) { int val = (mSystemUiVisibility&~localChanges) | (localValue&localChanges); if (val != mSystemUiVisibility) { setSystemUiVisibility(val); + return true; } + return false; } /** @hide */ @@ -16632,7 +16877,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Interface definition for a callback to be invoked when the status bar changes * visibility. This reports <strong>global</strong> changes to the system UI - * state, not just what the application is requesting. + * state, not what the application is requesting. * * @see View#setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener) */ @@ -16641,10 +16886,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Called when the status bar changes visibility because of a call to * {@link View#setSystemUiVisibility(int)}. * - * @param visibility Bitwise-or of flags {@link #SYSTEM_UI_FLAG_LOW_PROFILE} or - * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}. This tells you the - * <strong>global</strong> state of the UI visibility flags, not what your - * app is currently applying. + * @param visibility Bitwise-or of flags {@link #SYSTEM_UI_FLAG_LOW_PROFILE}, + * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, and {@link #SYSTEM_UI_FLAG_FULLSCREEN}. + * This tells you the <strong>global</strong> state of these UI visibility + * flags, not what your app is currently applying. */ public void onSystemUiVisibilityChange(int visibility); } @@ -16930,6 +17175,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal int mDisabledSystemUiVisibility; /** + * Last global system UI visibility reported by the window manager. + */ + int mGlobalSystemUiVisibility; + + /** * True if a view in this hierarchy has an OnSystemUiVisibilityChangeListener * attached. */ @@ -17021,7 +17271,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Show where the margins, bounds and layout bounds are for each view. */ - final boolean mDebugLayout = SystemProperties.getBoolean(DEBUG_LAYOUT_PROPERTY, false); + boolean mDebugLayout = SystemProperties.getBoolean(DEBUG_LAYOUT_PROPERTY, false); /** * Point used to compute visible regions. @@ -17253,6 +17503,26 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** + * Performs the specified accessibility action on the view. For + * possible accessibility actions look at {@link AccessibilityNodeInfo}. + * <p> + * The default implementation behaves as + * {@link View#performAccessibilityAction(int, Bundle) + * View#performAccessibilityAction(int, Bundle)} for the case of + * no accessibility delegate been set. + * </p> + * + * @param action The action to perform. + * @return Whether the action was performed. + * + * @see View#performAccessibilityAction(int, Bundle) + * View#performAccessibilityAction(int, Bundle) + */ + public boolean performAccessibilityAction(View host, int action, Bundle args) { + return host.performAccessibilityActionInternal(action, args); + } + + /** * Sends an accessibility event. This method behaves exactly as * {@link #sendAccessibilityEvent(View, int)} but takes as an argument an * empty {@link AccessibilityEvent} and does not perform a check whether diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 29613d5..b3c8895 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -1317,15 +1317,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } @Override - void updateLocalSystemUiVisibility(int localValue, int localChanges) { - super.updateLocalSystemUiVisibility(localValue, localChanges); + boolean updateLocalSystemUiVisibility(int localValue, int localChanges) { + boolean changed = super.updateLocalSystemUiVisibility(localValue, localChanges); final int count = mChildrenCount; final View[] children = mChildren; for (int i=0; i <count; i++) { final View child = children[i]; - child.updateLocalSystemUiVisibility(localValue, localChanges); + changed |= child.updateLocalSystemUiVisibility(localValue, localChanges); } + return changed; } /** @@ -3586,6 +3587,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager clearChildFocus = true; } + view.clearAccessibilityFocus(); + cancelTouchTarget(view); cancelHoverTarget(view); @@ -3669,6 +3672,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager clearChildFocus = view; } + view.clearAccessibilityFocus(); + cancelTouchTarget(view); cancelHoverTarget(view); @@ -3742,6 +3747,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager clearChildFocus = view; } + view.clearAccessibilityFocus(); + cancelTouchTarget(view); cancelHoverTarget(view); @@ -3790,6 +3797,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager child.clearFocus(); } + child.clearAccessibilityFocus(); + cancelTouchTarget(child); cancelHoverTarget(child); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index ec6bd81..1fcb2c3 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -328,8 +328,6 @@ public final class ViewRootImpl implements ViewParent, private final int mDensity; - final KeyCharacterMap.FallbackAction mFallbackAction = new KeyCharacterMap.FallbackAction(); - /** * Consistency verifier for debugging purposes. */ @@ -408,6 +406,7 @@ public final class ViewRootImpl implements ViewParent, PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mAttachInfo.mScreenOn = powerManager.isScreenOn(); + loadSystemProperties(); } /** @@ -846,6 +845,16 @@ public final class ViewRootImpl implements ViewParent, scheduleTraversals(); } + void invalidateWorld(View view) { + view.invalidate(); + if (view instanceof ViewGroup) { + ViewGroup parent = (ViewGroup)view; + for (int i=0; i<parent.getChildCount(); i++) { + invalidateWorld(parent.getChildAt(i)); + } + } + } + public void invalidateChild(View child, Rect dirty) { invalidateChildInParent(null, dirty); } @@ -2373,7 +2382,7 @@ public final class ViewRootImpl implements ViewParent, } } else { if (mAccessibilityFocusedVirtualView == null) { - mAccessibilityFocusedVirtualView = provider.findAccessibilitiyFocus(View.NO_ID); + mAccessibilityFocusedVirtualView = provider.findAccessibilityFocus(View.NO_ID); } if (mAccessibilityFocusedVirtualView == null) { return; @@ -2730,6 +2739,7 @@ public final class ViewRootImpl implements ViewParent, private final static int MSG_INVALIDATE_DISPLAY_LIST = 21; private final static int MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST = 22; private final static int MSG_DISPATCH_DONE_ANIMATING = 23; + private final static int MSG_INVALIDATE_WORLD = 24; final class ViewRootHandler extends Handler { @Override @@ -2997,6 +3007,9 @@ public final class ViewRootImpl implements ViewParent, case MSG_DISPATCH_DONE_ANIMATING: { handleDispatchDoneAnimating(); } break; + case MSG_INVALIDATE_WORLD: { + invalidateWorld(mView); + } break; } } } @@ -3782,13 +3795,15 @@ public final class ViewRootImpl implements ViewParent, } if (mView == null) return; if (args.localChanges != 0) { - if (mAttachInfo != null) { - mAttachInfo.mRecomputeGlobalAttributes = true; - } mView.updateLocalSystemUiVisibility(args.localValue, args.localChanges); - scheduleTraversals(); } - mView.dispatchSystemUiVisibilityChanged(args.globalVisibility); + if (mAttachInfo != null) { + int visibility = args.globalVisibility&View.SYSTEM_UI_CLEARABLE_FLAGS; + if (visibility != mAttachInfo.mGlobalSystemUiVisibility) { + mAttachInfo.mGlobalSystemUiVisibility = visibility; + mView.dispatchSystemUiVisibilityChanged(visibility); + } + } } public void handleDispatchDoneAnimating() { @@ -4016,6 +4031,17 @@ public final class ViewRootImpl implements ViewParent, mHandler.sendMessage(msg); } + public void loadSystemProperties() { + boolean layout = SystemProperties.getBoolean( + View.DEBUG_LAYOUT_PROPERTY, false); + if (layout != mAttachInfo.mDebugLayout) { + mAttachInfo.mDebugLayout = layout; + if (!mHandler.hasMessages(MSG_INVALIDATE_WORLD)) { + mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_WORLD, 200); + } + } + } + private void destroyHardwareRenderer() { AttachInfo attachInfo = mAttachInfo; HardwareRenderer hardwareRenderer = attachInfo.mHardwareRenderer; @@ -4412,14 +4438,23 @@ public final class ViewRootImpl implements ViewParent, mInvalidateOnAnimationRunnable.addViewRect(info); } - public void invalidateDisplayList(DisplayList displayList) { + public void enqueueDisplayList(DisplayList displayList) { mDisplayLists.add(displayList); mHandler.removeMessages(MSG_INVALIDATE_DISPLAY_LIST); Message msg = mHandler.obtainMessage(MSG_INVALIDATE_DISPLAY_LIST); mHandler.sendMessage(msg); } - + + public void dequeueDisplayList(DisplayList displayList) { + if (mDisplayLists.remove(displayList)) { + displayList.invalidate(); + if (mDisplayLists.size() == 0) { + mHandler.removeMessages(MSG_INVALIDATE_DISPLAY_LIST); + } + } + } + public void cancelInvalidate(View view) { mHandler.removeMessages(MSG_INVALIDATE, view); // fixme: might leak the AttachInfo.InvalidateInfo objects instead of returning @@ -4446,20 +4481,19 @@ public final class ViewRootImpl implements ViewParent, final int keyCode = event.getKeyCode(); final int metaState = event.getMetaState(); - KeyEvent fallbackEvent = null; - synchronized (mFallbackAction) { - // Check for fallback actions specified by the key character map. - if (kcm.getFallbackAction(keyCode, metaState, mFallbackAction)) { - int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK; - fallbackEvent = KeyEvent.obtain( - event.getDownTime(), event.getEventTime(), - event.getAction(), mFallbackAction.keyCode, - event.getRepeatCount(), mFallbackAction.metaState, - event.getDeviceId(), event.getScanCode(), - flags, event.getSource(), null); - } - } - if (fallbackEvent != null) { + // Check for fallback actions specified by the key character map. + KeyCharacterMap.FallbackAction fallbackAction = + kcm.getFallbackAction(keyCode, metaState); + if (fallbackAction != null) { + final int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK; + KeyEvent fallbackEvent = KeyEvent.obtain( + event.getDownTime(), event.getEventTime(), + event.getAction(), fallbackAction.keyCode, + event.getRepeatCount(), fallbackAction.metaState, + event.getDeviceId(), event.getScanCode(), + flags, event.getSource(), null); + fallbackAction.recycle(); + dispatchKey(fallbackEvent); } } diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java index 1c5d436..6a8a60a 100644 --- a/core/java/android/view/ViewTreeObserver.java +++ b/core/java/android/view/ViewTreeObserver.java @@ -20,7 +20,6 @@ import android.graphics.Rect; import android.graphics.Region; import java.util.ArrayList; -import java.util.concurrent.CopyOnWriteArrayList; /** * A view tree observer is used to register listeners that can be notified of global @@ -32,12 +31,12 @@ import java.util.concurrent.CopyOnWriteArrayList; * for more information. */ public final class ViewTreeObserver { - private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners; - private CopyOnWriteArrayList<OnGlobalLayoutListener> mOnGlobalLayoutListeners; - private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners; - private CopyOnWriteArrayList<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners; - private CopyOnWriteArrayList<OnScrollChangedListener> mOnScrollChangedListeners; - private ArrayList<OnPreDrawListener> mOnPreDrawListeners; + private CopyOnWriteArray<OnGlobalFocusChangeListener> mOnGlobalFocusListeners; + private CopyOnWriteArray<OnGlobalLayoutListener> mOnGlobalLayoutListeners; + private CopyOnWriteArray<OnTouchModeChangeListener> mOnTouchModeChangeListeners; + private CopyOnWriteArray<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners; + private CopyOnWriteArray<OnScrollChangedListener> mOnScrollChangedListeners; + private CopyOnWriteArray<OnPreDrawListener> mOnPreDrawListeners; private ArrayList<OnDrawListener> mOnDrawListeners; private boolean mAlive = true; @@ -147,7 +146,7 @@ public final class ViewTreeObserver { * windows behind it should be placed. */ public final Rect contentInsets = new Rect(); - + /** * Offsets from the frame of the window at which windows behind it * are visible. @@ -166,13 +165,13 @@ public final class ViewTreeObserver { * can be touched. */ public static final int TOUCHABLE_INSETS_FRAME = 0; - + /** * Option for {@link #setTouchableInsets(int)}: the area inside of * the content insets can be touched. */ public static final int TOUCHABLE_INSETS_CONTENT = 1; - + /** * Option for {@link #setTouchableInsets(int)}: the area inside of * the visible insets can be touched. @@ -195,7 +194,7 @@ public final class ViewTreeObserver { } int mTouchableInsets; - + void reset() { contentInsets.setEmpty(); visibleInsets.setEmpty(); @@ -231,7 +230,7 @@ public final class ViewTreeObserver { mTouchableInsets = other.mTouchableInsets; } } - + /** * Interface definition for a callback to be invoked when layout has * completed and the client can compute its interior insets. @@ -328,7 +327,7 @@ public final class ViewTreeObserver { checkIsAlive(); if (mOnGlobalFocusListeners == null) { - mOnGlobalFocusListeners = new CopyOnWriteArrayList<OnGlobalFocusChangeListener>(); + mOnGlobalFocusListeners = new CopyOnWriteArray<OnGlobalFocusChangeListener>(); } mOnGlobalFocusListeners.add(listener); @@ -363,7 +362,7 @@ public final class ViewTreeObserver { checkIsAlive(); if (mOnGlobalLayoutListeners == null) { - mOnGlobalLayoutListeners = new CopyOnWriteArrayList<OnGlobalLayoutListener>(); + mOnGlobalLayoutListeners = new CopyOnWriteArray<OnGlobalLayoutListener>(); } mOnGlobalLayoutListeners.add(listener); @@ -413,7 +412,7 @@ public final class ViewTreeObserver { checkIsAlive(); if (mOnPreDrawListeners == null) { - mOnPreDrawListeners = new ArrayList<OnPreDrawListener>(); + mOnPreDrawListeners = new CopyOnWriteArray<OnPreDrawListener>(); } mOnPreDrawListeners.add(listener); @@ -485,7 +484,7 @@ public final class ViewTreeObserver { checkIsAlive(); if (mOnScrollChangedListeners == null) { - mOnScrollChangedListeners = new CopyOnWriteArrayList<OnScrollChangedListener>(); + mOnScrollChangedListeners = new CopyOnWriteArray<OnScrollChangedListener>(); } mOnScrollChangedListeners.add(listener); @@ -519,7 +518,7 @@ public final class ViewTreeObserver { checkIsAlive(); if (mOnTouchModeChangeListeners == null) { - mOnTouchModeChangeListeners = new CopyOnWriteArrayList<OnTouchModeChangeListener>(); + mOnTouchModeChangeListeners = new CopyOnWriteArray<OnTouchModeChangeListener>(); } mOnTouchModeChangeListeners.add(listener); @@ -558,7 +557,7 @@ public final class ViewTreeObserver { if (mOnComputeInternalInsetsListeners == null) { mOnComputeInternalInsetsListeners = - new CopyOnWriteArrayList<OnComputeInternalInsetsListener>(); + new CopyOnWriteArray<OnComputeInternalInsetsListener>(); } mOnComputeInternalInsetsListeners.add(listener); @@ -622,10 +621,16 @@ public final class ViewTreeObserver { // perform the dispatching. The iterator is a safe guard against listeners that // could mutate the list by calling the various add/remove methods. This prevents // the array from being modified while we iterate it. - final CopyOnWriteArrayList<OnGlobalFocusChangeListener> listeners = mOnGlobalFocusListeners; + final CopyOnWriteArray<OnGlobalFocusChangeListener> listeners = mOnGlobalFocusListeners; if (listeners != null && listeners.size() > 0) { - for (OnGlobalFocusChangeListener listener : listeners) { - listener.onGlobalFocusChanged(oldFocus, newFocus); + CopyOnWriteArray.Access<OnGlobalFocusChangeListener> access = listeners.start(); + try { + int count = access.size(); + for (int i = 0; i < count; i++) { + access.get(i).onGlobalFocusChanged(oldFocus, newFocus); + } + } finally { + listeners.end(); } } } @@ -640,10 +645,16 @@ public final class ViewTreeObserver { // perform the dispatching. The iterator is a safe guard against listeners that // could mutate the list by calling the various add/remove methods. This prevents // the array from being modified while we iterate it. - final CopyOnWriteArrayList<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners; + final CopyOnWriteArray<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners; if (listeners != null && listeners.size() > 0) { - for (OnGlobalLayoutListener listener : listeners) { - listener.onGlobalLayout(); + CopyOnWriteArray.Access<OnGlobalLayoutListener> access = listeners.start(); + try { + int count = access.size(); + for (int i = 0; i < count; i++) { + access.get(i).onGlobalLayout(); + } + } finally { + listeners.end(); } } } @@ -658,17 +669,17 @@ public final class ViewTreeObserver { */ @SuppressWarnings("unchecked") public final boolean dispatchOnPreDraw() { - // NOTE: we *must* clone the listener list to perform the dispatching. - // The clone is a safe guard against listeners that - // could mutate the list by calling the various add/remove methods. This prevents - // the array from being modified while we process it. boolean cancelDraw = false; - if (mOnPreDrawListeners != null && mOnPreDrawListeners.size() > 0) { - final ArrayList<OnPreDrawListener> listeners = - (ArrayList<OnPreDrawListener>) mOnPreDrawListeners.clone(); - int numListeners = listeners.size(); - for (int i = 0; i < numListeners; ++i) { - cancelDraw |= !(listeners.get(i).onPreDraw()); + final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners; + if (listeners != null && listeners.size() > 0) { + CopyOnWriteArray.Access<OnPreDrawListener> access = listeners.start(); + try { + int count = access.size(); + for (int i = 0; i < count; i++) { + cancelDraw |= !(access.get(i).onPreDraw()); + } + } finally { + listeners.end(); } } return cancelDraw; @@ -693,11 +704,17 @@ public final class ViewTreeObserver { * @param inTouchMode True if the touch mode is now enabled, false otherwise. */ final void dispatchOnTouchModeChanged(boolean inTouchMode) { - final CopyOnWriteArrayList<OnTouchModeChangeListener> listeners = + final CopyOnWriteArray<OnTouchModeChangeListener> listeners = mOnTouchModeChangeListeners; if (listeners != null && listeners.size() > 0) { - for (OnTouchModeChangeListener listener : listeners) { - listener.onTouchModeChanged(inTouchMode); + CopyOnWriteArray.Access<OnTouchModeChangeListener> access = listeners.start(); + try { + int count = access.size(); + for (int i = 0; i < count; i++) { + access.get(i).onTouchModeChanged(inTouchMode); + } + } finally { + listeners.end(); } } } @@ -710,10 +727,16 @@ public final class ViewTreeObserver { // perform the dispatching. The iterator is a safe guard against listeners that // could mutate the list by calling the various add/remove methods. This prevents // the array from being modified while we iterate it. - final CopyOnWriteArrayList<OnScrollChangedListener> listeners = mOnScrollChangedListeners; + final CopyOnWriteArray<OnScrollChangedListener> listeners = mOnScrollChangedListeners; if (listeners != null && listeners.size() > 0) { - for (OnScrollChangedListener listener : listeners) { - listener.onScrollChanged(); + CopyOnWriteArray.Access<OnScrollChangedListener> access = listeners.start(); + try { + int count = access.size(); + for (int i = 0; i < count; i++) { + access.get(i).onScrollChanged(); + } + } finally { + listeners.end(); } } } @@ -722,11 +745,11 @@ public final class ViewTreeObserver { * Returns whether there are listeners for computing internal insets. */ final boolean hasComputeInternalInsetsListeners() { - final CopyOnWriteArrayList<OnComputeInternalInsetsListener> listeners = + final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners = mOnComputeInternalInsetsListeners; return (listeners != null && listeners.size() > 0); } - + /** * Calls all listeners to compute the current insets. */ @@ -735,12 +758,105 @@ public final class ViewTreeObserver { // perform the dispatching. The iterator is a safe guard against listeners that // could mutate the list by calling the various add/remove methods. This prevents // the array from being modified while we iterate it. - final CopyOnWriteArrayList<OnComputeInternalInsetsListener> listeners = + final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners = mOnComputeInternalInsetsListeners; if (listeners != null && listeners.size() > 0) { - for (OnComputeInternalInsetsListener listener : listeners) { - listener.onComputeInternalInsets(inoutInfo); + CopyOnWriteArray.Access<OnComputeInternalInsetsListener> access = listeners.start(); + try { + int count = access.size(); + for (int i = 0; i < count; i++) { + access.get(i).onComputeInternalInsets(inoutInfo); + } + } finally { + listeners.end(); + } + } + } + + /** + * Copy on write array. This array is not thread safe, and only one loop can + * iterate over this array at any given time. This class avoids allocations + * until a concurrent modification happens. + * + * Usage: + * + * CopyOnWriteArray.Access<MyData> access = array.start(); + * try { + * for (int i = 0; i < access.size(); i++) { + * MyData d = access.get(i); + * } + * } finally { + * access.end(); + * } + */ + static class CopyOnWriteArray<T> { + private ArrayList<T> mData = new ArrayList<T>(); + private ArrayList<T> mDataCopy; + + private final Access<T> mAccess = new Access<T>(); + + private boolean mStart; + + static class Access<T> { + private ArrayList<T> mData; + private int mSize; + + T get(int index) { + return mData.get(index); } + + int size() { + return mSize; + } + } + + CopyOnWriteArray() { + } + + private ArrayList<T> getArray() { + if (mStart) { + if (mDataCopy == null) mDataCopy = new ArrayList<T>(mData); + return mDataCopy; + } + return mData; + } + + Access<T> start() { + if (mStart) throw new IllegalStateException("Iteration already started"); + mStart = true; + mDataCopy = null; + mAccess.mData = mData; + mAccess.mSize = mData.size(); + return mAccess; + } + + void end() { + if (!mStart) throw new IllegalStateException("Iteration not started"); + mStart = false; + if (mDataCopy != null) { + mData = mDataCopy; + } + mDataCopy = null; + } + + int size() { + return getArray().size(); + } + + void add(T item) { + getArray().add(item); + } + + void addAll(CopyOnWriteArray<T> array) { + getArray().addAll(array.mData); + } + + void remove(T item) { + getArray().remove(item); + } + + void clear() { + getArray().clear(); } } } diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index b5690e9..5d33cec 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -23,6 +23,7 @@ import android.content.res.Configuration; import android.graphics.PixelFormat; import android.opengl.ManagedEGLContext; import android.os.IBinder; +import android.os.SystemProperties; import android.util.AndroidRuntimeException; import android.util.Log; import android.view.inputmethod.InputMethodManager; @@ -112,6 +113,8 @@ public class WindowManagerImpl implements WindowManager { private WindowManager.LayoutParams[] mParams; private boolean mNeedsEglTerminate; + private Runnable mSystemPropertyUpdater = null; + private final static Object sLock = new Object(); private final static WindowManagerImpl sWindowManager = new WindowManagerImpl(); private final static HashMap<CompatibilityInfo, WindowManager> sCompatWindowManagers @@ -237,6 +240,22 @@ public class WindowManagerImpl implements WindowManager { View panelParentView = null; synchronized (this) { + // Start watching for system property changes. + if (mSystemPropertyUpdater == null) { + mSystemPropertyUpdater = new Runnable() { + @Override public void run() { + synchronized (this) { + synchronized (this) { + for (ViewRootImpl root : mRoots) { + root.loadSystemProperties(); + } + } + } + } + }; + SystemProperties.addChangeCallback(mSystemPropertyUpdater); + } + // Here's an odd/questionable case: if someone tries to add a // view multiple times, then we simply bump up a nesting count // and they need to remove the view the corresponding number of diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index e725e75..388cfb3 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -1017,7 +1017,7 @@ public interface WindowManagerPolicy { /** * Called when we have finished booting and can now display the home - * screen to the user. This wilWl happen after systemReady(), and at + * screen to the user. This will happen after systemReady(), and at * this point the display is active. */ public void enableScreenAfterBoot(); diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java index c28b220..4c34dd4 100755 --- a/core/java/android/view/WindowOrientationListener.java +++ b/core/java/android/view/WindowOrientationListener.java @@ -21,6 +21,7 @@ import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; +import android.os.SystemProperties; import android.util.FloatMath; import android.util.Log; import android.util.Slog; @@ -34,20 +35,15 @@ import android.util.Slog; * "App/Activity/Screen Orientation" to ensure that all orientation * modes still work correctly. * - * You can also visualize the behavior of the WindowOrientationListener by - * enabling the window orientation listener log using the Development Settings - * in the Dev Tools application (Development.apk) - * and running frameworks/base/tools/orientationplot/orientationplot.py. - * - * More information about how to tune this algorithm in - * frameworks/base/tools/orientationplot/README.txt. + * You can also visualize the behavior of the WindowOrientationListener. + * Refer to frameworks/base/tools/orientationplot/README.txt for details. * * @hide */ public abstract class WindowOrientationListener { private static final String TAG = "WindowOrientationListener"; - private static final boolean DEBUG = false; - private static final boolean localLOGV = DEBUG || false; + private static final boolean LOG = SystemProperties.getBoolean( + "debug.orientation.log", false); private static final boolean USE_GRAVITY_SENSOR = false; @@ -56,7 +52,6 @@ public abstract class WindowOrientationListener { private int mRate; private Sensor mSensor; private SensorEventListenerImpl mSensorEventListener; - boolean mLogEnabled; int mCurrentRotation = -1; /** @@ -100,7 +95,9 @@ public abstract class WindowOrientationListener { return; } if (mEnabled == false) { - if (localLOGV) Log.d(TAG, "WindowOrientationListener enabled"); + if (LOG) { + Log.d(TAG, "WindowOrientationListener enabled"); + } mSensorManager.registerListener(mSensorEventListener, mSensor, mRate); mEnabled = true; } @@ -115,7 +112,9 @@ public abstract class WindowOrientationListener { return; } if (mEnabled == true) { - if (localLOGV) Log.d(TAG, "WindowOrientationListener disabled"); + if (LOG) { + Log.d(TAG, "WindowOrientationListener disabled"); + } mSensorManager.unregisterListener(mSensorEventListener); mEnabled = false; } @@ -165,16 +164,6 @@ public abstract class WindowOrientationListener { public abstract void onProposedRotationChanged(int rotation); /** - * Enables or disables the window orientation listener logging for use with - * the orientationplot.py tool. - * Logging is usually enabled via Development Settings. (See class comments.) - * @param enable True to enable logging. - */ - public void setLogEnabled(boolean enable) { - mLogEnabled = enable; - } - - /** * This class filters the raw accelerometer data and tries to detect actual changes in * orientation. This is a very ill-defined problem so there are a lot of tweakable parameters, * but here's the outline: @@ -238,11 +227,16 @@ public abstract class WindowOrientationListener { // can change. private static final long PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS = 500 * NANOS_PER_MS; - // The mininum amount of time that must have elapsed since the device stopped + // The minimum amount of time that must have elapsed since the device stopped // swinging (time since device appeared to be in the process of being put down // or put away into a pocket) before the proposed rotation can change. private static final long PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS = 300 * NANOS_PER_MS; + // The minimum amount of time that must have elapsed since the device stopped + // undergoing external acceleration before the proposed rotation can change. + private static final long PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS = + 500 * NANOS_PER_MS; + // If the tilt angle remains greater than the specified angle for a minimum of // the specified time, then the device is deemed to be lying flat // (just chillin' on a table). @@ -300,10 +294,15 @@ public abstract class WindowOrientationListener { // singularities in the tilt and orientation calculations. // // In both cases, we postpone choosing an orientation. + // + // However, we need to tolerate some acceleration because the angular momentum + // of turning the device can skew the observed acceleration for a short period of time. + private static final float NEAR_ZERO_MAGNITUDE = 1; // m/s^2 + private static final float ACCELERATION_TOLERANCE = 4; // m/s^2 private static final float MIN_ACCELERATION_MAGNITUDE = - SensorManager.STANDARD_GRAVITY * 0.3f; + SensorManager.STANDARD_GRAVITY - ACCELERATION_TOLERANCE; private static final float MAX_ACCELERATION_MAGNITUDE = - SensorManager.STANDARD_GRAVITY * 1.25f; + SensorManager.STANDARD_GRAVITY + ACCELERATION_TOLERANCE; // Maximum absolute tilt angle at which to consider orientation data. Beyond this (i.e. // when screen is facing the sky or ground), we completely ignore orientation data. @@ -353,6 +352,9 @@ public abstract class WindowOrientationListener { // Timestamp when the device last appeared to be swinging. private long mSwingTimestampNanos; + // Timestamp when the device last appeared to be undergoing external acceleration. + private long mAccelerationTimestampNanos; + // History of observed tilt angles. private static final int TILT_HISTORY_SIZE = 40; private float[] mTiltHistory = new float[TILT_HISTORY_SIZE]; @@ -374,15 +376,13 @@ public abstract class WindowOrientationListener { @Override public void onSensorChanged(SensorEvent event) { - final boolean log = mOrientationListener.mLogEnabled; - // The vector given in the SensorEvent points straight up (towards the sky) under ideal // conditions (the phone is not accelerating). I'll call this up vector elsewhere. float x = event.values[ACCELEROMETER_DATA_X]; float y = event.values[ACCELEROMETER_DATA_Y]; float z = event.values[ACCELEROMETER_DATA_Z]; - if (log) { + if (LOG) { Slog.v(TAG, "Raw acceleration vector: " + "x=" + x + ", y=" + y + ", z=" + z + ", magnitude=" + FloatMath.sqrt(x * x + y * y + z * z)); @@ -399,7 +399,7 @@ public abstract class WindowOrientationListener { if (now < then || now > then + MAX_FILTER_DELTA_TIME_NANOS || (x == 0 && y == 0 && z == 0)) { - if (log) { + if (LOG) { Slog.v(TAG, "Resetting orientation listener."); } reset(); @@ -409,7 +409,7 @@ public abstract class WindowOrientationListener { x = alpha * (x - mLastFilteredX) + mLastFilteredX; y = alpha * (y - mLastFilteredY) + mLastFilteredY; z = alpha * (z - mLastFilteredZ) + mLastFilteredZ; - if (log) { + if (LOG) { Slog.v(TAG, "Filtered acceleration vector: " + "x=" + x + ", y=" + y + ", z=" + z + ", magnitude=" + FloatMath.sqrt(x * x + y * y + z * z)); @@ -421,18 +421,24 @@ public abstract class WindowOrientationListener { mLastFilteredY = y; mLastFilteredZ = z; + boolean isAccelerating = false; boolean isFlat = false; boolean isSwinging = false; if (!skipSample) { // Calculate the magnitude of the acceleration vector. final float magnitude = FloatMath.sqrt(x * x + y * y + z * z); - if (magnitude < MIN_ACCELERATION_MAGNITUDE - || magnitude > MAX_ACCELERATION_MAGNITUDE) { - if (log) { - Slog.v(TAG, "Ignoring sensor data, magnitude out of range."); + if (magnitude < NEAR_ZERO_MAGNITUDE) { + if (LOG) { + Slog.v(TAG, "Ignoring sensor data, magnitude too close to zero."); } clearPredictedRotation(); } else { + // Determine whether the device appears to be undergoing external acceleration. + if (isAccelerating(magnitude)) { + isAccelerating = true; + mAccelerationTimestampNanos = now; + } + // Calculate the tilt angle. // This is the angle between the up vector and the x-y plane (the plane of // the screen) in a range of [-90, 90] degrees. @@ -441,6 +447,7 @@ public abstract class WindowOrientationListener { // 90 degrees: screen horizontal and facing the sky (on table) final int tiltAngle = (int) Math.round( Math.asin(z / magnitude) * RADIANS_TO_DEGREES); + addTiltHistoryEntry(now, tiltAngle); // Determine whether the device appears to be flat or swinging. if (isFlat(now)) { @@ -451,12 +458,11 @@ public abstract class WindowOrientationListener { isSwinging = true; mSwingTimestampNanos = now; } - addTiltHistoryEntry(now, tiltAngle); // If the tilt angle is too close to horizontal then we cannot determine // the orientation angle of the screen. if (Math.abs(tiltAngle) > MAX_TILT) { - if (log) { + if (LOG) { Slog.v(TAG, "Ignoring sensor data, tilt angle too high: " + "tiltAngle=" + tiltAngle); } @@ -483,7 +489,7 @@ public abstract class WindowOrientationListener { && isOrientationAngleAcceptable(nearestRotation, orientationAngle)) { updatePredictedRotation(now, nearestRotation); - if (log) { + if (LOG) { Slog.v(TAG, "Predicted: " + "tiltAngle=" + tiltAngle + ", orientationAngle=" + orientationAngle @@ -493,7 +499,7 @@ public abstract class WindowOrientationListener { * 0.000001f)); } } else { - if (log) { + if (LOG) { Slog.v(TAG, "Ignoring sensor data, no predicted rotation: " + "tiltAngle=" + tiltAngle + ", orientationAngle=" + orientationAngle); @@ -511,15 +517,18 @@ public abstract class WindowOrientationListener { } // Write final statistics about where we are in the orientation detection process. - if (log) { + if (LOG) { Slog.v(TAG, "Result: currentRotation=" + mOrientationListener.mCurrentRotation + ", proposedRotation=" + mProposedRotation + ", predictedRotation=" + mPredictedRotation + ", timeDeltaMS=" + timeDeltaMS + + ", isAccelerating=" + isAccelerating + ", isFlat=" + isFlat + ", isSwinging=" + isSwinging + ", timeUntilSettledMS=" + remainingMS(now, mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS) + + ", timeUntilAccelerationDelayExpiredMS=" + remainingMS(now, + mAccelerationTimestampNanos + PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS) + ", timeUntilFlatDelayExpiredMS=" + remainingMS(now, mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS) + ", timeUntilSwingDelayExpiredMS=" + remainingMS(now, @@ -528,7 +537,7 @@ public abstract class WindowOrientationListener { // Tell the listener. if (mProposedRotation != oldProposedRotation && mProposedRotation >= 0) { - if (log) { + if (LOG) { Slog.v(TAG, "Proposed rotation changed! proposedRotation=" + mProposedRotation + ", oldProposedRotation=" + oldProposedRotation); } @@ -618,6 +627,12 @@ public abstract class WindowOrientationListener { return false; } + // The last acceleration state must have been sufficiently long ago. + if (now < mAccelerationTimestampNanos + + PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS) { + return false; + } + // Looks good! return true; } @@ -627,6 +642,7 @@ public abstract class WindowOrientationListener { mProposedRotation = -1; mFlatTimestampNanos = Long.MIN_VALUE; mSwingTimestampNanos = Long.MIN_VALUE; + mAccelerationTimestampNanos = Long.MIN_VALUE; clearPredictedRotation(); clearTiltHistory(); } @@ -643,6 +659,11 @@ public abstract class WindowOrientationListener { } } + private boolean isAccelerating(float magnitude) { + return magnitude < MIN_ACCELERATION_MAGNITUDE + || magnitude > MAX_ACCELERATION_MAGNITUDE; + } + private void clearTiltHistory() { mTiltHistoryTimestampNanos[0] = Long.MIN_VALUE; mTiltHistoryIndex = 1; diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index f70ffa9..1a2a194 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -236,12 +236,19 @@ import java.util.List; * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> - * <li>{@link #getText()} - The text of the current text at the movement granularity.</li> + * <li>{@link #getMovementGranularity()} - Sets the granularity at which a view's text + * was traversed.</li> + * <li>{@link #getText()} - The text of the source's sub-tree.</li> + * <li>{@link #getFromIndex()} - The start of the next/previous text at the specified granularity + * - inclusive.</li> + * <li>{@link #getToIndex()} - The end of the next/previous text at the specified granularity + * - exclusive.</li> * <li>{@link #isPassword()} - Whether the source is password.</li> * <li>{@link #isEnabled()} - Whether the source is enabled.</li> * <li>{@link #getContentDescription()} - The content description of the source.</li> * <li>{@link #getMovementGranularity()} - Sets the granularity at which a view's text * was traversed.</li> + * <li>{@link #getAction()} - Gets traversal action which specifies the direction.</li> * </ul> * </p> * <p> @@ -635,6 +642,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par private CharSequence mPackageName; private long mEventTime; int mMovementGranularity; + int mAction; private final ArrayList<AccessibilityRecord> mRecords = new ArrayList<AccessibilityRecord>(); @@ -653,6 +661,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par super.init(event); mEventType = event.mEventType; mMovementGranularity = event.mMovementGranularity; + mAction = event.mAction; mEventTime = event.mEventTime; mPackageName = event.mPackageName; } @@ -791,6 +800,27 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par } /** + * Sets the performed action that triggered this event. + * + * @param action The action. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setAction(int action) { + enforceNotSealed(); + mAction = action; + } + + /** + * Gets the performed action that triggered this event. + * + * @return The action. + */ + public int getAction() { + return mAction; + } + + /** * Returns a cached instance if such is available or a new one is * instantiated with its type property set. * @@ -879,6 +909,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par super.clear(); mEventType = 0; mMovementGranularity = 0; + mAction = 0; mPackageName = null; mEventTime = 0; while (!mRecords.isEmpty()) { @@ -896,6 +927,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par mSealed = (parcel.readInt() == 1); mEventType = parcel.readInt(); mMovementGranularity = parcel.readInt(); + mAction = parcel.readInt(); mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); mEventTime = parcel.readLong(); mConnectionId = parcel.readInt(); @@ -947,6 +979,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par parcel.writeInt(isSealed() ? 1 : 0); parcel.writeInt(mEventType); parcel.writeInt(mMovementGranularity); + parcel.writeInt(mAction); TextUtils.writeToParcel(mPackageName, parcel, 0); parcel.writeLong(mEventTime); parcel.writeInt(mConnectionId); @@ -1004,6 +1037,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par builder.append("; EventTime: ").append(mEventTime); builder.append("; PackageName: ").append(mPackageName); builder.append("; MovementGranularity: ").append(mMovementGranularity); + builder.append("; Action: ").append(mAction); builder.append(super.toString()); if (DEBUG) { builder.append("\n"); diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index c0696a9..0517d4b 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -102,12 +102,12 @@ public class AccessibilityNodeInfo implements Parcelable { public static final int ACTION_CLEAR_SELECTION = 0x00000008; /** - * Action that long clicks on the node info. + * Action that clicks on the node info. */ public static final int ACTION_CLICK = 0x00000010; /** - * Action that clicks on the node. + * Action that long clicks on the node. */ public static final int ACTION_LONG_CLICK = 0x00000020; @@ -205,6 +205,16 @@ public class AccessibilityNodeInfo implements Parcelable { public static final int ACTION_PREVIOUS_HTML_ELEMENT = 0x00000800; /** + * Action to scroll the node content forward. + */ + public static final int ACTION_SCROLL_FORWARD = 0x00001000; + + /** + * Action to scroll the node content backward. + */ + public static final int ACTION_SCROLL_BACKWARD = 0x00002000; + + /** * Argument for which movement granularity to be used when traversing the node text. * <p> * <strong>Type:</strong> int<br> @@ -287,6 +297,8 @@ public class AccessibilityNodeInfo implements Parcelable { private static final int PROPERTY_ACCESSIBILITY_FOCUSED = 0x00000400; + private static final int PROPERTY_VISIBLE_TO_USER = 0x00000800; + /** * Bits that provide the id of a virtual descendant of a view. */ @@ -567,6 +579,16 @@ public class AccessibilityNodeInfo implements Parcelable { * @see AccessibilityNodeInfo#ACTION_CLEAR_FOCUS * @see AccessibilityNodeInfo#ACTION_SELECT * @see AccessibilityNodeInfo#ACTION_CLEAR_SELECTION + * @see AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS + * @see AccessibilityNodeInfo#ACTION_CLEAR_ACCESSIBILITY_FOCUS + * @see AccessibilityNodeInfo#ACTION_CLICK + * @see AccessibilityNodeInfo#ACTION_LONG_CLICK + * @see AccessibilityNodeInfo#ACTION_NEXT_AT_MOVEMENT_GRANULARITY + * @see AccessibilityNodeInfo#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY + * @see AccessibilityNodeInfo#ACTION_NEXT_HTML_ELEMENT + * @see AccessibilityNodeInfo#ACTION_PREVIOUS_HTML_ELEMENT + * @see AccessibilityNodeInfo#ACTION_SCROLL_FORWARD + * @see AccessibilityNodeInfo#ACTION_SCROLL_BACKWARD */ public int getActions() { return mActions; @@ -910,6 +932,31 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Sets whether this node is visible to the user. + * + * @return Whether the node is visible to the user. + */ + public boolean isVisibleToUser() { + return getBooleanProperty(PROPERTY_VISIBLE_TO_USER); + } + + /** + * Sets whether this node is visible to the user. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param visibleToUser Whether the node is visible to the user. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setVisibleToUser(boolean visibleToUser) { + setBooleanProperty(PROPERTY_VISIBLE_TO_USER, visibleToUser); + } + + /** * Gets whether this node is accessibility focused. * * @return True if the node is accessibility focused. @@ -1551,6 +1598,10 @@ public class AccessibilityNodeInfo implements Parcelable { return "ACTION_NEXT_HTML_ELEMENT"; case ACTION_PREVIOUS_HTML_ELEMENT: return "ACTION_PREVIOUS_HTML_ELEMENT"; + case ACTION_SCROLL_FORWARD: + return "ACTION_SCROLL_FORWARD"; + case ACTION_SCROLL_BACKWARD: + return "ACTION_SCROLL_BACKWARD"; default: throw new IllegalArgumentException("Unknown action: " + action); } diff --git a/core/java/android/view/accessibility/AccessibilityNodeProvider.java b/core/java/android/view/accessibility/AccessibilityNodeProvider.java index a2e0d88..e60716d 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeProvider.java +++ b/core/java/android/view/accessibility/AccessibilityNodeProvider.java @@ -151,7 +151,7 @@ public abstract class AccessibilityNodeProvider { * @see #createAccessibilityNodeInfo(int) * @see AccessibilityNodeInfo */ - public AccessibilityNodeInfo findAccessibilitiyFocus(int virtualViewId) { + public AccessibilityNodeInfo findAccessibilityFocus(int virtualViewId) { return null; } diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java index c22750e..b7c94a3 100644 --- a/core/java/android/view/inputmethod/InputMethodSubtype.java +++ b/core/java/android/view/inputmethod/InputMethodSubtype.java @@ -58,7 +58,7 @@ public final class InputMethodSubtype implements Parcelable { private final String mSubtypeLocale; private final String mSubtypeMode; private final String mSubtypeExtraValue; - private HashMap<String, String> mExtraValueHashMapCache; + private volatile HashMap<String, String> mExtraValueHashMapCache; /** * Constructor. @@ -237,18 +237,22 @@ public final class InputMethodSubtype implements Parcelable { private HashMap<String, String> getExtraValueHashMap() { if (mExtraValueHashMapCache == null) { - mExtraValueHashMapCache = new HashMap<String, String>(); - final String[] pairs = mSubtypeExtraValue.split(EXTRA_VALUE_PAIR_SEPARATOR); - final int N = pairs.length; - for (int i = 0; i < N; ++i) { - final String[] pair = pairs[i].split(EXTRA_VALUE_KEY_VALUE_SEPARATOR); - if (pair.length == 1) { - mExtraValueHashMapCache.put(pair[0], null); - } else if (pair.length > 1) { - if (pair.length > 2) { - Slog.w(TAG, "ExtraValue has two or more '='s"); + synchronized(this) { + if (mExtraValueHashMapCache == null) { + mExtraValueHashMapCache = new HashMap<String, String>(); + final String[] pairs = mSubtypeExtraValue.split(EXTRA_VALUE_PAIR_SEPARATOR); + final int N = pairs.length; + for (int i = 0; i < N; ++i) { + final String[] pair = pairs[i].split(EXTRA_VALUE_KEY_VALUE_SEPARATOR); + if (pair.length == 1) { + mExtraValueHashMapCache.put(pair[0], null); + } else if (pair.length > 1) { + if (pair.length > 2) { + Slog.w(TAG, "ExtraValue has two or more '='s"); + } + mExtraValueHashMapCache.put(pair[0], pair[1]); + } } - mExtraValueHashMapCache.put(pair[0], pair[1]); } } } diff --git a/core/java/android/webkit/WebHistoryItem.java b/core/java/android/webkit/WebHistoryItem.java index ccf3d6b..788d05c 100644 --- a/core/java/android/webkit/WebHistoryItem.java +++ b/core/java/android/webkit/WebHistoryItem.java @@ -32,12 +32,8 @@ public class WebHistoryItem implements Cloneable { private static int sNextId = 0; // Unique identifier. private final int mId; - // The title of this item's document. - private String mTitle; - // The base url of this item. - private String mUrl; - // The original requested url of this item. - private String mOriginalUrl; + // A point to a native WebHistoryItem instance which contains the actual data + private int mNativeBridge; // The favicon for this item. private Bitmap mFavicon; // The pre-flattened data used for saving the state. @@ -55,10 +51,19 @@ public class WebHistoryItem implements Cloneable { * Basic constructor that assigns a unique id to the item. Called by JNI * only. */ - private WebHistoryItem() { + private WebHistoryItem(int nativeBridge) { synchronized (WebHistoryItem.class) { mId = sNextId++; } + mNativeBridge = nativeBridge; + nativeRef(mNativeBridge); + } + + protected void finalize() throws Throwable { + if (mNativeBridge != 0) { + nativeUnref(mNativeBridge); + mNativeBridge = 0; + } } /** @@ -66,7 +71,6 @@ public class WebHistoryItem implements Cloneable { * @param data The pre-flattened data coming from restoreState. */ /*package*/ WebHistoryItem(byte[] data) { - mUrl = null; // This will be updated natively mFlattenedData = data; synchronized (WebHistoryItem.class) { mId = sNextId++; @@ -78,12 +82,14 @@ public class WebHistoryItem implements Cloneable { * @param item The history item to clone. */ private WebHistoryItem(WebHistoryItem item) { - mUrl = item.mUrl; - mTitle = item.mTitle; mFlattenedData = item.mFlattenedData; - mFavicon = item.mFavicon; mId = item.mId; -} + mFavicon = item.mFavicon; + mNativeBridge = item.mNativeBridge; + if (mNativeBridge != 0) { + nativeRef(mNativeBridge); + } + } /** * Return an identifier for this history item. If an item is a copy of @@ -106,7 +112,8 @@ public class WebHistoryItem implements Cloneable { * to synchronize this method. */ public String getUrl() { - return mUrl; + if (mNativeBridge == 0) return null; + return nativeGetUrl(mNativeBridge); } /** @@ -116,7 +123,8 @@ public class WebHistoryItem implements Cloneable { * @return The original url of this history item. */ public String getOriginalUrl() { - return mOriginalUrl; + if (mNativeBridge == 0) return null; + return nativeGetOriginalUrl(mNativeBridge); } /** @@ -126,7 +134,8 @@ public class WebHistoryItem implements Cloneable { * to synchronize this method. */ public String getTitle() { - return mTitle; + if (mNativeBridge == 0) return null; + return nativeGetTitle(mNativeBridge); } /** @@ -136,6 +145,9 @@ public class WebHistoryItem implements Cloneable { * to synchronize this method. */ public Bitmap getFavicon() { + if (mFavicon == null && mNativeBridge != 0) { + mFavicon = nativeGetFavicon(mNativeBridge); + } return mFavicon; } @@ -156,7 +168,7 @@ public class WebHistoryItem implements Cloneable { } try { - URL url = new URL(mOriginalUrl); + URL url = new URL(getOriginalUrl()); mTouchIconUrlServerDefault = new URL(url.getProtocol(), url.getHost(), url.getPort(), "/apple-touch-icon.png").toString(); } catch (MalformedURLException e) { @@ -214,6 +226,9 @@ public class WebHistoryItem implements Cloneable { * to synchronize this method. */ /*package*/ byte[] getFlattenedData() { + if (mNativeBridge != 0) { + return nativeGetFlattenedData(mNativeBridge); + } return mFlattenedData; } @@ -223,7 +238,8 @@ public class WebHistoryItem implements Cloneable { * to synchronize this method. */ /*package*/ void inflate(int nativeFrame) { - inflate(nativeFrame, mFlattenedData); + mNativeBridge = inflate(nativeFrame, mFlattenedData); + mFlattenedData = null; } /** @@ -235,15 +251,13 @@ public class WebHistoryItem implements Cloneable { /* Natively inflate this item, this method is called in the WebCore thread. */ - private native void inflate(int nativeFrame, byte[] data); + private native int inflate(int nativeFrame, byte[] data); + private native void nativeRef(int nptr); + private native void nativeUnref(int nptr); + private native String nativeGetTitle(int nptr); + private native String nativeGetUrl(int nptr); + private native String nativeGetOriginalUrl(int nptr); + private native byte[] nativeGetFlattenedData(int nptr); + private native Bitmap nativeGetFavicon(int nptr); - /* Called by jni when the item is updated */ - private void update(String url, String originalUrl, String title, - Bitmap favicon, byte[] data) { - mUrl = url; - mOriginalUrl = originalUrl; - mTitle = title; - mFavicon = favicon; - mFlattenedData = data; - } } diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java index eb5f835..057c3d1 100644 --- a/core/java/android/webkit/WebViewClassic.java +++ b/core/java/android/webkit/WebViewClassic.java @@ -149,24 +149,6 @@ import java.util.regex.Pattern; @SuppressWarnings("deprecation") public final class WebViewClassic implements WebViewProvider, WebViewProvider.ScrollDelegate, WebViewProvider.ViewDelegate { - private class InnerGlobalLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener { - @Override - public void onGlobalLayout() { - if (mWebView.isShown()) { - setInvScreenRect(); - } - } - } - - private class InnerScrollChangedListener implements ViewTreeObserver.OnScrollChangedListener { - @Override - public void onScrollChanged() { - if (mWebView.isShown()) { - setInvScreenRect(); - } - } - } - /** * InputConnection used for ContentEditable. This captures changes * to the text and sends them either as key strokes or text changes. @@ -617,12 +599,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } } - // The listener to capture global layout change event. - private InnerGlobalLayoutListener mGlobalLayoutListener = null; - - // The listener to capture scroll event. - private InnerScrollChangedListener mScrollChangedListener = null; - // if AUTO_REDRAW_HACK is true, then the CALL key will toggle redrawing // the screen all-the-time. Good for profiling our drawing code static private final boolean AUTO_REDRAW_HACK = false; @@ -633,6 +609,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc // The presumed scroll rate for the first scroll of edit text static private final long TEXT_SCROLL_FIRST_SCROLL_MS = 16; + // Buffer pixels of the caret rectangle when moving edit text into view + // after resize. + static private final int EDIT_RECT_BUFFER = 10; + // true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK private boolean mAutoRedraw; @@ -647,7 +627,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private final Rect mInvScreenRect = new Rect(); private final Rect mScreenRect = new Rect(); private final RectF mVisibleContentRect = new RectF(); - private boolean mGLViewportEmpty = false; + private boolean mIsWebViewVisible = true; WebViewInputConnection mInputConnection = null; private int mFieldPointer; private PastePopupWindow mPasteWindow; @@ -2024,11 +2004,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } private void destroyImpl() { - int drawGLFunction = nativeGetDrawGLFunction(mNativeClass); - ViewRootImpl viewRoot = mWebView.getViewRootImpl(); - Log.d(LOGTAG, String.format("destroyImpl, drawGLFunction %x, viewroot == null %b, isHWAccel %b", - drawGLFunction, (viewRoot == null), mWebView.isHardwareAccelerated())); - mCallbackProxy.blockMessages(); clearHelpers(); if (mListBoxDialog != null) { @@ -2979,7 +2954,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc // updated when we get out of that mode. if (!mDrawHistory) { // repin our scroll, taking into account the new content size - updateScrollCoordinates(pinLocX(getScrollX()), pinLocY(getScrollY())); + if (updateScrollCoordinates(pinLocX(getScrollX()), + pinLocY(getScrollY()))) { + invalidate(); + } if (!mScroller.isFinished()) { // We are in the middle of a scroll. Repin the final scroll // position. @@ -3050,21 +3028,14 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc r.bottom = viewToContentY(r.bottom); } - private Rect mContentVisibleRect = new Rect(); + private final Rect mTempContentVisibleRect = new Rect(); // Sets r to be our visible rectangle in content coordinates. We use this // method on the native side to compute the position of the fixed layers. // Uses floating coordinates (necessary to correctly place elements when // the scale factor is not 1) private void calcOurContentVisibleRectF(RectF r) { - calcOurVisibleRect(mContentVisibleRect); - r.left = viewToContentXf(mContentVisibleRect.left) / mWebView.getScaleX(); - // viewToContentY will remove the total height of the title bar. Add - // the visible height back in to account for the fact that if the title - // bar is partially visible, the part of the visible rect which is - // displaying our content is displaced by that amount. - r.top = viewToContentYf(mContentVisibleRect.top + getVisibleTitleHeightImpl()) / mWebView.getScaleY(); - r.right = viewToContentXf(mContentVisibleRect.right) / mWebView.getScaleX(); - r.bottom = viewToContentYf(mContentVisibleRect.bottom) / mWebView.getScaleY(); + calcOurVisibleRect(mTempContentVisibleRect); + viewToContentVisibleRect(r, mTempContentVisibleRect); } static class ViewSizeData { @@ -3327,7 +3298,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } public int getPageBackgroundColor() { - return nativeGetBackgroundColor(); + if (mNativeClass == 0) return Color.WHITE; + return nativeGetBackgroundColor(mNativeClass); } /** @@ -4224,8 +4196,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc calcOurContentVisibleRectF(mVisibleContentRect); if (canvas.isHardwareAccelerated()) { - Rect invScreenRect = mGLViewportEmpty ? null : mInvScreenRect; - Rect screenRect = mGLViewportEmpty ? null : mScreenRect; + Rect invScreenRect = mIsWebViewVisible ? mInvScreenRect : null; + Rect screenRect = mIsWebViewVisible ? mScreenRect : null; int functor = nativeCreateDrawGLFunction(mNativeClass, invScreenRect, screenRect, mVisibleContentRect, getScale(), extras); @@ -4484,7 +4456,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc if (mNativeClass == 0) { return 0; } - return nativeGetBaseLayer(); + return nativeGetBaseLayer(mNativeClass); } private void onZoomAnimationStart() { @@ -5405,15 +5377,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc @Override public void onAttachedToWindow() { if (mWebView.hasWindowFocus()) setActive(true); - final ViewTreeObserver treeObserver = mWebView.getViewTreeObserver(); - if (mGlobalLayoutListener == null) { - mGlobalLayoutListener = new InnerGlobalLayoutListener(); - treeObserver.addOnGlobalLayoutListener(mGlobalLayoutListener); - } - if (mScrollChangedListener == null) { - mScrollChangedListener = new InnerScrollChangedListener(); - treeObserver.addOnScrollChangedListener(mScrollChangedListener); - } addAccessibilityApisToJavaScript(); @@ -5426,24 +5389,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc mZoomManager.dismissZoomPicker(); if (mWebView.hasWindowFocus()) setActive(false); - final ViewTreeObserver treeObserver = mWebView.getViewTreeObserver(); - if (mGlobalLayoutListener != null) { - treeObserver.removeGlobalOnLayoutListener(mGlobalLayoutListener); - mGlobalLayoutListener = null; - } - if (mScrollChangedListener != null) { - treeObserver.removeOnScrollChangedListener(mScrollChangedListener); - mScrollChangedListener = null; - } - removeAccessibilityApisFromJavaScript(); updateHwAccelerated(); - int drawGLFunction = nativeGetDrawGLFunction(mNativeClass); - ViewRootImpl viewRoot = mWebView.getViewRootImpl(); - Log.d(LOGTAG, String.format("onDetachedFromWindow, drawGLFunction %x, viewroot == null %b, isHWAccel %b", - drawGLFunction, (viewRoot == null), mWebView.isHardwareAccelerated())); if (mWebView.isHardwareAccelerated()) { + int drawGLFunction = nativeGetDrawGLFunction(mNativeClass); + ViewRootImpl viewRoot = mWebView.getViewRootImpl(); if (drawGLFunction != 0 && viewRoot != null) { viewRoot.detachFunctor(drawGLFunction); } @@ -5547,11 +5498,18 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } } - void setInvScreenRect() { + // updateRectsForGL() happens almost every draw call, in order to avoid creating + // any object in this code path, we move the local variable out to be a private + // final member, and we marked them as mTemp*. + private final Point mTempVisibleRectOffset = new Point(); + private final Rect mTempVisibleRect = new Rect(); + + void updateRectsForGL() { // Use the getGlobalVisibleRect() to get the intersection among the parents // visible == false means we're clipped - send a null rect down to indicate that // we should not draw - boolean visible = mWebView.getGlobalVisibleRect(mInvScreenRect); + boolean visible = mWebView.getGlobalVisibleRect(mTempVisibleRect, mTempVisibleRectOffset); + mInvScreenRect.set(mTempVisibleRect); if (visible) { // Then need to invert the Y axis, just for GL View rootView = mWebView.getRootView(); @@ -5560,16 +5518,33 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc int savedWebViewBottom = mInvScreenRect.bottom; mInvScreenRect.bottom = rootViewHeight - mInvScreenRect.top - getVisibleTitleHeightImpl(); mInvScreenRect.top = rootViewHeight - savedWebViewBottom; - mGLViewportEmpty = false; + mIsWebViewVisible = true; } else { - mGLViewportEmpty = true; + mIsWebViewVisible = false; } - calcOurContentVisibleRectF(mVisibleContentRect); - nativeUpdateDrawGLFunction(mNativeClass, mGLViewportEmpty ? null : mInvScreenRect, - mGLViewportEmpty ? null : mScreenRect, + + mTempVisibleRect.offset(-mTempVisibleRectOffset.x, -mTempVisibleRectOffset.y); + viewToContentVisibleRect(mVisibleContentRect, mTempVisibleRect); + + nativeUpdateDrawGLFunction(mNativeClass, mIsWebViewVisible ? mInvScreenRect : null, + mIsWebViewVisible ? mScreenRect : null, mVisibleContentRect, getScale()); } + // Input : viewRect, rect in view/screen coordinate. + // Output: contentRect, rect in content/document coordinate. + private void viewToContentVisibleRect(RectF contentRect, Rect viewRect) { + contentRect.left = viewToContentXf(viewRect.left) / mWebView.getScaleX(); + // viewToContentY will remove the total height of the title bar. Add + // the visible height back in to account for the fact that if the title + // bar is partially visible, the part of the visible rect which is + // displaying our content is displaced by that amount. + contentRect.top = viewToContentYf(viewRect.top + getVisibleTitleHeightImpl()) + / mWebView.getScaleY(); + contentRect.right = viewToContentXf(viewRect.right) / mWebView.getScaleX(); + contentRect.bottom = viewToContentYf(viewRect.bottom) / mWebView.getScaleY(); + } + @Override public boolean setFrame(int left, int top, int right, int bottom) { boolean changed = mWebViewPrivate.super_setFrame(left, top, right, bottom); @@ -5582,7 +5557,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc // notify the WebKit about the new dimensions. sendViewSizeZoom(false); } - setInvScreenRect(); + updateRectsForGL(); return changed; } @@ -5604,9 +5579,76 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc // However, do not update the base layer as that hasn't changed setNewPicture(mLoadedPicture, false); } + if (mIsEditingText) { + scrollEditIntoView(); + } relocateAutoCompletePopup(); } + /** + * Scrolls the edit field into view using the minimum scrolling necessary. + * If the edit field is too large to fit in the visible window, the caret + * dimensions are used so that at least the caret is visible. + * A buffer of EDIT_RECT_BUFFER in view pixels is used to offset the + * edit rectangle to ensure a margin with the edge of the screen. + */ + private void scrollEditIntoView() { + Rect visibleRect = new Rect(viewToContentX(getScrollX()), + viewToContentY(getScrollY()), + viewToContentX(getScrollX() + getWidth()), + viewToContentY(getScrollY() + getViewHeightWithTitle())); + if (visibleRect.contains(mEditTextContentBounds)) { + return; // no need to scroll + } + syncSelectionCursors(); + final int buffer = Math.max(1, viewToContentDimension(EDIT_RECT_BUFFER)); + Rect showRect = new Rect( + Math.max(0, mEditTextContentBounds.left - buffer), + Math.max(0, mEditTextContentBounds.top - buffer), + mEditTextContentBounds.right + buffer, + mEditTextContentBounds.bottom + buffer); + Point caretTop = calculateCaretTop(); + if (visibleRect.width() < mEditTextContentBounds.width()) { + // The whole edit won't fit in the width, so use the caret rect + if (mSelectCursorBase.x < caretTop.x) { + showRect.left = Math.max(0, mSelectCursorBase.x - buffer); + showRect.right = caretTop.x + buffer; + } else { + showRect.left = Math.max(0, caretTop.x - buffer); + showRect.right = mSelectCursorBase.x + buffer; + } + } + if (visibleRect.height() < mEditTextContentBounds.height()) { + // The whole edit won't fit in the height, so use the caret rect + if (mSelectCursorBase.y > caretTop.y) { + showRect.top = Math.max(0, caretTop.y - buffer); + showRect.bottom = mSelectCursorBase.y + buffer; + } else { + showRect.top = Math.max(0, mSelectCursorBase.y - buffer); + showRect.bottom = caretTop.y + buffer; + } + } + + if (visibleRect.contains(showRect)) { + return; // no need to scroll + } + + int scrollX = visibleRect.left; + if (visibleRect.left > showRect.left) { + scrollX = showRect.left; + } else if (visibleRect.right < showRect.right) { + scrollX = Math.max(0, showRect.right - visibleRect.width()); + } + int scrollY = visibleRect.top; + if (visibleRect.top > showRect.top) { + scrollY = showRect.top; + } else if (visibleRect.bottom < showRect.bottom) { + scrollY = Math.max(0, showRect.bottom - visibleRect.height()); + } + + contentScrollTo(scrollX, scrollY, false); + } + @Override public void onScrollChanged(int l, int t, int oldl, int oldt) { if (!mInOverScrollMode) { @@ -8616,7 +8658,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private native void nativeSetHeightCanMeasure(boolean measure); private native boolean nativeSetBaseLayer(int nativeInstance, int layer, boolean showVisualIndicator, boolean isPictureAfterFirstLayout); - private native int nativeGetBaseLayer(); + private native int nativeGetBaseLayer(int nativeInstance); private native void nativeCopyBaseContentToPicture(Picture pict); private native boolean nativeHasContent(); private native void nativeStopGL(); @@ -8644,7 +8686,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ private native boolean nativeScrollLayer(int nativeInstance, int layer, int newX, int newY); private native void nativeSetIsScrolling(boolean isScrolling); - private native int nativeGetBackgroundColor(); + private native int nativeGetBackgroundColor(int nativeInstance); native boolean nativeSetProperty(String key, String value); native String nativeGetProperty(String key); /** diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 661bbf8..4adfd6a 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -2176,11 +2176,12 @@ public final class WebViewCore { DrawData mLastDrawData = null; - private Boolean m_skipDrawFlag = false; + private Object m_skipDrawFlagLock = new Object(); + private boolean m_skipDrawFlag = false; private boolean m_drawWasSkipped = false; void pauseWebKitDraw() { - synchronized (m_skipDrawFlag) { + synchronized (m_skipDrawFlagLock) { if (!m_skipDrawFlag) { m_skipDrawFlag = true; } @@ -2188,7 +2189,7 @@ public final class WebViewCore { } void resumeWebKitDraw() { - synchronized (m_skipDrawFlag) { + synchronized (m_skipDrawFlagLock) { if (m_skipDrawFlag && m_drawWasSkipped) { // a draw was dropped, send a retry m_drawWasSkipped = false; @@ -2199,7 +2200,7 @@ public final class WebViewCore { } private void webkitDraw() { - synchronized (m_skipDrawFlag) { + synchronized (m_skipDrawFlagLock) { if (m_skipDrawFlag) { m_drawWasSkipped = true; return; diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 449870e..c4e1bf5 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -25,6 +25,7 @@ import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.TransitionDrawable; +import android.os.Bundle; import android.os.Debug; import android.os.Handler; import android.os.Parcel; @@ -57,6 +58,7 @@ import android.view.ViewGroup; import android.view.ViewParent; import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; @@ -648,6 +650,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te private int mGlowPaddingLeft; private int mGlowPaddingRight; + /** + * Used for interacting with list items from an accessibility service. + */ + private ListItemAccessibilityDelegate mAccessibilityDelegate; + private int mLastAccessibilityScrollEventFromIndex; private int mLastAccessibilityScrollEventToIndex; @@ -1348,6 +1355,33 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(AbsListView.class.getName()); + if (getFirstVisiblePosition() > 0) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + } + if (getLastVisiblePosition() < getCount() - 1) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + } + } + + @Override + public boolean performAccessibilityAction(int action, Bundle arguments) { + switch (action) { + case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { + if (getLastVisiblePosition() < getCount() - 1) { + final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom; + smoothScrollBy(viewportHeight, PositionScroller.SCROLL_DURATION); + return true; + } + } return false; + case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { + if (mFirstPosition > 0) { + final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom; + smoothScrollBy(-viewportHeight, PositionScroller.SCROLL_DURATION); + return true; + } + } return false; + } + return super.performAccessibilityAction(action, arguments); } /** @@ -2121,9 +2155,77 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te child.setLayoutParams(lp); } + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + if (mAccessibilityDelegate == null) { + mAccessibilityDelegate = new ListItemAccessibilityDelegate(); + } + child.setAccessibilityDelegate(mAccessibilityDelegate); + } + return child; } + class ListItemAccessibilityDelegate extends AccessibilityDelegate { + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + + final int position = getPositionForView(host); + + if (position == INVALID_POSITION) { + return; + } + + if (isClickable()) { + info.addAction(AccessibilityNodeInfo.ACTION_CLICK); + info.setClickable(true); + } + + if (isLongClickable()) { + info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK); + info.setLongClickable(true); + } + + info.addAction(AccessibilityNodeInfo.ACTION_SELECT); + + if (position == getSelectedItemPosition()) { + info.setSelected(true); + } + } + + @Override + public boolean performAccessibilityAction(View host, int action, Bundle arguments) { + final int position = getPositionForView(host); + + if (position == INVALID_POSITION) { + return false; + } + + final long id = getItemIdAtPosition(position); + + switch (action) { + case AccessibilityNodeInfo.ACTION_SELECT: + setSelection(position); + return true; + case AccessibilityNodeInfo.ACTION_CLICK: + if (!super.performAccessibilityAction(host, action, arguments)) { + return performItemClick(host, position, id); + } + return true; + case AccessibilityNodeInfo.ACTION_LONG_CLICK: + if (!super.performAccessibilityAction(host, action, arguments)) { + return performLongPress(host, position, id); + } + return true; + case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: + smoothScrollToPosition(position); + break; + } + + return super.performAccessibilityAction(host, action, arguments); + } + } + void positionSelector(int position, View sel) { if (position != INVALID_POSITION) { mSelectorPosition = position; @@ -3985,7 +4087,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } class PositionScroller implements Runnable { - private static final int SCROLL_DURATION = 400; + private static final int SCROLL_DURATION = 200; private static final int MOVE_DOWN_POS = 1; private static final int MOVE_UP_POS = 2; @@ -4006,21 +4108,37 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength(); } - void start(int position) { + void start(final int position) { stop(); + if (mDataChanged) { + // Wait until we're back in a stable state to try this. + post(new Runnable() { + @Override public void run() { + start(position); + } + }); + return; + } + + final int childCount = getChildCount(); + if (childCount == 0) { + // Can't scroll without children. + return; + } + final int firstPos = mFirstPosition; - final int lastPos = firstPos + getChildCount() - 1; + final int lastPos = firstPos + childCount - 1; int viewTravelCount; - if (position <= firstPos) { + if (position < firstPos) { viewTravelCount = firstPos - position + 1; mMode = MOVE_UP_POS; - } else if (position >= lastPos) { + } else if (position > lastPos) { viewTravelCount = position - lastPos + 1; mMode = MOVE_DOWN_POS; } else { - // Already on screen, nothing to do + scrollToVisible(position, INVALID_POSITION, SCROLL_DURATION); return; } @@ -4036,7 +4154,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te postOnAnimation(this); } - void start(int position, int boundPosition) { + void start(final int position, final int boundPosition) { stop(); if (boundPosition == INVALID_POSITION) { @@ -4044,11 +4162,27 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te return; } + if (mDataChanged) { + // Wait until we're back in a stable state to try this. + post(new Runnable() { + @Override public void run() { + start(position, boundPosition); + } + }); + return; + } + + final int childCount = getChildCount(); + if (childCount == 0) { + // Can't scroll without children. + return; + } + final int firstPos = mFirstPosition; - final int lastPos = firstPos + getChildCount() - 1; + final int lastPos = firstPos + childCount - 1; int viewTravelCount; - if (position <= firstPos) { + if (position < firstPos) { final int boundPosFromLast = lastPos - boundPosition; if (boundPosFromLast < 1) { // Moving would shift our bound position off the screen. Abort. @@ -4064,7 +4198,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te viewTravelCount = posTravel; mMode = MOVE_UP_POS; } - } else if (position >= lastPos) { + } else if (position > lastPos) { final int boundPosFromFirst = boundPosition - firstPos; if (boundPosFromFirst < 1) { // Moving would shift our bound position off the screen. Abort. @@ -4081,7 +4215,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mMode = MOVE_DOWN_POS; } } else { - // Already on screen, nothing to do + scrollToVisible(position, boundPosition, SCROLL_DURATION); return; } @@ -4101,9 +4235,26 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te startWithOffset(position, offset, SCROLL_DURATION); } - void startWithOffset(int position, int offset, int duration) { + void startWithOffset(final int position, int offset, final int duration) { stop(); + if (mDataChanged) { + // Wait until we're back in a stable state to try this. + final int postOffset = offset; + post(new Runnable() { + @Override public void run() { + startWithOffset(position, postOffset, duration); + } + }); + return; + } + + final int childCount = getChildCount(); + if (childCount == 0) { + // Can't scroll without children. + return; + } + offset += getPaddingTop(); mTargetPos = position; @@ -4113,7 +4264,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mMode = MOVE_OFFSET; final int firstPos = mFirstPosition; - final int childCount = getChildCount(); final int lastPos = firstPos + childCount - 1; int viewTravelCount; @@ -4137,6 +4287,60 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te postOnAnimation(this); } + /** + * Scroll such that targetPos is in the visible padded region without scrolling + * boundPos out of view. Assumes targetPos is onscreen. + */ + void scrollToVisible(int targetPos, int boundPos, int duration) { + final int firstPos = mFirstPosition; + final int childCount = getChildCount(); + final int lastPos = firstPos + childCount - 1; + final int paddedTop = mListPadding.top; + final int paddedBottom = getHeight() - mListPadding.bottom; + + if (targetPos < firstPos || targetPos > lastPos) { + Log.w(TAG, "scrollToVisible called with targetPos " + targetPos + + " not visible [" + firstPos + ", " + lastPos + "]"); + } + if (boundPos < firstPos || boundPos > lastPos) { + // boundPos doesn't matter, it's already offscreen. + boundPos = INVALID_POSITION; + } + + final View targetChild = getChildAt(targetPos - firstPos); + final int targetTop = targetChild.getTop(); + final int targetBottom = targetChild.getBottom(); + int scrollBy = 0; + + if (targetBottom > paddedBottom) { + scrollBy = targetBottom - paddedBottom; + } + if (targetTop < paddedTop) { + scrollBy = targetTop - paddedTop; + } + + if (scrollBy == 0) { + return; + } + + if (boundPos >= 0) { + final View boundChild = getChildAt(boundPos - firstPos); + final int boundTop = boundChild.getTop(); + final int boundBottom = boundChild.getBottom(); + final int absScroll = Math.abs(scrollBy); + + if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) { + // Don't scroll the bound view off the bottom of the screen. + scrollBy = Math.max(0, boundBottom - paddedBottom); + } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) { + // Don't scroll the bound view off the top of the screen. + scrollBy = Math.min(0, boundTop - paddedTop); + } + } + + smoothScrollBy(scrollBy, duration); + } + void stop() { removeCallbacks(this); } @@ -4432,7 +4636,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (distance == 0 || mItemCount == 0 || childCount == 0 || (firstPos == 0 && getChildAt(0).getTop() == topLimit && distance < 0) || - (lastPos == mItemCount - 1 && + (lastPos == mItemCount && getChildAt(childCount - 1).getBottom() == bottomLimit && distance > 0)) { mFlingRunnable.endFling(); if (mPositionScroller != null) { @@ -5569,6 +5773,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te // Don't reclaim header or footer views, or views that should be ignored if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) { views.add(child); + child.setAccessibilityDelegate(null); if (listener != null) { // Pretend they went through the scrap heap listener.onMovedToScrapHeap(child); @@ -6124,6 +6329,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mScrapViews[viewType].add(scrap); } + scrap.setAccessibilityDelegate(null); if (mRecyclerListener != null) { mRecyclerListener.onMovedToScrapHeap(scrap); } @@ -6185,6 +6391,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te lp.scrappedFromPosition = mFirstActivePosition + i; scrapViews.add(victim); + victim.setAccessibilityDelegate(null); if (hasListener) { mRecyclerListener.onMovedToScrapHeap(victim); } @@ -6217,6 +6424,16 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te removeDetachedView(scrapPile.remove(size--), false); } } + + if (mTransientStateViews != null) { + for (int i = 0; i < mTransientStateViews.size(); i++) { + final View v = mTransientStateViews.valueAt(i); + if (!v.hasTransientState()) { + mTransientStateViews.removeAt(i); + i--; + } + } + } } /** diff --git a/core/java/android/widget/AccessibilityIterators.java b/core/java/android/widget/AccessibilityIterators.java new file mode 100644 index 0000000..e800e8d --- /dev/null +++ b/core/java/android/widget/AccessibilityIterators.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2012 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.widget; + +import android.graphics.Rect; +import android.text.Layout; +import android.text.Spannable; +import android.view.AccessibilityIterators.AbstractTextSegmentIterator; + +/** + * This class contains the implementation of text segment iterators + * for accessibility support. + */ +final class AccessibilityIterators { + + static class LineTextSegmentIterator extends AbstractTextSegmentIterator { + private static LineTextSegmentIterator sLineInstance; + + protected static final int DIRECTION_START = -1; + protected static final int DIRECTION_END = 1; + + protected Layout mLayout; + + public static LineTextSegmentIterator getInstance() { + if (sLineInstance == null) { + sLineInstance = new LineTextSegmentIterator(); + } + return sLineInstance; + } + + public void initialize(Spannable text, Layout layout) { + mText = text.toString(); + mLayout = layout; + } + + @Override + public int[] following(int offset) { + final int textLegth = mText.length(); + if (textLegth <= 0) { + return null; + } + if (offset >= mText.length()) { + return null; + } + int nextLine = -1; + if (offset < 0) { + nextLine = mLayout.getLineForOffset(0); + } else { + final int currentLine = mLayout.getLineForOffset(offset); + if (currentLine < mLayout.getLineCount() - 1) { + nextLine = currentLine + 1; + } + } + if (nextLine < 0) { + return null; + } + final int start = getLineEdgeIndex(nextLine, DIRECTION_START); + final int end = getLineEdgeIndex(nextLine, DIRECTION_END) + 1; + return getRange(start, end); + } + + @Override + public int[] preceding(int offset) { + final int textLegth = mText.length(); + if (textLegth <= 0) { + return null; + } + if (offset <= 0) { + return null; + } + int previousLine = -1; + if (offset > mText.length()) { + previousLine = mLayout.getLineForOffset(mText.length()); + } else { + final int currentLine = mLayout.getLineForOffset(offset - 1); + if (currentLine > 0) { + previousLine = currentLine - 1; + } + } + if (previousLine < 0) { + return null; + } + final int start = getLineEdgeIndex(previousLine, DIRECTION_START); + final int end = getLineEdgeIndex(previousLine, DIRECTION_END) + 1; + return getRange(start, end); + } + + protected int getLineEdgeIndex(int lineNumber, int direction) { + final int paragraphDirection = mLayout.getParagraphDirection(lineNumber); + if (direction * paragraphDirection < 0) { + return mLayout.getLineStart(lineNumber); + } else { + return mLayout.getLineEnd(lineNumber) - 1; + } + } + } + + static class PageTextSegmentIterator extends LineTextSegmentIterator { + private static PageTextSegmentIterator sPageInstance; + + private TextView mView; + + private final Rect mTempRect = new Rect(); + + public static PageTextSegmentIterator getInstance() { + if (sPageInstance == null) { + sPageInstance = new PageTextSegmentIterator(); + } + return sPageInstance; + } + + public void initialize(TextView view) { + super.initialize((Spannable) view.getIterableTextForAccessibility(), view.getLayout()); + mView = view; + } + + @Override + public int[] following(int offset) { + final int textLegth = mText.length(); + if (textLegth <= 0) { + return null; + } + if (offset >= mText.length()) { + return null; + } + if (!mView.getGlobalVisibleRect(mTempRect)) { + return null; + } + + final int currentLine = mLayout.getLineForOffset(offset); + final int currentLineTop = mLayout.getLineTop(currentLine); + final int pageHeight = mTempRect.height() - mView.getTotalPaddingTop() + - mView.getTotalPaddingBottom(); + + final int nextPageStartLine; + final int nextPageEndLine; + if (offset < 0) { + nextPageStartLine = currentLine; + final int nextPageEndY = currentLineTop + pageHeight; + nextPageEndLine = mLayout.getLineForVertical(nextPageEndY); + } else { + final int nextPageStartY = currentLineTop + pageHeight; + nextPageStartLine = mLayout.getLineForVertical(nextPageStartY) + 1; + if (mLayout.getLineTop(nextPageStartLine) <= nextPageStartY) { + return null; + } + final int nextPageEndY = nextPageStartY + pageHeight; + nextPageEndLine = mLayout.getLineForVertical(nextPageEndY); + } + + final int start = getLineEdgeIndex(nextPageStartLine, DIRECTION_START); + final int end = getLineEdgeIndex(nextPageEndLine, DIRECTION_END) + 1; + + return getRange(start, end); + } + + @Override + public int[] preceding(int offset) { + final int textLegth = mText.length(); + if (textLegth <= 0) { + return null; + } + if (offset <= 0) { + return null; + } + if (!mView.getGlobalVisibleRect(mTempRect)) { + return null; + } + + final int currentLine = mLayout.getLineForOffset(offset); + final int currentLineTop = mLayout.getLineTop(currentLine); + final int pageHeight = mTempRect.height() - mView.getTotalPaddingTop() + - mView.getTotalPaddingBottom(); + + final int previousPageStartLine; + final int previousPageEndLine; + if (offset > mText.length()) { + final int prevousPageStartY = mLayout.getHeight() - pageHeight; + if (prevousPageStartY < 0) { + return null; + } + previousPageStartLine = mLayout.getLineForVertical(prevousPageStartY); + previousPageEndLine = mLayout.getLineCount() - 1; + } else { + final int prevousPageStartY; + if (offset == mText.length()) { + prevousPageStartY = mLayout.getHeight() - 2 * pageHeight; + } else { + prevousPageStartY = currentLineTop - 2 * pageHeight; + } + if (prevousPageStartY < 0) { + return null; + } + previousPageStartLine = mLayout.getLineForVertical(prevousPageStartY); + final int previousPageEndY = prevousPageStartY + pageHeight; + previousPageEndLine = mLayout.getLineForVertical(previousPageEndY) - 1; + } + + final int start = getLineEdgeIndex(previousPageStartLine, DIRECTION_START); + final int end = getLineEdgeIndex(previousPageEndLine, DIRECTION_END) + 1; + + return getRange(start, end); + } + } +} diff --git a/core/java/android/widget/ActivityChooserModel.java b/core/java/android/widget/ActivityChooserModel.java index fba8d3a..c6104bc 100644 --- a/core/java/android/widget/ActivityChooserModel.java +++ b/core/java/android/widget/ActivityChooserModel.java @@ -23,7 +23,6 @@ import android.content.pm.ResolveInfo; import android.database.DataSetObservable; import android.database.DataSetObserver; import android.os.AsyncTask; -import android.os.Handler; import android.text.TextUtils; import android.util.Log; import android.util.Xml; @@ -42,10 +41,8 @@ import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Set; /** * <p> @@ -239,7 +236,7 @@ public class ActivityChooserModel extends DataSetObservable { /** * List of activities that can handle the current intent. */ - private final List<ActivityResolveInfo> mActivites = new ArrayList<ActivityResolveInfo>(); + private final List<ActivityResolveInfo> mActivities = new ArrayList<ActivityResolveInfo>(); /** * List with historical choice records. @@ -278,18 +275,18 @@ public class ActivityChooserModel extends DataSetObservable { /** * Flag whether choice history can be read. In general many clients can - * share the same data model and {@link #readHistoricalData()} may be called + * share the same data model and {@link #readHistoricalDataIfNeeded()} may be called * by arbitrary of them any number of times. Therefore, this class guarantees * that the very first read succeeds and subsequent reads can be performed - * only after a call to {@link #persistHistoricalData()} followed by change + * only after a call to {@link #persistHistoricalDataIfNeeded()} followed by change * of the share records. */ private boolean mCanReadHistoricalData = true; /** * Flag whether the choice history was read. This is used to enforce that - * before calling {@link #persistHistoricalData()} a call to - * {@link #persistHistoricalData()} has been made. This aims to avoid a + * before calling {@link #persistHistoricalDataIfNeeded()} a call to + * {@link #persistHistoricalDataIfNeeded()} has been made. This aims to avoid a * scenario in which a choice history file exits, it is not read yet and * it is overwritten. Note that always all historical records are read in * full and the file is rewritten. This is necessary since we need to @@ -299,16 +296,16 @@ public class ActivityChooserModel extends DataSetObservable { /** * Flag whether the choice records have changed. In general many clients can - * share the same data model and {@link #persistHistoricalData()} may be called + * share the same data model and {@link #persistHistoricalDataIfNeeded()} may be called * by arbitrary of them any number of times. Therefore, this class guarantees * that choice history will be persisted only if it has changed. */ private boolean mHistoricalRecordsChanged = true; /** - * Hander for scheduling work on client tread. + * Flag whether to reload the activities for the current intent. */ - private final Handler mHandler = new Handler(); + private boolean mReloadActivities = false; /** * Policy for controlling how the model handles chosen activities. @@ -346,7 +343,6 @@ public class ActivityChooserModel extends DataSetObservable { dataModel = new ActivityChooserModel(context, historyFileName); sDataModelRegistry.put(historyFileName, dataModel); } - dataModel.readHistoricalData(); return dataModel; } } @@ -383,7 +379,8 @@ public class ActivityChooserModel extends DataSetObservable { return; } mIntent = intent; - loadActivitiesLocked(); + mReloadActivities = true; + ensureConsistentState(); } } @@ -407,7 +404,8 @@ public class ActivityChooserModel extends DataSetObservable { */ public int getActivityCount() { synchronized (mInstanceLock) { - return mActivites.size(); + ensureConsistentState(); + return mActivities.size(); } } @@ -421,7 +419,8 @@ public class ActivityChooserModel extends DataSetObservable { */ public ResolveInfo getActivity(int index) { synchronized (mInstanceLock) { - return mActivites.get(index).resolveInfo; + ensureConsistentState(); + return mActivities.get(index).resolveInfo; } } @@ -433,15 +432,18 @@ public class ActivityChooserModel extends DataSetObservable { * @return The index if found, -1 otherwise. */ public int getActivityIndex(ResolveInfo activity) { - List<ActivityResolveInfo> activities = mActivites; - final int activityCount = activities.size(); - for (int i = 0; i < activityCount; i++) { - ActivityResolveInfo currentActivity = activities.get(i); - if (currentActivity.resolveInfo == activity) { - return i; + synchronized (mInstanceLock) { + ensureConsistentState(); + List<ActivityResolveInfo> activities = mActivities; + final int activityCount = activities.size(); + for (int i = 0; i < activityCount; i++) { + ActivityResolveInfo currentActivity = activities.get(i); + if (currentActivity.resolveInfo == activity) { + return i; + } } + return INVALID_INDEX; } - return INVALID_INDEX; } /** @@ -462,30 +464,34 @@ public class ActivityChooserModel extends DataSetObservable { * @see OnChooseActivityListener */ public Intent chooseActivity(int index) { - ActivityResolveInfo chosenActivity = mActivites.get(index); + synchronized (mInstanceLock) { + ensureConsistentState(); - ComponentName chosenName = new ComponentName( - chosenActivity.resolveInfo.activityInfo.packageName, - chosenActivity.resolveInfo.activityInfo.name); + ActivityResolveInfo chosenActivity = mActivities.get(index); - Intent choiceIntent = new Intent(mIntent); - choiceIntent.setComponent(chosenName); + ComponentName chosenName = new ComponentName( + chosenActivity.resolveInfo.activityInfo.packageName, + chosenActivity.resolveInfo.activityInfo.name); - if (mActivityChoserModelPolicy != null) { - // Do not allow the policy to change the intent. - Intent choiceIntentCopy = new Intent(choiceIntent); - final boolean handled = mActivityChoserModelPolicy.onChooseActivity(this, - choiceIntentCopy); - if (handled) { - return null; + Intent choiceIntent = new Intent(mIntent); + choiceIntent.setComponent(chosenName); + + if (mActivityChoserModelPolicy != null) { + // Do not allow the policy to change the intent. + Intent choiceIntentCopy = new Intent(choiceIntent); + final boolean handled = mActivityChoserModelPolicy.onChooseActivity(this, + choiceIntentCopy); + if (handled) { + return null; + } } - } - HistoricalRecord historicalRecord = new HistoricalRecord(chosenName, - System.currentTimeMillis(), DEFAULT_HISTORICAL_RECORD_WEIGHT); - addHisoricalRecord(historicalRecord); + HistoricalRecord historicalRecord = new HistoricalRecord(chosenName, + System.currentTimeMillis(), DEFAULT_HISTORICAL_RECORD_WEIGHT); + addHisoricalRecord(historicalRecord); - return choiceIntent; + return choiceIntent; + } } /** @@ -494,7 +500,9 @@ public class ActivityChooserModel extends DataSetObservable { * @param listener The listener. */ public void setOnChooseActivityListener(OnChooseActivityListener listener) { - mActivityChoserModelPolicy = listener; + synchronized (mInstanceLock) { + mActivityChoserModelPolicy = listener; + } } /** @@ -508,8 +516,9 @@ public class ActivityChooserModel extends DataSetObservable { */ public ResolveInfo getDefaultActivity() { synchronized (mInstanceLock) { - if (!mActivites.isEmpty()) { - return mActivites.get(0).resolveInfo; + ensureConsistentState(); + if (!mActivities.isEmpty()) { + return mActivities.get(0).resolveInfo; } } return null; @@ -526,72 +535,50 @@ public class ActivityChooserModel extends DataSetObservable { * @param index The index of the activity to set as default. */ public void setDefaultActivity(int index) { - ActivityResolveInfo newDefaultActivity = mActivites.get(index); - ActivityResolveInfo oldDefaultActivity = mActivites.get(0); - - final float weight; - if (oldDefaultActivity != null) { - // Add a record with weight enough to boost the chosen at the top. - weight = oldDefaultActivity.weight - newDefaultActivity.weight - + DEFAULT_ACTIVITY_INFLATION; - } else { - weight = DEFAULT_HISTORICAL_RECORD_WEIGHT; - } - - ComponentName defaultName = new ComponentName( - newDefaultActivity.resolveInfo.activityInfo.packageName, - newDefaultActivity.resolveInfo.activityInfo.name); - HistoricalRecord historicalRecord = new HistoricalRecord(defaultName, - System.currentTimeMillis(), weight); - addHisoricalRecord(historicalRecord); - } - - /** - * Reads the history data from the backing file if the latter - * was provided. Calling this method more than once before a call - * to {@link #persistHistoricalData()} has been made has no effect. - * <p> - * <strong>Note:</strong> Historical data is read asynchronously and - * as soon as the reading is completed any registered - * {@link DataSetObserver}s will be notified. Also no historical - * data is read until this method is invoked. - * <p> - */ - private void readHistoricalData() { synchronized (mInstanceLock) { - if (!mCanReadHistoricalData || !mHistoricalRecordsChanged) { - return; - } - mCanReadHistoricalData = false; - mReadShareHistoryCalled = true; - if (!TextUtils.isEmpty(mHistoryFileName)) { - AsyncTask.SERIAL_EXECUTOR.execute(new HistoryLoader()); + ensureConsistentState(); + + ActivityResolveInfo newDefaultActivity = mActivities.get(index); + ActivityResolveInfo oldDefaultActivity = mActivities.get(0); + + final float weight; + if (oldDefaultActivity != null) { + // Add a record with weight enough to boost the chosen at the top. + weight = oldDefaultActivity.weight - newDefaultActivity.weight + + DEFAULT_ACTIVITY_INFLATION; + } else { + weight = DEFAULT_HISTORICAL_RECORD_WEIGHT; } + + ComponentName defaultName = new ComponentName( + newDefaultActivity.resolveInfo.activityInfo.packageName, + newDefaultActivity.resolveInfo.activityInfo.name); + HistoricalRecord historicalRecord = new HistoricalRecord(defaultName, + System.currentTimeMillis(), weight); + addHisoricalRecord(historicalRecord); } } /** * Persists the history data to the backing file if the latter - * was provided. Calling this method before a call to {@link #readHistoricalData()} + * was provided. Calling this method before a call to {@link #readHistoricalDataIfNeeded()} * throws an exception. Calling this method more than one without choosing an * activity has not effect. * * @throws IllegalStateException If this method is called before a call to - * {@link #readHistoricalData()}. + * {@link #readHistoricalDataIfNeeded()}. */ - private void persistHistoricalData() { - synchronized (mInstanceLock) { - if (!mReadShareHistoryCalled) { - throw new IllegalStateException("No preceding call to #readHistoricalData"); - } - if (!mHistoricalRecordsChanged) { - return; - } - mHistoricalRecordsChanged = false; - mCanReadHistoricalData = true; - if (!TextUtils.isEmpty(mHistoryFileName)) { - AsyncTask.SERIAL_EXECUTOR.execute(new HistoryPersister()); - } + private void persistHistoricalDataIfNeeded() { + if (!mReadShareHistoryCalled) { + throw new IllegalStateException("No preceding call to #readHistoricalData"); + } + if (!mHistoricalRecordsChanged) { + return; + } + mHistoricalRecordsChanged = false; + if (!TextUtils.isEmpty(mHistoryFileName)) { + new PersistHistoryAsyncTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, + new ArrayList<HistoricalRecord>(mHistoricalRecords), mHistoryFileName); } } @@ -608,21 +595,7 @@ public class ActivityChooserModel extends DataSetObservable { return; } mActivitySorter = activitySorter; - sortActivities(); - } - } - - /** - * Sorts the activities based on history and an intent. If - * a sorter is not specified this a default implementation is used. - * - * @see #setActivitySorter(ActivitySorter) - */ - private void sortActivities() { - synchronized (mInstanceLock) { - if (mActivitySorter != null && !mActivites.isEmpty()) { - mActivitySorter.sort(mIntent, mActivites, - Collections.unmodifiableList(mHistoricalRecords)); + if (sortActivitiesIfNeeded()) { notifyChanged(); } } @@ -647,8 +620,10 @@ public class ActivityChooserModel extends DataSetObservable { return; } mHistoryMaxSize = historyMaxSize; - pruneExcessiveHistoricalRecordsLocked(); - sortActivities(); + pruneExcessiveHistoricalRecordsIfNeeded(); + if (sortActivitiesIfNeeded()) { + notifyChanged(); + } } } @@ -670,6 +645,7 @@ public class ActivityChooserModel extends DataSetObservable { */ public int getHistorySize() { synchronized (mInstanceLock) { + ensureConsistentState(); return mHistoricalRecords.size(); } } @@ -681,82 +657,110 @@ public class ActivityChooserModel extends DataSetObservable { } /** - * Adds a historical record. - * - * @param historicalRecord The record to add. - * @return True if the record was added. + * Ensures the model is in a consistent state which is the + * activities for the current intent have been loaded, the + * most recent history has been read, and the activities + * are sorted. */ - private boolean addHisoricalRecord(HistoricalRecord historicalRecord) { - synchronized (mInstanceLock) { - final boolean added = mHistoricalRecords.add(historicalRecord); - if (added) { - mHistoricalRecordsChanged = true; - pruneExcessiveHistoricalRecordsLocked(); - persistHistoricalData(); - sortActivities(); - } - return added; + private void ensureConsistentState() { + boolean stateChanged = loadActivitiesIfNeeded(); + stateChanged |= readHistoricalDataIfNeeded(); + pruneExcessiveHistoricalRecordsIfNeeded(); + if (stateChanged) { + sortActivitiesIfNeeded(); + notifyChanged(); } } /** - * Prunes older excessive records to guarantee {@link #mHistoryMaxSize}. + * Sorts the activities if necessary which is if there is a + * sorter, there are some activities to sort, and there is some + * historical data. + * + * @return Whether sorting was performed. */ - private void pruneExcessiveHistoricalRecordsLocked() { - List<HistoricalRecord> choiceRecords = mHistoricalRecords; - final int pruneCount = choiceRecords.size() - mHistoryMaxSize; - if (pruneCount <= 0) { - return; - } - mHistoricalRecordsChanged = true; - for (int i = 0; i < pruneCount; i++) { - HistoricalRecord prunedRecord = choiceRecords.remove(0); - if (DEBUG) { - Log.i(LOG_TAG, "Pruned: " + prunedRecord); - } + private boolean sortActivitiesIfNeeded() { + if (mActivitySorter != null && mIntent != null + && !mActivities.isEmpty() && !mHistoricalRecords.isEmpty()) { + mActivitySorter.sort(mIntent, mActivities, + Collections.unmodifiableList(mHistoricalRecords)); + return true; } + return false; } /** - * Loads the activities. + * Loads the activities for the current intent if needed which is + * if they are not already loaded for the current intent. + * + * @return Whether loading was performed. */ - private void loadActivitiesLocked() { - mActivites.clear(); - if (mIntent != null) { - List<ResolveInfo> resolveInfos = - mContext.getPackageManager().queryIntentActivities(mIntent, 0); + private boolean loadActivitiesIfNeeded() { + if (mReloadActivities && mIntent != null) { + mReloadActivities = false; + mActivities.clear(); + List<ResolveInfo> resolveInfos = mContext.getPackageManager() + .queryIntentActivities(mIntent, 0); final int resolveInfoCount = resolveInfos.size(); for (int i = 0; i < resolveInfoCount; i++) { ResolveInfo resolveInfo = resolveInfos.get(i); - mActivites.add(new ActivityResolveInfo(resolveInfo)); + mActivities.add(new ActivityResolveInfo(resolveInfo)); } - sortActivities(); - } else { - notifyChanged(); + return true; } + return false; } /** - * Prunes historical records for a package that goes away. + * Reads the historical data if necessary which is it has + * changed, there is a history file, and there is not persist + * in progress. * - * @param packageName The name of the package that goes away. + * @return Whether reading was performed. */ - private void pruneHistoricalRecordsForPackageLocked(String packageName) { - boolean recordsRemoved = false; - - List<HistoricalRecord> historicalRecords = mHistoricalRecords; - for (int i = 0; i < historicalRecords.size(); i++) { - HistoricalRecord historicalRecord = historicalRecords.get(i); - String recordPackageName = historicalRecord.activity.getPackageName(); - if (recordPackageName.equals(packageName)) { - historicalRecords.remove(historicalRecord); - recordsRemoved = true; - } + private boolean readHistoricalDataIfNeeded() { + if (mCanReadHistoricalData && mHistoricalRecordsChanged && + !TextUtils.isEmpty(mHistoryFileName)) { + mCanReadHistoricalData = false; + mReadShareHistoryCalled = true; + readHistoricalDataImpl(); + return true; } + return false; + } - if (recordsRemoved) { + /** + * Adds a historical record. + * + * @param historicalRecord The record to add. + * @return True if the record was added. + */ + private boolean addHisoricalRecord(HistoricalRecord historicalRecord) { + final boolean added = mHistoricalRecords.add(historicalRecord); + if (added) { mHistoricalRecordsChanged = true; - sortActivities(); + pruneExcessiveHistoricalRecordsIfNeeded(); + persistHistoricalDataIfNeeded(); + sortActivitiesIfNeeded(); + notifyChanged(); + } + return added; + } + + /** + * Prunes older excessive records to guarantee maxHistorySize. + */ + private void pruneExcessiveHistoricalRecordsIfNeeded() { + final int pruneCount = mHistoricalRecords.size() - mHistoryMaxSize; + if (pruneCount <= 0) { + return; + } + mHistoricalRecordsChanged = true; + for (int i = 0; i < pruneCount; i++) { + HistoricalRecord prunedRecord = mHistoricalRecords.remove(0); + if (DEBUG) { + Log.i(LOG_TAG, "Pruned: " + prunedRecord); + } } } @@ -974,112 +978,72 @@ public class ActivityChooserModel extends DataSetObservable { /** * Command for reading the historical records from a file off the UI thread. */ - private final class HistoryLoader implements Runnable { - - public void run() { - FileInputStream fis = null; - try { - fis = mContext.openFileInput(mHistoryFileName); - } catch (FileNotFoundException fnfe) { - if (DEBUG) { - Log.i(LOG_TAG, "Could not open historical records file: " + mHistoryFileName); - } - return; + private void readHistoricalDataImpl() { + FileInputStream fis = null; + try { + fis = mContext.openFileInput(mHistoryFileName); + } catch (FileNotFoundException fnfe) { + if (DEBUG) { + Log.i(LOG_TAG, "Could not open historical records file: " + mHistoryFileName); } - try { - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(fis, null); - - int type = XmlPullParser.START_DOCUMENT; - while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { - type = parser.next(); - } - - if (!TAG_HISTORICAL_RECORDS.equals(parser.getName())) { - throw new XmlPullParserException("Share records file does not start with " - + TAG_HISTORICAL_RECORDS + " tag."); - } - - List<HistoricalRecord> readRecords = new ArrayList<HistoricalRecord>(); + return; + } + try { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(fis, null); - while (true) { - type = parser.next(); - if (type == XmlPullParser.END_DOCUMENT) { - break; - } - if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { - continue; - } - String nodeName = parser.getName(); - if (!TAG_HISTORICAL_RECORD.equals(nodeName)) { - throw new XmlPullParserException("Share records file not well-formed."); - } + int type = XmlPullParser.START_DOCUMENT; + while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { + type = parser.next(); + } - String activity = parser.getAttributeValue(null, ATTRIBUTE_ACTIVITY); - final long time = - Long.parseLong(parser.getAttributeValue(null, ATTRIBUTE_TIME)); - final float weight = - Float.parseFloat(parser.getAttributeValue(null, ATTRIBUTE_WEIGHT)); + if (!TAG_HISTORICAL_RECORDS.equals(parser.getName())) { + throw new XmlPullParserException("Share records file does not start with " + + TAG_HISTORICAL_RECORDS + " tag."); + } - HistoricalRecord readRecord = new HistoricalRecord(activity, time, - weight); - readRecords.add(readRecord); + List<HistoricalRecord> historicalRecords = mHistoricalRecords; + historicalRecords.clear(); - if (DEBUG) { - Log.i(LOG_TAG, "Read " + readRecord.toString()); - } + while (true) { + type = parser.next(); + if (type == XmlPullParser.END_DOCUMENT) { + break; } - - if (DEBUG) { - Log.i(LOG_TAG, "Read " + readRecords.size() + " historical records."); + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + String nodeName = parser.getName(); + if (!TAG_HISTORICAL_RECORD.equals(nodeName)) { + throw new XmlPullParserException("Share records file not well-formed."); } - synchronized (mInstanceLock) { - Set<HistoricalRecord> uniqueShareRecords = - new LinkedHashSet<HistoricalRecord>(readRecords); - - // Make sure no duplicates. Example: Read a file with - // one record, add one record, persist the two records, - // add a record, read the persisted records - the - // read two records should not be added again. - List<HistoricalRecord> historicalRecords = mHistoricalRecords; - final int historicalRecordsCount = historicalRecords.size(); - for (int i = historicalRecordsCount - 1; i >= 0; i--) { - HistoricalRecord historicalRecord = historicalRecords.get(i); - uniqueShareRecords.add(historicalRecord); - } - - if (historicalRecords.size() == uniqueShareRecords.size()) { - return; - } + String activity = parser.getAttributeValue(null, ATTRIBUTE_ACTIVITY); + final long time = + Long.parseLong(parser.getAttributeValue(null, ATTRIBUTE_TIME)); + final float weight = + Float.parseFloat(parser.getAttributeValue(null, ATTRIBUTE_WEIGHT)); + HistoricalRecord readRecord = new HistoricalRecord(activity, time, weight); + historicalRecords.add(readRecord); - // Make sure the oldest records go to the end. - historicalRecords.clear(); - historicalRecords.addAll(uniqueShareRecords); - - mHistoricalRecordsChanged = true; - - // Do this on the client thread since the client may be on the UI - // thread, wait for data changes which happen during sorting, and - // perform UI modification based on the data change. - mHandler.post(new Runnable() { - public void run() { - pruneExcessiveHistoricalRecordsLocked(); - sortActivities(); - } - }); + if (DEBUG) { + Log.i(LOG_TAG, "Read " + readRecord.toString()); } - } catch (XmlPullParserException xppe) { - Log.e(LOG_TAG, "Error reading historical recrod file: " + mHistoryFileName, xppe); - } catch (IOException ioe) { - Log.e(LOG_TAG, "Error reading historical recrod file: " + mHistoryFileName, ioe); - } finally { - if (fis != null) { - try { - fis.close(); - } catch (IOException ioe) { - /* ignore */ - } + } + + if (DEBUG) { + Log.i(LOG_TAG, "Read " + historicalRecords.size() + " historical records."); + } + } catch (XmlPullParserException xppe) { + Log.e(LOG_TAG, "Error reading historical recrod file: " + mHistoryFileName, xppe); + } catch (IOException ioe) { + Log.e(LOG_TAG, "Error reading historical recrod file: " + mHistoryFileName, ioe); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException ioe) { + /* ignore */ } } } @@ -1088,21 +1052,21 @@ public class ActivityChooserModel extends DataSetObservable { /** * Command for persisting the historical records to a file off the UI thread. */ - private final class HistoryPersister implements Runnable { + private final class PersistHistoryAsyncTask extends AsyncTask<Object, Void, Void> { - public void run() { - FileOutputStream fos = null; - List<HistoricalRecord> records = null; + @Override + @SuppressWarnings("unchecked") + public Void doInBackground(Object... args) { + List<HistoricalRecord> historicalRecords = (List<HistoricalRecord>) args[0]; + String hostoryFileName = (String) args[1]; - synchronized (mInstanceLock) { - records = new ArrayList<HistoricalRecord>(mHistoricalRecords); - } + FileOutputStream fos = null; try { - fos = mContext.openFileOutput(mHistoryFileName, Context.MODE_PRIVATE); + fos = mContext.openFileOutput(hostoryFileName, Context.MODE_PRIVATE); } catch (FileNotFoundException fnfe) { - Log.e(LOG_TAG, "Error writing historical recrod file: " + mHistoryFileName, fnfe); - return; + Log.e(LOG_TAG, "Error writing historical recrod file: " + hostoryFileName, fnfe); + return null; } XmlSerializer serializer = Xml.newSerializer(); @@ -1112,11 +1076,12 @@ public class ActivityChooserModel extends DataSetObservable { serializer.startDocument("UTF-8", true); serializer.startTag(null, TAG_HISTORICAL_RECORDS); - final int recordCount = records.size(); + final int recordCount = historicalRecords.size(); for (int i = 0; i < recordCount; i++) { - HistoricalRecord record = records.remove(0); + HistoricalRecord record = historicalRecords.remove(0); serializer.startTag(null, TAG_HISTORICAL_RECORD); - serializer.attribute(null, ATTRIBUTE_ACTIVITY, record.activity.flattenToString()); + serializer.attribute(null, ATTRIBUTE_ACTIVITY, + record.activity.flattenToString()); serializer.attribute(null, ATTRIBUTE_TIME, String.valueOf(record.time)); serializer.attribute(null, ATTRIBUTE_WEIGHT, String.valueOf(record.weight)); serializer.endTag(null, TAG_HISTORICAL_RECORD); @@ -1138,6 +1103,7 @@ public class ActivityChooserModel extends DataSetObservable { } catch (IOException ioe) { Log.e(LOG_TAG, "Error writing historical recrod file: " + mHistoryFileName, ioe); } finally { + mCanReadHistoricalData = true; if (fos != null) { try { fos.close(); @@ -1146,6 +1112,7 @@ public class ActivityChooserModel extends DataSetObservable { } } } + return null; } } @@ -1155,33 +1122,8 @@ public class ActivityChooserModel extends DataSetObservable { private final class DataModelPackageMonitor extends PackageMonitor { @Override - public void onPackageAdded(String packageName, int uid) { - synchronized (mInstanceLock) { - loadActivitiesLocked(); - } - } - - @Override - public void onPackageAppeared(String packageName, int reason) { - synchronized (mInstanceLock) { - loadActivitiesLocked(); - } - } - - @Override - public void onPackageRemoved(String packageName, int uid) { - synchronized (mInstanceLock) { - pruneHistoricalRecordsForPackageLocked(packageName); - loadActivitiesLocked(); - } - } - - @Override - public void onPackageDisappeared(String packageName, int reason) { - synchronized (mInstanceLock) { - pruneHistoricalRecordsForPackageLocked(packageName); - loadActivitiesLocked(); - } + public void onSomePackagesChanged() { + mReloadActivities = true; } } } diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index 1986450..ffabd1d 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; +import android.os.Bundle; import android.util.AttributeSet; import android.view.FocusFinder; import android.view.InputDevice; @@ -737,10 +738,42 @@ public class HorizontalScrollView extends FrameLayout { } @Override + public boolean performAccessibilityAction(int action, Bundle arguments) { + switch (action) { + case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { + final int viewportWidth = getWidth() - mPaddingLeft - mPaddingRight; + final int targetScrollX = Math.min(mScrollX + viewportWidth, getScrollRange()); + if (targetScrollX != mScrollX) { + smoothScrollTo(targetScrollX, 0); + return true; + } + } return false; + case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { + final int viewportWidth = getWidth() - mPaddingLeft - mPaddingRight; + final int targetScrollX = Math.max(0, mScrollX - viewportWidth); + if (targetScrollX != mScrollX) { + smoothScrollTo(targetScrollX, 0); + return true; + } + } return false; + } + return super.performAccessibilityAction(action, arguments); + } + + @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(HorizontalScrollView.class.getName()); - info.setScrollable(getScrollRange() > 0); + final int scrollRange = getScrollRange(); + if (scrollRange > 0) { + info.setScrollable(true); + if (mScrollX > 0) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + } + if (mScrollX < scrollRange) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + } + } } @Override diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java index 11d1ed0..6ff924b 100644 --- a/core/java/android/widget/NumberPicker.java +++ b/core/java/android/widget/NumberPicker.java @@ -133,16 +133,6 @@ public class NumberPicker extends LinearLayout { private static final int UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE = 48; /** - * The default unscaled minimal distance for a swipe to be considered a fling. - */ - private static final int UNSCALED_DEFAULT_MIN_FLING_DISTANCE = 150; - - /** - * Coefficient for adjusting touch scroll distance. - */ - private static final float TOUCH_SCROLL_DECELERATION_COEFFICIENT = 2.0f; - - /** * The resource id for the default layout. */ private static final int DEFAULT_LAYOUT_RESOURCE_ID = R.layout.number_picker; @@ -233,11 +223,6 @@ public class NumberPicker extends LinearLayout { private final int mTextSize; /** - * The minimal distance for a swipe to be considered a fling. - */ - private final int mMinFlingDistance; - - /** * The height of the gap between text elements if the selector wheel. */ private int mSelectorTextGapHeight; @@ -298,6 +283,11 @@ public class NumberPicker extends LinearLayout { private final Paint mSelectorWheelPaint; /** + * The {@link Drawable} for pressed virtual (increment/decrement) buttons. + */ + private final Drawable mVirtualButtonPressedDrawable; + + /** * The height of a selector element (text + gap). */ private int mSelectorElementHeight; @@ -435,11 +425,26 @@ public class NumberPicker extends LinearLayout { private int mLastHoveredChildVirtualViewId; /** + * Whether the increment virtual button is pressed. + */ + private boolean mIncrementVirtualButtonPressed; + + /** + * Whether the decrement virtual button is pressed. + */ + private boolean mDecrementVirtualButtonPressed; + + /** * Provider to report to clients the semantic structure of this widget. */ private AccessibilityNodeProviderImpl mAccessibilityNodeProvider; /** + * Helper class for managing pressed state of the virtual buttons. + */ + private final PressedStateHelper mPressedStateHelper; + + /** * Interface to listen for changes of the current value. */ public interface OnValueChangeListener { @@ -553,12 +558,6 @@ public class NumberPicker extends LinearLayout { mSelectionDividersDistance = attributesArray.getDimensionPixelSize( R.styleable.NumberPicker_selectionDividersDistance, defSelectionDividerDistance); - final int defMinFlingDistance = (int) TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_MIN_FLING_DISTANCE, - getResources().getDisplayMetrics()); - mMinFlingDistance = attributesArray.getDimensionPixelSize( - R.styleable.NumberPicker_minFlingDistance, defMinFlingDistance); - mMinHeight = attributesArray.getDimensionPixelSize( R.styleable.NumberPicker_internalMinHeight, SIZE_UNSPECIFIED); @@ -581,8 +580,13 @@ public class NumberPicker extends LinearLayout { mComputeMaxWidth = (mMaxWidth == SIZE_UNSPECIFIED); + mVirtualButtonPressedDrawable = attributesArray.getDrawable( + R.styleable.NumberPicker_virtualButtonPressedDrawable); + attributesArray.recycle(); + mPressedStateHelper = new PressedStateHelper(); + // By default Linearlayout that we extend is not drawn. This is // its draw() method is not called but dispatchDraw() is called // directly (see ViewGroup.drawChild()). However, this class uses @@ -776,7 +780,19 @@ public class NumberPicker extends LinearLayout { mLastDownEventTime = event.getEventTime(); mIngonreMoveEvents = false; mShowSoftInputOnTap = false; - // Make sure we wupport flinging inside scrollables. + // Handle pressed state before any state change. + if (mLastDownEventY < mTopSelectionDividerTop) { + if (mScrollState == OnScrollListener.SCROLL_STATE_IDLE) { + mPressedStateHelper.buttonPressDelayed( + PressedStateHelper.BUTTON_DECREMENT); + } + } else if (mLastDownEventY > mBottomSelectionDividerBottom) { + if (mScrollState == OnScrollListener.SCROLL_STATE_IDLE) { + mPressedStateHelper.buttonPressDelayed( + PressedStateHelper.BUTTON_INCREMENT); + } + } + // Make sure we support flinging inside scrollables. getParent().requestDisallowInterceptTouchEvent(true); if (!mFlingScroller.isFinished()) { mFlingScroller.forceFinished(true); @@ -826,8 +842,7 @@ public class NumberPicker extends LinearLayout { onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); } } else { - int deltaMoveY = (int) ((currentMoveY - mLastDownOrMoveEventY) - / TOUCH_SCROLL_DECELERATION_COEFFICIENT); + int deltaMoveY = (int) ((currentMoveY - mLastDownOrMoveEventY)); scrollBy(0, deltaMoveY); invalidate(); } @@ -836,23 +851,12 @@ public class NumberPicker extends LinearLayout { case MotionEvent.ACTION_UP: { removeBeginSoftInputCommand(); removeChangeCurrentByOneFromLongPress(); + mPressedStateHelper.cancel(); VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity); int initialVelocity = (int) velocityTracker.getYVelocity(); if (Math.abs(initialVelocity) > mMinimumFlingVelocity) { - int deltaMove = (int) (event.getY() - mLastDownEventY); - int absDeltaMoveY = Math.abs(deltaMove); - if (absDeltaMoveY > mMinFlingDistance) { - fling(initialVelocity); - } else { - final int normalizedDeltaMove = - (int) (absDeltaMoveY / TOUCH_SCROLL_DECELERATION_COEFFICIENT); - if (normalizedDeltaMove < mSelectorElementHeight) { - snapToNextValue(deltaMove < 0); - } else { - snapToClosestValue(); - } - } + fling(initialVelocity); onScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); } else { int eventY = (int) event.getY(); @@ -867,8 +871,12 @@ public class NumberPicker extends LinearLayout { - SELECTOR_MIDDLE_ITEM_INDEX; if (selectorIndexOffset > 0) { changeValueByOne(true); + mPressedStateHelper.buttonTapped( + PressedStateHelper.BUTTON_INCREMENT); } else if (selectorIndexOffset < 0) { changeValueByOne(false); + mPressedStateHelper.buttonTapped( + PressedStateHelper.BUTTON_DECREMENT); } } } else { @@ -1356,6 +1364,22 @@ public class NumberPicker extends LinearLayout { float x = (mRight - mLeft) / 2; float y = mCurrentScrollOffset; + // draw the virtual buttons pressed state if needed + if (mVirtualButtonPressedDrawable != null + && mScrollState == OnScrollListener.SCROLL_STATE_IDLE) { + if (mDecrementVirtualButtonPressed) { + mVirtualButtonPressedDrawable.setState(PRESSED_STATE_SET); + mVirtualButtonPressedDrawable.setBounds(0, 0, mRight, mTopSelectionDividerTop); + mVirtualButtonPressedDrawable.draw(canvas); + } + if (mIncrementVirtualButtonPressed) { + mVirtualButtonPressedDrawable.setState(PRESSED_STATE_SET); + mVirtualButtonPressedDrawable.setBounds(0, mBottomSelectionDividerBottom, mRight, + mBottom); + mVirtualButtonPressedDrawable.draw(canvas); + } + } + // draw the selector wheel int[] selectorIndices = mSelectorIndices; for (int i = 0; i < selectorIndices.length; i++) { @@ -1465,15 +1489,15 @@ public class NumberPicker extends LinearLayout { */ private void initializeSelectorWheelIndices() { mSelectorIndexToStringCache.clear(); - int[] selectorIdices = mSelectorIndices; + int[] selectorIndices = mSelectorIndices; int current = getValue(); for (int i = 0; i < mSelectorIndices.length; i++) { int selectorIndex = current + (i - SELECTOR_MIDDLE_ITEM_INDEX); if (mWrapSelectorWheel) { selectorIndex = getWrappedSelectorIndex(selectorIndex); } - mSelectorIndices[i] = selectorIndex; - ensureCachedScrollSelectorValue(mSelectorIndices[i]); + selectorIndices[i] = selectorIndex; + ensureCachedScrollSelectorValue(selectorIndices[i]); } } @@ -1775,6 +1799,7 @@ public class NumberPicker extends LinearLayout { if (mBeginSoftInputOnLongPressCommand != null) { removeCallbacks(mBeginSoftInputOnLongPressCommand); } + mPressedStateHelper.cancel(); } /** @@ -1910,39 +1935,80 @@ public class NumberPicker extends LinearLayout { return false; } - private void snapToNextValue(boolean increment) { - int deltaY = mCurrentScrollOffset - mInitialScrollOffset; - int amountToScroll = 0; - if (deltaY != 0) { - mPreviousScrollerY = 0; - if (deltaY > 0) { - if (increment) { - amountToScroll = - deltaY; - } else { - amountToScroll = mSelectorElementHeight - deltaY; - } - } else { - if (increment) { - amountToScroll = - mSelectorElementHeight - deltaY; - } else { - amountToScroll = - deltaY; - } + class PressedStateHelper implements Runnable { + public static final int BUTTON_INCREMENT = 1; + public static final int BUTTON_DECREMENT = 2; + + private final int MODE_PRESS = 1; + private final int MODE_TAPPED = 2; + + private int mManagedButton; + private int mMode; + + public void cancel() { + mMode = 0; + mManagedButton = 0; + NumberPicker.this.removeCallbacks(this); + if (mIncrementVirtualButtonPressed) { + mIncrementVirtualButtonPressed = false; + invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom); + } + mDecrementVirtualButtonPressed = false; + if (mDecrementVirtualButtonPressed) { + invalidate(0, 0, mRight, mTopSelectionDividerTop); } - mFlingScroller.startScroll(0, 0, 0, amountToScroll, SNAP_SCROLL_DURATION); - invalidate(); } - } - private void snapToClosestValue() { - // adjust to the closest value - int deltaY = mInitialScrollOffset - mCurrentScrollOffset; - if (deltaY != 0) { - mPreviousScrollerY = 0; - if (Math.abs(deltaY) > mSelectorElementHeight / 2) { - deltaY += (deltaY > 0) ? -mSelectorElementHeight : mSelectorElementHeight; + public void buttonPressDelayed(int button) { + cancel(); + mMode = MODE_PRESS; + mManagedButton = button; + NumberPicker.this.postDelayed(this, ViewConfiguration.getTapTimeout()); + } + + public void buttonTapped(int button) { + cancel(); + mMode = MODE_TAPPED; + mManagedButton = button; + NumberPicker.this.post(this); + } + + @Override + public void run() { + switch (mMode) { + case MODE_PRESS: { + switch (mManagedButton) { + case BUTTON_INCREMENT: { + mIncrementVirtualButtonPressed = true; + invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom); + } break; + case BUTTON_DECREMENT: { + mDecrementVirtualButtonPressed = true; + invalidate(0, 0, mRight, mTopSelectionDividerTop); + } + } + } break; + case MODE_TAPPED: { + switch (mManagedButton) { + case BUTTON_INCREMENT: { + if (!mIncrementVirtualButtonPressed) { + NumberPicker.this.postDelayed(this, + ViewConfiguration.getPressedStateDuration()); + } + mIncrementVirtualButtonPressed ^= true; + invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom); + } break; + case BUTTON_DECREMENT: { + if (!mDecrementVirtualButtonPressed) { + NumberPicker.this.postDelayed(this, + ViewConfiguration.getPressedStateDuration()); + } + mDecrementVirtualButtonPressed ^= true; + invalidate(0, 0, mRight, mTopSelectionDividerTop); + } + } + } break; } - mFlingScroller.startScroll(0, 0, 0, deltaY, SNAP_SCROLL_DURATION); - invalidate(); } } diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index f912c66..b398ce4 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; +import android.os.Bundle; import android.os.StrictMode; import android.util.AttributeSet; import android.view.FocusFinder; @@ -740,10 +741,42 @@ public class ScrollView extends FrameLayout { } @Override + public boolean performAccessibilityAction(int action, Bundle arguments) { + switch (action) { + case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { + final int viewportHeight = getHeight() - mPaddingBottom - mPaddingTop; + final int targetScrollY = Math.min(mScrollY + viewportHeight, getScrollRange()); + if (targetScrollY != mScrollY) { + smoothScrollTo(0, targetScrollY); + return true; + } + } return false; + case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { + final int viewportHeight = getHeight() - mPaddingBottom - mPaddingTop; + final int targetScrollY = Math.max(mScrollY - viewportHeight, 0); + if (targetScrollY != mScrollY) { + smoothScrollTo(0, targetScrollY); + return true; + } + } return false; + } + return super.performAccessibilityAction(action, arguments); + } + + @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(ScrollView.class.getName()); - info.setScrollable(getScrollRange() > 0); + final int scrollRange = getScrollRange(); + if (scrollRange > 0) { + info.setScrollable(true); + if (mScrollY > 0) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + } + if (mScrollY < scrollRange) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + } + } } @Override diff --git a/core/java/android/widget/ShareActionProvider.java b/core/java/android/widget/ShareActionProvider.java index 367561e..21840ca 100644 --- a/core/java/android/widget/ShareActionProvider.java +++ b/core/java/android/widget/ShareActionProvider.java @@ -240,12 +240,25 @@ public class ShareActionProvider extends ActionProvider { * <p> * <strong>Note:</strong> The history file name can be set any time, however * only the action views created by {@link #onCreateActionView()} after setting - * the file name will be backed by the provided file. Hence, if you are using - * a share action provider on a menu item and want to change the history file - * based on the type of the currently selected item, you need to call - * {@link android.app.Activity#invalidateOptionsMenu()} to force the system - * to recreate the menu UI. + * the file name will be backed by the provided file. Therefore, if you want to + * use different history files for sharing specific types of content, every time + * you change the history file {@link #setShareHistoryFileName(String)} you must + * call {@link android.app.Activity#invalidateOptionsMenu()} to recreate the + * action view. You should <strong>not</strong> call + * {@link android.app.Activity#invalidateOptionsMenu()} from + * {@link android.app.Activity#onCreateOptionsMenu(Menu)}. * <p> + * <code> + * private void doShare(Intent intent) { + * if (IMAGE.equals(intent.getMimeType())) { + * mShareActionProvider.setHistoryFileName(SHARE_IMAGE_HISTORY_FILE_NAME); + * } else if (TEXT.equals(intent.getMimeType())) { + * mShareActionProvider.setHistoryFileName(SHARE_TEXT_HISTORY_FILE_NAME); + * } + * mShareActionProvider.setIntent(intent); + * invalidateOptionsMenu(); + * } + * <code> * * @param shareHistoryFile The share history file name. */ diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java index e1103dd..ebf8a4a 100644 --- a/core/java/android/widget/SpellChecker.java +++ b/core/java/android/widget/SpellChecker.java @@ -427,12 +427,6 @@ public class SpellChecker implements SpellCheckerSessionListener { if (spellCheckSpanStart < 0 || spellCheckSpanEnd <= spellCheckSpanStart) return; // span was removed in the meantime - final int suggestionsCount = suggestionsInfo.getSuggestionsCount(); - if (suggestionsCount <= 0) { - // A negative suggestion count is possible - return; - } - final int start; final int end; if (offset != USE_SPAN_RANGE && length != USE_SPAN_RANGE) { @@ -443,9 +437,15 @@ public class SpellChecker implements SpellCheckerSessionListener { end = spellCheckSpanEnd; } - String[] suggestions = new String[suggestionsCount]; - for (int i = 0; i < suggestionsCount; i++) { - suggestions[i] = suggestionsInfo.getSuggestionAt(i); + final int suggestionsCount = suggestionsInfo.getSuggestionsCount(); + String[] suggestions; + if (suggestionsCount > 0) { + suggestions = new String[suggestionsCount]; + for (int i = 0; i < suggestionsCount; i++) { + suggestions[i] = suggestionsInfo.getSuggestionAt(i); + } + } else { + suggestions = ArrayUtils.emptyArray(String.class); } SuggestionSpan suggestionSpan = new SuggestionSpan(mTextView.getContext(), suggestions, @@ -453,7 +453,7 @@ public class SpellChecker implements SpellCheckerSessionListener { // TODO: Remove mIsSentenceSpellCheckSupported by extracting an interface // to share the logic of word level spell checker and sentence level spell checker if (mIsSentenceSpellCheckSupported) { - final long key = TextUtils.packRangeInLong(start, end); + final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end)); final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key); if (tempSuggestionSpan != null) { if (DBG) { @@ -611,6 +611,9 @@ public class SpellChecker implements SpellCheckerSessionListener { if (spellCheckEnd < start) { break; } + if (spellCheckEnd <= spellCheckStart) { + break; + } if (createSpellCheckSpan) { addSpellCheckSpan(editable, spellCheckStart, spellCheckEnd); } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 8c81343..555c974 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -26,7 +26,6 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; @@ -91,6 +90,7 @@ import android.util.AttributeSet; import android.util.FloatMath; import android.util.Log; import android.util.TypedValue; +import android.view.AccessibilityIterators.TextSegmentIterator; import android.view.ActionMode; import android.view.DragEvent; import android.view.Gravity; @@ -397,7 +397,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * EditText specific data, created on demand when one of the Editor fields is used. - * See {@link #createEditorIfNeeded(String)}. + * See {@link #createEditorIfNeeded()}. */ private Editor mEditor; @@ -798,20 +798,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener break; case com.android.internal.R.styleable.TextView_imeOptions: - createEditorIfNeeded("IME options specified in constructor"); + createEditorIfNeeded(); mEditor.createInputContentTypeIfNeeded(); mEditor.mInputContentType.imeOptions = a.getInt(attr, mEditor.mInputContentType.imeOptions); break; case com.android.internal.R.styleable.TextView_imeActionLabel: - createEditorIfNeeded("IME action label specified in constructor"); + createEditorIfNeeded(); mEditor.createInputContentTypeIfNeeded(); mEditor.mInputContentType.imeActionLabel = a.getText(attr); break; case com.android.internal.R.styleable.TextView_imeActionId: - createEditorIfNeeded("IME action id specified in constructor"); + createEditorIfNeeded(); mEditor.createInputContentTypeIfNeeded(); mEditor.mInputContentType.imeActionId = a.getInt(attr, mEditor.mInputContentType.imeActionId); @@ -883,7 +883,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } try { - createEditorIfNeeded("inputMethod in ctor"); + createEditorIfNeeded(); mEditor.mKeyListener = (KeyListener) c.newInstance(); } catch (InstantiationException ex) { throw new RuntimeException(ex); @@ -898,7 +898,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; } } else if (digits != null) { - createEditorIfNeeded("digits in ctor"); + createEditorIfNeeded(); mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString()); // If no input type was specified, we will default to generic // text, since we can't tell the IME about the set of digits @@ -910,11 +910,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // If set, the input type overrides what was set using the deprecated singleLine flag. singleLine = !isMultilineInputType(inputType); } else if (phone) { - createEditorIfNeeded("dialer in ctor"); + createEditorIfNeeded(); mEditor.mKeyListener = DialerKeyListener.getInstance(); mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE; } else if (numeric != 0) { - createEditorIfNeeded("numeric in ctor"); + createEditorIfNeeded(); mEditor.mKeyListener = DigitsKeyListener.getInstance((numeric & SIGNED) != 0, (numeric & DECIMAL) != 0); inputType = EditorInfo.TYPE_CLASS_NUMBER; @@ -951,7 +951,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener break; } - createEditorIfNeeded("text input in ctor"); + createEditorIfNeeded(); mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap); mEditor.mInputType = inputType; } else if (isTextSelectable()) { @@ -964,7 +964,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // So that selection can be changed using arrow keys and touch is handled. setMovementMethod(ArrowKeyMovementMethod.getInstance()); } else if (editable) { - createEditorIfNeeded("editable input in ctor"); + createEditorIfNeeded(); mEditor.mKeyListener = TextKeyListener.getInstance(); mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; } else { @@ -987,7 +987,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener webPasswordInputType, numberPasswordInputType); if (selectallonfocus) { - createEditorIfNeeded("selectallonfocus in constructor"); + createEditorIfNeeded(); mEditor.mSelectAllOnFocus = true; if (bufferType == BufferType.NORMAL) @@ -1335,7 +1335,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener fixFocusableAndClickableSettings(); if (input != null) { - createEditorIfNeeded("input is not null"); + createEditorIfNeeded(); try { mEditor.mInputType = mEditor.mKeyListener.getInputType(); } catch (IncompatibleClassChangeError e) { @@ -1355,7 +1355,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private void setKeyListenerOnly(KeyListener input) { if (mEditor == null && input == null) return; // null is the default value - createEditorIfNeeded("setKeyListenerOnly"); + createEditorIfNeeded(); if (mEditor.mKeyListener != input) { mEditor.mKeyListener = input; if (input != null && !(mText instanceof Editable)) { @@ -2383,7 +2383,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ @android.view.RemotableViewMethod public final void setShowSoftInputOnFocus(boolean show) { - createEditorIfNeeded("setShowSoftInputOnFocus"); + createEditorIfNeeded(); mEditor.mShowSoftInputOnFocus = show; } @@ -3263,7 +3263,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd); if (ss.frozenWithFocus) { - createEditorIfNeeded("restore instance with focus"); + createEditorIfNeeded(); mEditor.mFrozenWithFocus = true; } } @@ -3424,7 +3424,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (type == BufferType.EDITABLE || getKeyListener() != null || needEditableForNotification) { - createEditorIfNeeded("setText with BufferType.EDITABLE or non null mInput"); + createEditorIfNeeded(); Editable t = mEditableFactory.newEditable(text); text = t; setFilters(t, mFilters); @@ -3768,7 +3768,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ public void setRawInputType(int type) { if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value - createEditorIfNeeded("non null input type"); + createEditorIfNeeded(); mEditor.mInputType = type; } @@ -3811,7 +3811,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } setRawInputType(type); if (direct) { - createEditorIfNeeded("setInputType"); + createEditorIfNeeded(); mEditor.mKeyListener = input; } else { setKeyListenerOnly(input); @@ -3837,7 +3837,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @attr ref android.R.styleable#TextView_imeOptions */ public void setImeOptions(int imeOptions) { - createEditorIfNeeded("IME options specified"); + createEditorIfNeeded(); mEditor.createInputContentTypeIfNeeded(); mEditor.mInputContentType.imeOptions = imeOptions; } @@ -3864,7 +3864,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @attr ref android.R.styleable#TextView_imeActionId */ public void setImeActionLabel(CharSequence label, int actionId) { - createEditorIfNeeded("IME action label specified"); + createEditorIfNeeded(); mEditor.createInputContentTypeIfNeeded(); mEditor.mInputContentType.imeActionLabel = label; mEditor.mInputContentType.imeActionId = actionId; @@ -3901,7 +3901,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * modifier will, however, allow the user to insert a newline character. */ public void setOnEditorActionListener(OnEditorActionListener l) { - createEditorIfNeeded("Editor action listener set"); + createEditorIfNeeded(); mEditor.createInputContentTypeIfNeeded(); mEditor.mInputContentType.onEditorActionListener = l; } @@ -3998,7 +3998,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @attr ref android.R.styleable#TextView_privateImeOptions */ public void setPrivateImeOptions(String type) { - createEditorIfNeeded("Private IME option set"); + createEditorIfNeeded(); mEditor.createInputContentTypeIfNeeded(); mEditor.mInputContentType.privateImeOptions = type; } @@ -4026,7 +4026,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @attr ref android.R.styleable#TextView_editorExtras */ public void setInputExtras(int xmlResId) throws XmlPullParserException, IOException { - createEditorIfNeeded("Input extra set"); + createEditorIfNeeded(); XmlResourceParser parser = getResources().getXml(xmlResId); mEditor.createInputContentTypeIfNeeded(); mEditor.mInputContentType.extras = new Bundle(); @@ -4045,7 +4045,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ public Bundle getInputExtras(boolean create) { if (mEditor == null && !create) return null; - createEditorIfNeeded("get Input extra"); + createEditorIfNeeded(); if (mEditor.mInputContentType == null) { if (!create) return null; mEditor.createInputContentTypeIfNeeded(); @@ -4097,7 +4097,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * be cleared (and you should provide a <code>null</code> icon as well). */ public void setError(CharSequence error, Drawable icon) { - createEditorIfNeeded("setError"); + createEditorIfNeeded(); mEditor.setError(error, icon); } @@ -4609,7 +4609,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void setTextIsSelectable(boolean selectable) { if (!selectable && mEditor == null) return; // false is default value with no edit data - createEditorIfNeeded("setTextIsSelectable"); + createEditorIfNeeded(); if (mEditor.mTextIsSelectable == selectable) return; mEditor.mTextIsSelectable = selectable; @@ -5422,7 +5422,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @return Returns true if the text was successfully extracted, else false. */ public boolean extractText(ExtractedTextRequest request, ExtractedText outText) { - createEditorIfNeeded("extractText"); + createEditorIfNeeded(); return mEditor.extractText(request, outText); } @@ -6836,7 +6836,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ @android.view.RemotableViewMethod public void setSelectAllOnFocus(boolean selectAllOnFocus) { - createEditorIfNeeded("setSelectAllOnFocus"); + createEditorIfNeeded(); mEditor.mSelectAllOnFocus = selectAllOnFocus; if (selectAllOnFocus && !(mText instanceof Spannable)) { @@ -6855,7 +6855,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @android.view.RemotableViewMethod public void setCursorVisible(boolean visible) { if (visible && mEditor == null) return; // visible is the default value with no edit data - createEditorIfNeeded("setCursorVisible"); + createEditorIfNeeded(); if (mEditor.mCursorVisible != visible) { mEditor.mCursorVisible = visible; invalidate(); @@ -7712,6 +7712,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (!isPassword) { info.setText(getTextForAccessibility()); } + + if (TextUtils.isEmpty(getContentDescription()) + && !TextUtils.isEmpty(mText)) { + info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); + info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); + info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER + | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD + | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE + | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH + | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE); + } } @Override @@ -7726,12 +7737,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Gets the text reported for accessibility purposes. It is the - * text if not empty or the hint. + * Gets the text reported for accessibility purposes. * * @return The accessibility text. + * + * @hide */ - private CharSequence getTextForAccessibility() { + public CharSequence getTextForAccessibility() { CharSequence text = getText(); if (TextUtils.isEmpty(text)) { text = getHint(); @@ -7902,7 +7914,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * that case, to allow for quick replacement. */ public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) { - createEditorIfNeeded("custom selection action mode set"); + createEditorIfNeeded(); mEditor.mCustomSelectionActionModeCallback = actionModeCallback; } @@ -8273,16 +8285,82 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * Also note that for performance reasons, the mEditor is created when needed, but not * reset when no more edit-specific fields are needed. */ - private void createEditorIfNeeded(String reason) { + private void createEditorIfNeeded() { if (mEditor == null) { - if (!(this instanceof EditText)) { - Log.e(LOG_TAG + " EDITOR", "Creating an Editor on a regular TextView. " + reason); - } mEditor = new Editor(this); + } + } + + /** + * @hide + */ + @Override + public CharSequence getIterableTextForAccessibility() { + if (getContentDescription() == null) { + if (!(mText instanceof Spannable)) { + setText(mText, BufferType.SPANNABLE); + } + return mText; + } + return super.getIterableTextForAccessibility(); + } + + /** + * @hide + */ + @Override + public TextSegmentIterator getIteratorForGranularity(int granularity) { + switch (granularity) { + case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: { + Spannable text = (Spannable) getIterableTextForAccessibility(); + if (!TextUtils.isEmpty(text) && getLayout() != null) { + AccessibilityIterators.LineTextSegmentIterator iterator = + AccessibilityIterators.LineTextSegmentIterator.getInstance(); + iterator.initialize(text, getLayout()); + return iterator; + } + } break; + case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: { + Spannable text = (Spannable) getIterableTextForAccessibility(); + if (!TextUtils.isEmpty(text) && getLayout() != null) { + AccessibilityIterators.PageTextSegmentIterator iterator = + AccessibilityIterators.PageTextSegmentIterator.getInstance(); + iterator.initialize(this); + return iterator; + } + } break; + } + return super.getIteratorForGranularity(granularity); + } + + /** + * @hide + */ + @Override + public int getAccessibilityCursorPosition() { + if (TextUtils.isEmpty(getContentDescription())) { + return getSelectionEnd(); } else { - if (!(this instanceof EditText)) { - Log.d(LOG_TAG + " EDITOR", "Redundant Editor creation. " + reason); + return super.getAccessibilityCursorPosition(); + } + } + + /** + * @hide + */ + @Override + public void setAccessibilityCursorPosition(int index) { + if (getAccessibilityCursorPosition() == index) { + return; + } + if (TextUtils.isEmpty(getContentDescription())) { + if (index >= 0) { + Selection.setSelection((Spannable) mText, index); + } else { + Selection.removeSelection((Spannable) mText); } + } else { + super.setAccessibilityCursorPosition(index); } } |
