summaryrefslogtreecommitdiffstats
path: root/core/java/android
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java27
-rw-r--r--core/java/android/accessibilityservice/AccessibilityServiceInfo.java42
-rw-r--r--core/java/android/accessibilityservice/UiTestAutomationBridge.java5
-rwxr-xr-xcore/java/android/animation/ValueAnimator.java2
-rw-r--r--core/java/android/app/Activity.java7
-rw-r--r--core/java/android/app/ActivityManager.java13
-rw-r--r--core/java/android/app/ActivityOptions.java50
-rw-r--r--core/java/android/app/ActivityThread.java38
-rw-r--r--core/java/android/app/ApplicationPackageManager.java5
-rw-r--r--core/java/android/app/ContextImpl.java12
-rw-r--r--core/java/android/app/Notification.java39
-rw-r--r--core/java/android/app/TaskStackBuilder.java12
-rw-r--r--core/java/android/appwidget/AppWidgetHostView.java24
-rw-r--r--core/java/android/content/AsyncTaskLoader.java1
-rw-r--r--core/java/android/content/ContentProvider.java3
-rw-r--r--core/java/android/content/ContentProviderClient.java13
-rw-r--r--core/java/android/content/ContentProviderNative.java1
-rw-r--r--core/java/android/content/ContentResolver.java211
-rw-r--r--core/java/android/content/CursorLoader.java2
-rw-r--r--core/java/android/content/IContentProvider.java1
-rw-r--r--core/java/android/content/Intent.java18
-rw-r--r--core/java/android/content/SyncStorageEngine.java26
-rw-r--r--core/java/android/content/pm/ContainerEncryptionParams.aidl (renamed from core/java/android/net/nsd/NetworkServiceInfo.java)21
-rw-r--r--core/java/android/content/pm/ContainerEncryptionParams.java380
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl3
-rw-r--r--core/java/android/content/pm/LimitedLengthInputStream.java94
-rw-r--r--core/java/android/content/pm/MacAuthenticatedInputStream.java78
-rw-r--r--core/java/android/content/pm/PackageManager.java12
-rw-r--r--core/java/android/content/res/AssetFileDescriptor.java3
-rw-r--r--core/java/android/database/AbstractCursor.java10
-rw-r--r--core/java/android/database/DatabaseUtils.java2
-rw-r--r--core/java/android/database/sqlite/SQLiteConnection.java4
-rw-r--r--core/java/android/database/sqlite/SQLiteConnectionPool.java4
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java4
-rw-r--r--core/java/android/database/sqlite/SQLiteDirectCursorDriver.java2
-rw-r--r--core/java/android/database/sqlite/SQLiteProgram.java2
-rw-r--r--core/java/android/database/sqlite/SQLiteQuery.java4
-rw-r--r--core/java/android/database/sqlite/SQLiteQueryBuilder.java4
-rw-r--r--core/java/android/database/sqlite/SQLiteSession.java4
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java16
-rw-r--r--core/java/android/net/Uri.java6
-rw-r--r--core/java/android/net/nsd/DnsSdTxtRecord.java1
-rw-r--r--core/java/android/net/nsd/NsdManager.java677
-rw-r--r--core/java/android/net/nsd/NsdServiceInfo.java (renamed from core/java/android/net/nsd/DnsSdServiceInfo.java)22
-rw-r--r--core/java/android/nfc/NdefRecord.java12
-rw-r--r--core/java/android/os/CancellationSignal.java (renamed from core/java/android/content/CancellationSignal.java)5
-rw-r--r--core/java/android/os/IBinder.java3
-rw-r--r--core/java/android/os/ICancellationSignal.aidl (renamed from core/java/android/content/ICancellationSignal.aidl)2
-rw-r--r--core/java/android/os/OperationCanceledException.java (renamed from core/java/android/content/OperationCanceledException.java)3
-rw-r--r--core/java/android/os/Process.java3
-rw-r--r--core/java/android/os/ServiceManagerNative.java36
-rw-r--r--core/java/android/os/SystemProperties.java29
-rw-r--r--core/java/android/os/Trace.java19
-rw-r--r--core/java/android/preference/MultiCheckPreference.java327
-rw-r--r--core/java/android/provider/Settings.java21
-rw-r--r--core/java/android/view/AccessibilityInteractionController.java34
-rw-r--r--core/java/android/view/AccessibilityIterators.java352
-rw-r--r--core/java/android/view/Choreographer.java33
-rw-r--r--core/java/android/view/IWindowManager.aidl2
-rw-r--r--core/java/android/view/KeyCharacterMap.java55
-rw-r--r--core/java/android/view/View.java332
-rw-r--r--core/java/android/view/ViewGroup.java15
-rw-r--r--core/java/android/view/ViewRootImpl.java82
-rw-r--r--core/java/android/view/ViewTreeObserver.java206
-rw-r--r--core/java/android/view/WindowManagerImpl.java19
-rw-r--r--core/java/android/view/WindowManagerPolicy.java2
-rwxr-xr-xcore/java/android/view/WindowOrientationListener.java101
-rw-r--r--core/java/android/view/accessibility/AccessibilityEvent.java36
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java55
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeProvider.java2
-rw-r--r--core/java/android/view/inputmethod/InputMethodSubtype.java28
-rw-r--r--core/java/android/webkit/WebHistoryItem.java68
-rw-r--r--core/java/android/webkit/WebViewClassic.java198
-rw-r--r--core/java/android/webkit/WebViewCore.java9
-rw-r--r--core/java/android/widget/AbsListView.java245
-rw-r--r--core/java/android/widget/AccessibilityIterators.java219
-rw-r--r--core/java/android/widget/ActivityChooserModel.java546
-rw-r--r--core/java/android/widget/HorizontalScrollView.java35
-rw-r--r--core/java/android/widget/NumberPicker.java204
-rw-r--r--core/java/android/widget/ScrollView.java35
-rw-r--r--core/java/android/widget/ShareActionProvider.java23
-rw-r--r--core/java/android/widget/SpellChecker.java23
-rw-r--r--core/java/android/widget/TextView.java158
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>&lt;{@link android.R.styleable#AccessibilityService_canHandleGestures
- * canHandleGestures}&gt;</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);
}
}