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.java199
-rw-r--r--core/java/android/accessibilityservice/AccessibilityServiceInfo.java36
-rw-r--r--core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl (renamed from core/java/android/accessibilityservice/IEventListener.aidl)6
-rw-r--r--core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl51
-rw-r--r--core/java/android/accessibilityservice/UiTestAutomationBridge.java12
-rw-r--r--core/java/android/app/ActivityManagerNative.java20
-rw-r--r--core/java/android/app/ContextImpl.java4
-rw-r--r--core/java/android/app/DownloadManager.java20
-rw-r--r--core/java/android/app/IActivityManager.java4
-rw-r--r--core/java/android/app/Notification.java29
-rw-r--r--core/java/android/content/ClipData.java299
-rw-r--r--core/java/android/content/ClipDescription.java5
-rw-r--r--core/java/android/content/ContentResolver.java2
-rw-r--r--core/java/android/content/Intent.java36
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl4
-rw-r--r--core/java/android/content/pm/PackageManager.java17
-rwxr-xr-xcore/java/android/content/res/Resources.java51
-rw-r--r--core/java/android/hardware/Camera.java12
-rw-r--r--core/java/android/hardware/input/IInputDevicesChangedListener.aidl29
-rw-r--r--core/java/android/hardware/input/IInputManager.aidl9
-rwxr-xr-xcore/java/android/hardware/input/InputManager.java358
-rw-r--r--core/java/android/net/ConnectivityManager.java24
-rw-r--r--core/java/android/net/IConnectivityManager.aidl1
-rw-r--r--core/java/android/net/INetworkPolicyManager.aidl2
-rw-r--r--core/java/android/net/NetworkPolicyManager.java8
-rw-r--r--core/java/android/net/nsd/DnsSdServiceInfo.java47
-rw-r--r--core/java/android/net/nsd/DnsSdTxtRecord.java22
-rw-r--r--core/java/android/net/nsd/NsdManager.java50
-rw-r--r--core/java/android/nfc/INdefPushCallback.aidl3
-rw-r--r--core/java/android/nfc/NfcActivityManager.java57
-rw-r--r--core/java/android/nfc/NfcAdapter.java43
-rw-r--r--core/java/android/os/NullVibrator.java55
-rw-r--r--core/java/android/os/SystemVibrator.java94
-rw-r--r--core/java/android/os/Vibrator.java86
-rw-r--r--core/java/android/provider/BrowserContract.java191
-rw-r--r--core/java/android/provider/ContactsContract.java20
-rw-r--r--core/java/android/provider/Downloads.java31
-rw-r--r--core/java/android/provider/Settings.java15
-rw-r--r--core/java/android/text/Html.java11
-rw-r--r--core/java/android/text/SpannableStringBuilder.java220
-rw-r--r--core/java/android/view/AccessibilityInteractionController.java900
-rw-r--r--core/java/android/view/FocusFinder.java372
-rwxr-xr-xcore/java/android/view/InputDevice.java58
-rw-r--r--core/java/android/view/KeyCharacterMap.java2
-rw-r--r--core/java/android/view/TextureView.java4
-rw-r--r--core/java/android/view/View.java673
-rw-r--r--core/java/android/view/ViewConfiguration.java12
-rw-r--r--core/java/android/view/ViewDebug.java12
-rw-r--r--core/java/android/view/ViewGroup.java364
-rw-r--r--core/java/android/view/ViewParent.java18
-rw-r--r--core/java/android/view/ViewRootImpl.java979
-rw-r--r--core/java/android/view/VolumePanel.java2
-rw-r--r--core/java/android/view/WindowManagerPolicy.java2
-rw-r--r--core/java/android/view/accessibility/AccessibilityEvent.java19
-rw-r--r--core/java/android/view/accessibility/AccessibilityInteractionClient.java118
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java6
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java120
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfoCache.java53
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeProvider.java56
-rw-r--r--core/java/android/view/accessibility/AccessibilityRecord.java24
-rw-r--r--core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl20
-rw-r--r--core/java/android/view/accessibility/IAccessibilityManager.aidl7
-rw-r--r--core/java/android/webkit/CallbackProxy.java166
-rw-r--r--core/java/android/webkit/CookieManager.java2
-rwxr-xr-xcore/java/android/webkit/GeolocationPermissions.java12
-rw-r--r--core/java/android/webkit/JsPromptResult.java28
-rw-r--r--core/java/android/webkit/JsResult.java67
-rw-r--r--core/java/android/webkit/WebChromeClient.java65
-rw-r--r--core/java/android/webkit/WebIconDatabase.java2
-rw-r--r--core/java/android/webkit/WebSettings.java35
-rw-r--r--core/java/android/webkit/WebStorage.java119
-rw-r--r--core/java/android/webkit/WebView.java2
-rw-r--r--core/java/android/webkit/WebViewClassic.java148
-rw-r--r--core/java/android/webkit/WebViewCore.java37
-rw-r--r--core/java/android/webkit/WebViewInputDispatcher.java11
-rw-r--r--core/java/android/widget/AbsListView.java9
-rw-r--r--core/java/android/widget/AbsSpinner.java4
-rw-r--r--core/java/android/widget/AdapterView.java62
-rw-r--r--core/java/android/widget/AdapterViewAnimator.java4
-rw-r--r--core/java/android/widget/CheckBox.java12
-rw-r--r--core/java/android/widget/DatePicker.java7
-rw-r--r--core/java/android/widget/Editor.java2
-rw-r--r--core/java/android/widget/GridLayout.java48
-rw-r--r--core/java/android/widget/ImageView.java4
-rw-r--r--core/java/android/widget/NumberPicker.java19
-rw-r--r--core/java/android/widget/ShareActionProvider.java14
-rw-r--r--core/java/android/widget/Spinner.java2
-rw-r--r--core/java/android/widget/Switch.java11
-rw-r--r--core/java/android/widget/TextView.java7
-rw-r--r--core/java/android/widget/TimePicker.java5
90 files changed, 5411 insertions, 1497 deletions
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index ddd7f7c..3da35d3 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -19,17 +19,22 @@ package android.accessibilityservice;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Configuration;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.util.LocaleUtil;
import android.util.Log;
+import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityNodeInfo;
import com.android.internal.os.HandlerCaller;
+import java.util.Locale;
+
/**
* An accessibility service runs in the background and receives callbacks by the system
* when {@link AccessibilityEvent}s are fired. Such events denote some state transition
@@ -202,12 +207,65 @@ import com.android.internal.os.HandlerCaller;
* @see android.view.accessibility.AccessibilityManager
*/
public abstract class AccessibilityService extends Service {
+
+ /**
+ * The user has performed a swipe up gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_UP = 1;
+
+ /**
+ * The user has performed a swipe down gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_DOWN = 2;
+
+ /**
+ * The user has performed a swipe left gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_LEFT = 3;
+
+ /**
+ * The user has performed a swipe right gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_RIGHT = 4;
+
+ /**
+ * The user has performed a swipe left and right gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_LEFT_AND_RIGHT = 5;
+
+ /**
+ * The user has performed a swipe right and left gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_RIGHT_AND_LEFT = 6;
+
+ /**
+ * The user has performed a swipe up and down gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_UP_AND_DOWN = 7;
+
+ /**
+ * The user has performed a swipe down and up gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_DOWN_AND_UP = 8;
+
+ /**
+ * The user has performed a clockwise circle gesture on the touch screen.
+ */
+ public static final int GESTURE_CLOCKWISE_CIRCLE = 9;
+
+ /**
+ * The user has performed a counter clockwise circle gesture on the touch screen.
+ */
+ public static final int GESTURE_COUNTER_CLOCKWISE_CIRCLE = 10;
+
/**
* The {@link Intent} that must be declared as handled by the service.
*/
public static final String SERVICE_INTERFACE =
"android.accessibilityservice.AccessibilityService";
+ private static final int UNDEFINED = -1;
+
/**
* Name under which an AccessibilityService component publishes information
* about itself. This meta-data must reference an XML resource containing an
@@ -233,12 +291,15 @@ public abstract class AccessibilityService extends Service {
public void onInterrupt();
public void onServiceConnected();
public void onSetConnectionId(int connectionId);
+ public void onGesture(int gestureId);
}
private int mConnectionId;
private AccessibilityServiceInfo mInfo;
+ private int mLayoutDirection;
+
/**
* Callback for {@link android.view.accessibility.AccessibilityEvent}s.
*
@@ -264,6 +325,106 @@ public abstract class AccessibilityService extends Service {
}
/**
+ * Called by the system when the user performs a specific gesture on the
+ * touch screen.
+ *
+ * @param gestureId The unique id of the performed gesture.
+ *
+ * @see #GESTURE_SWIPE_UP
+ * @see #GESTURE_SWIPE_DOWN
+ * @see #GESTURE_SWIPE_LEFT
+ * @see #GESTURE_SWIPE_RIGHT
+ * @see #GESTURE_SWIPE_UP_AND_DOWN
+ * @see #GESTURE_SWIPE_DOWN_AND_UP
+ * @see #GESTURE_SWIPE_LEFT_AND_RIGHT
+ * @see #GESTURE_SWIPE_RIGHT_AND_LEFT
+ * @see #GESTURE_CLOCKWISE_CIRCLE
+ * @see #GESTURE_COUNTER_CLOCKWISE_CIRCLE
+ */
+ protected void onGesture(int gestureId) {
+ // TODO: Describe the default gesture processing in the javaDoc once it is finalized.
+
+ // Cache the id to avoid locking
+ final int connectionId = mConnectionId;
+ if (connectionId == UNDEFINED) {
+ throw new IllegalStateException("AccessibilityService not connected."
+ + " Did you receive a call of onServiceConnected()?");
+ }
+ AccessibilityNodeInfo root = AccessibilityInteractionClient.getInstance()
+ .findAccessibilityNodeInfoByAccessibilityId(connectionId,
+ AccessibilityNodeInfo.ACTIVE_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID,
+ AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS);
+ if (root == null) {
+ return;
+ }
+ AccessibilityNodeInfo current = root.findFocus(View.FOCUS_ACCESSIBILITY);
+ if (current == null) {
+ current = root;
+ }
+ AccessibilityNodeInfo next = null;
+ switch (gestureId) {
+ case GESTURE_SWIPE_UP: {
+ next = current.focusSearch(View.ACCESSIBILITY_FOCUS_OUT);
+ } break;
+ case GESTURE_SWIPE_DOWN: {
+ next = current.focusSearch(View.ACCESSIBILITY_FOCUS_IN);
+ } break;
+ case GESTURE_SWIPE_LEFT: {
+ if (mLayoutDirection == View.LAYOUT_DIRECTION_LTR) {
+ next = current.focusSearch(View.ACCESSIBILITY_FOCUS_BACKWARD);
+ } else { // LAYOUT_DIRECTION_RTL
+ next = current.focusSearch(View.ACCESSIBILITY_FOCUS_FORWARD);
+ }
+ } break;
+ case GESTURE_SWIPE_RIGHT: {
+ if (mLayoutDirection == View.LAYOUT_DIRECTION_LTR) {
+ next = current.focusSearch(View.ACCESSIBILITY_FOCUS_FORWARD);
+ } else { // LAYOUT_DIRECTION_RTL
+ next = current.focusSearch(View.ACCESSIBILITY_FOCUS_BACKWARD);
+ }
+ } break;
+ case GESTURE_SWIPE_UP_AND_DOWN: {
+ next = current.focusSearch(View.ACCESSIBILITY_FOCUS_UP);
+ } break;
+ case GESTURE_SWIPE_DOWN_AND_UP: {
+ next = current.focusSearch(View.ACCESSIBILITY_FOCUS_DOWN);
+ } break;
+ case GESTURE_SWIPE_LEFT_AND_RIGHT: {
+ next = current.focusSearch(View.ACCESSIBILITY_FOCUS_LEFT);
+ } break;
+ case GESTURE_SWIPE_RIGHT_AND_LEFT: {
+ next = current.focusSearch(View.ACCESSIBILITY_FOCUS_RIGHT);
+ } break;
+ }
+ if (next != null && !next.equals(current)) {
+ next.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+ }
+ }
+
+ /**
+ * Gets the an {@link AccessibilityServiceInfo} describing this
+ * {@link AccessibilityService}. This method is useful if one wants
+ * to change some of the dynamically configurable properties at
+ * runtime.
+ *
+ * @return The accessibility service info.
+ *
+ * @see AccessibilityNodeInfo
+ */
+ public final AccessibilityServiceInfo getServiceInfo() {
+ IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getServiceInfo();
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re);
+ }
+ }
+ return null;
+ }
+
+ /**
* Sets the {@link AccessibilityServiceInfo} that describes this service.
* <p>
* Note: You can call this method any time but the info will be picked up after
@@ -287,19 +448,33 @@ public abstract class AccessibilityService extends Service {
if (mInfo != null && connection != null) {
try {
connection.setServiceInfo(mInfo);
+ mInfo = null;
+ AccessibilityInteractionClient.getInstance().clearCache();
} catch (RemoteException re) {
Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re);
}
}
}
+ @Override
+ public void onCreate() {
+ Locale locale = getResources().getConfiguration().locale;
+ mLayoutDirection = LocaleUtil.getLayoutDirectionFromLocale(locale);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration configuration) {
+ super.onConfigurationChanged(configuration);
+ mLayoutDirection = LocaleUtil.getLayoutDirectionFromLocale(configuration.locale);
+ }
+
/**
* Implement to return the implementation of the internal accessibility
* service interface.
*/
@Override
public final IBinder onBind(Intent intent) {
- return new IEventListenerWrapper(this, getMainLooper(), new Callbacks() {
+ return new IAccessibilityServiceClientWrapper(this, getMainLooper(), new Callbacks() {
@Override
public void onServiceConnected() {
AccessibilityService.this.onServiceConnected();
@@ -319,14 +494,19 @@ public abstract class AccessibilityService extends Service {
public void onSetConnectionId( int connectionId) {
mConnectionId = connectionId;
}
+
+ @Override
+ public void onGesture(int gestureId) {
+ AccessibilityService.this.onGesture(gestureId);
+ }
});
}
/**
- * Implements the internal {@link IEventListener} interface to convert
+ * Implements the internal {@link IAccessibilityServiceClient} interface to convert
* incoming calls to it back to calls on an {@link AccessibilityService}.
*/
- static class IEventListenerWrapper extends IEventListener.Stub
+ static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub
implements HandlerCaller.Callback {
static final int NO_ID = -1;
@@ -334,12 +514,14 @@ public abstract class AccessibilityService extends Service {
private static final int DO_SET_SET_CONNECTION = 10;
private static final int DO_ON_INTERRUPT = 20;
private static final int DO_ON_ACCESSIBILITY_EVENT = 30;
+ private static final int DO_ON_GESTURE = 40;
private final HandlerCaller mCaller;
private final Callbacks mCallback;
- public IEventListenerWrapper(Context context, Looper looper, Callbacks callback) {
+ public IAccessibilityServiceClientWrapper(Context context, Looper looper,
+ Callbacks callback) {
mCallback = callback;
mCaller = new HandlerCaller(context, looper, this);
}
@@ -360,6 +542,11 @@ public abstract class AccessibilityService extends Service {
mCaller.sendMessage(message);
}
+ public void onGesture(int gestureId) {
+ Message message = mCaller.obtainMessageI(DO_ON_GESTURE, gestureId);
+ mCaller.sendMessage(message);
+ }
+
public void executeMessage(Message message) {
switch (message.what) {
case DO_ON_ACCESSIBILITY_EVENT :
@@ -387,6 +574,10 @@ public abstract class AccessibilityService extends Service {
mCallback.onSetConnectionId(AccessibilityInteractionClient.NO_ID);
}
return;
+ case DO_ON_GESTURE :
+ final int gestureId = message.arg1;
+ mCallback.onGesture(gestureId);
+ return;
default :
Log.w(LOG_TAG, "Unknown message type " + message.what);
}
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 8e53431..e77ed9a 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -25,11 +25,13 @@ import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.util.Xml;
+import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import org.xmlpull.v1.XmlPullParser;
@@ -101,6 +103,37 @@ public class AccessibilityServiceInfo implements Parcelable {
public static final int DEFAULT = 0x0000001;
/**
+ * If this flag is set the system will regard views that are not important
+ * for accessibility in addition to the ones that are important for accessibility.
+ * That is, views that are marked as not important for accessibility via
+ * {@link View#IMPORTANT_FOR_ACCESSIBILITY_NO} and views that are marked as
+ * potentially important for accessibility via
+ * {@link View#IMPORTANT_FOR_ACCESSIBILITY_AUTO} for which the system has determined
+ * that are not important for accessibility, are both reported while querying the
+ * window content and also the accessibility service will receive accessibility events
+ * from them.
+ * <p>
+ * <strong>Note:</strong> For accessibility services targeting API version
+ * {@link Build.VERSION_CODES#JELLY_BEAN} or higher this flag has to be explicitly
+ * set for the system to regard views that are not important for accessibility. For
+ * accessibility services targeting API version lower than
+ * {@link Build.VERSION_CODES#JELLY_BEAN} this flag is ignored and all views are
+ * regarded for accessibility purposes.
+ * </p>
+ * <p>
+ * Usually views not important for accessibility are layout managers that do not
+ * react to user actions, do not draw any content, and do not have any special
+ * semantics in the context of the screen content. For example, a three by three
+ * grid can be implemented as three horizontal linear layouts and one vertical,
+ * or three vertical linear layouts and one horizontal, or one grid layout, etc.
+ * In this context the actual layout mangers used to achieve the grid configuration
+ * are not important, rather it is important that there are nine evenly distributed
+ * elements.
+ * </p>
+ */
+ public static final int INCLUDE_NOT_IMPORTANT_VIEWS = 0x0000002;
+
+ /**
* The event types an {@link AccessibilityService} is interested in.
* <p>
* <strong>Can be dynamically set at runtime.</strong>
@@ -165,6 +198,7 @@ public class AccessibilityServiceInfo implements Parcelable {
* <strong>Can be dynamically set at runtime.</strong>
* </p>
* @see #DEFAULT
+ * @see #INCLUDE_NOT_IMPORTANT_VIEWS
*/
public int flags;
@@ -561,6 +595,8 @@ public class AccessibilityServiceInfo implements Parcelable {
switch (flag) {
case DEFAULT:
return "DEFAULT";
+ case INCLUDE_NOT_IMPORTANT_VIEWS:
+ return "REGARD_VIEWS_NOT_IMPORTANT_FOR_ACCESSIBILITY";
default:
return null;
}
diff --git a/core/java/android/accessibilityservice/IEventListener.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index 5536b3c..588728c 100644
--- a/core/java/android/accessibilityservice/IEventListener.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -20,15 +20,17 @@ import android.accessibilityservice.IAccessibilityServiceConnection;
import android.view.accessibility.AccessibilityEvent;
/**
- * Top-level interface to accessibility service component (implemented in Service).
+ * Top-level interface to an accessibility service component.
*
* @hide
*/
- oneway interface IEventListener {
+ oneway interface IAccessibilityServiceClient {
void setConnection(in IAccessibilityServiceConnection connection, int connectionId);
void onAccessibilityEvent(in AccessibilityEvent event);
void onInterrupt();
+
+ void onGesture(int gestureId);
}
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 8d17325..30da9db 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -41,13 +41,13 @@ interface IAccessibilityServiceConnection {
* to start from the root.
* @param interactionId The id of the interaction for matching with the callback result.
* @param callback Callback which to receive the result.
+ * @param flags Additional flags.
* @param threadId The id of the calling thread.
- * @param prefetchFlags flags to guide prefetching.
* @return The current window scale, where zero means a failure.
*/
float findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
long accessibilityNodeId, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, long threadId, int prefetchFlags);
+ IAccessibilityInteractionConnectionCallback callback, int flags, long threadId);
/**
* Finds {@link android.view.accessibility.AccessibilityNodeInfo}s by View text.
@@ -94,6 +94,48 @@ interface IAccessibilityServiceConnection {
long threadId);
/**
+ * Finds the {@link android.view.accessibility.AccessibilityNodeInfo} that has the specified
+ * focus type. The search is performed in the window whose id is specified and starts from
+ * the node whose accessibility id is specified.
+ *
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+ * to start from the root.
+ * @param focusType The type of focus to find.
+ * @param interactionId The id of the interaction for matching with the callback result.
+ * @param callback Callback which to receive the result.
+ * @param threadId The id of the calling thread.
+ * @return The current window scale, where zero means a failure.
+ */
+ float findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId);
+
+ /**
+ * Finds an {@link android.view.accessibility.AccessibilityNodeInfo} to take accessibility
+ * focus in the given direction. The search is performed in the window whose id is
+ * specified and starts from the node whose accessibility id is specified.
+ *
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+ * to start from the root.
+ * @param direction The direction in which to search for focusable.
+ * @param interactionId The id of the interaction for matching with the callback result.
+ * @param callback Callback which to receive the result.
+ * @param threadId The id of the calling thread.
+ * @return The current window scale, where zero means a failure.
+ */
+ float focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId);
+
+ /**
* Performs an accessibility action on an
* {@link android.view.accessibility.AccessibilityNodeInfo}.
*
@@ -113,4 +155,9 @@ interface IAccessibilityServiceConnection {
boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId,
int action, int interactionId, IAccessibilityInteractionConnectionCallback callback,
long threadId);
+
+ /**
+ * @return The associated accessibility service info.
+ */
+ AccessibilityServiceInfo getServiceInfo();
}
diff --git a/core/java/android/accessibilityservice/UiTestAutomationBridge.java b/core/java/android/accessibilityservice/UiTestAutomationBridge.java
index a898c3f..c840bd6 100644
--- a/core/java/android/accessibilityservice/UiTestAutomationBridge.java
+++ b/core/java/android/accessibilityservice/UiTestAutomationBridge.java
@@ -17,7 +17,7 @@
package android.accessibilityservice;
import android.accessibilityservice.AccessibilityService.Callbacks;
-import android.accessibilityservice.AccessibilityService.IEventListenerWrapper;
+import android.accessibilityservice.AccessibilityService.IAccessibilityServiceClientWrapper;
import android.content.Context;
import android.os.HandlerThread;
import android.os.Looper;
@@ -66,7 +66,7 @@ public class UiTestAutomationBridge {
private volatile int mConnectionId = AccessibilityInteractionClient.NO_ID;
- private IEventListenerWrapper mListener;
+ private IAccessibilityServiceClientWrapper mListener;
private AccessibilityEvent mLastEvent;
@@ -133,7 +133,7 @@ public class UiTestAutomationBridge {
mHandlerThread.start();
Looper looper = mHandlerThread.getLooper();
- mListener = new IEventListenerWrapper(null, looper, new Callbacks() {
+ mListener = new IAccessibilityServiceClientWrapper(null, looper, new Callbacks() {
@Override
public void onServiceConnected() {
/* do nothing */
@@ -175,6 +175,11 @@ public class UiTestAutomationBridge {
mLock.notifyAll();
}
}
+
+ @Override
+ public void onGesture(int gestureId) {
+ /* do nothing */
+ }
});
final IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
@@ -252,6 +257,7 @@ public class UiTestAutomationBridge {
public AccessibilityEvent executeCommandAndWaitForAccessibilityEvent(Runnable command,
Predicate<AccessibilityEvent> predicate, long timeoutMillis)
throws TimeoutException, Exception {
+ // TODO: This is broken - remove from here when finalizing this as public APIs.
synchronized (mLock) {
// Prepare to wait for an event.
mWaitingForEventDelivery = true;
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index a3fdf3e..7e1589f 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1000,7 +1000,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
}
return true;
}
-
+
case GOING_TO_SLEEP_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
goingToSleep();
@@ -1015,6 +1015,13 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case SET_LOCK_SCREEN_SHOWN_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ setLockScreenShown(data.readInt() != 0);
+ reply.writeNoException();
+ return true;
+ }
+
case SET_DEBUG_APP_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
String pn = data.readString();
@@ -2912,6 +2919,17 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
reply.recycle();
}
+ public void setLockScreenShown(boolean shown) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(shown ? 1 : 0);
+ mRemote.transact(SET_LOCK_SCREEN_SHOWN_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
public void setDebugApp(
String packageName, boolean waitForDebugger, boolean persistent)
throws RemoteException
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 138a88f..0645aa9 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -82,7 +82,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserId;
-import android.os.Vibrator;
+import android.os.SystemVibrator;
import android.os.storage.StorageManager;
import android.telephony.TelephonyManager;
import android.content.ClipboardManager;
@@ -455,7 +455,7 @@ class ContextImpl extends Context {
registerService(VIBRATOR_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
- return new Vibrator();
+ return new SystemVibrator();
}});
registerService(WALLPAPER_SERVICE, WALLPAPER_FETCHER);
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index dd58397..55f29e6 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -349,6 +349,7 @@ public class DownloadManager {
private String mMimeType;
private boolean mRoamingAllowed = true;
private int mAllowedNetworkTypes = ~0; // default to all network types allowed
+ private boolean mAllowedOverMetered = true;
private boolean mIsVisibleInDownloadsUi = true;
private boolean mScannable = false;
private boolean mUseSystemCache = false;
@@ -609,8 +610,11 @@ public class DownloadManager {
}
/**
- * Restrict the types of networks over which this download may proceed. By default, all
- * network types are allowed.
+ * Restrict the types of networks over which this download may proceed.
+ * By default, all network types are allowed. Consider using
+ * {@link #setAllowedOverMetered(boolean)} instead, since it's more
+ * flexible.
+ *
* @param flags any combination of the NETWORK_* bit flags.
* @return this object
*/
@@ -620,6 +624,17 @@ public class DownloadManager {
}
/**
+ * Set whether this download may proceed over a metered network
+ * connection. By default, metered networks are allowed.
+ *
+ * @see ConnectivityManager#isActiveNetworkMetered()
+ */
+ public Request setAllowedOverMetered(boolean allow) {
+ mAllowedOverMetered = allow;
+ return this;
+ }
+
+ /**
* Set whether this download may proceed over a roaming connection. By default, roaming is
* allowed.
* @param allowed whether to allow a roaming connection to be used
@@ -672,6 +687,7 @@ public class DownloadManager {
putIfNonNull(values, Downloads.Impl.COLUMN_DESCRIPTION, mDescription);
putIfNonNull(values, Downloads.Impl.COLUMN_MIME_TYPE, mMimeType);
+ // TODO: add COLUMN_ALLOW_METERED and persist
values.put(Downloads.Impl.COLUMN_VISIBILITY, mNotificationVisibility);
values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, mAllowedNetworkTypes);
values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, mRoamingAllowed);
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index c71b186..3fc2280 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -205,7 +205,8 @@ public interface IActivityManager extends IInterface {
// Note: probably don't want to allow applications access to these.
public void goingToSleep() throws RemoteException;
public void wakingUp() throws RemoteException;
-
+ public void setLockScreenShown(boolean shown) throws RemoteException;
+
public void unhandledBack() throws RemoteException;
public ParcelFileDescriptor openContentUri(Uri uri) throws RemoteException;
public void setDebugApp(
@@ -588,4 +589,5 @@ public interface IActivityManager extends IInterface {
int GET_CURRENT_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+144;
int TARGET_TASK_AFFINITY_MATCHES_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+145;
int NAVIGATE_UP_TO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+146;
+ int SET_LOCK_SCREEN_SHOWN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+147;
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 04c64a0..5cce25f 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -196,10 +196,9 @@ public class Notification implements Parcelable
public RemoteViews intruderView;
/**
- * A larger version of {@link #contentView}, giving the Notification an
+ * A large-format version of {@link #contentView}, giving the Notification an
* opportunity to show more detail. The system UI may choose to show this
* instead of the normal content view at its discretion.
- * @hide
*/
public RemoteViews bigContentView;
@@ -987,8 +986,6 @@ public class Notification implements Parcelable
}
/**
- * @hide
- *
* Show the {@link Notification#when} field as a countdown (or count-up) timer instead of a timestamp.
*
* @see Notification#when
@@ -1609,23 +1606,21 @@ public class Notification implements Parcelable
}
/**
- * @hide because this API is still very rough
+ * Helper class for generating large-format notifications that include a large image attachment.
*
- * This is a "rebuilder": It consumes a Builder object and modifies its output.
- *
- * This represents the "big picture" style notification, with a large Bitmap atop the usual notification.
- *
- * Usage:
+ * This class is a "rebuilder": It consumes a Builder object and modifies its behavior, like so:
* <pre class="prettyprint">
* Notification noti = new Notification.BigPictureStyle(
* new Notification.Builder()
- * .setContentTitle(&quot;New mail from &quot; + sender.toString())
+ * .setContentTitle(&quot;New photo from &quot; + sender.toString())
* .setContentText(subject)
- * .setSmallIcon(R.drawable.new_mail)
+ * .setSmallIcon(R.drawable.new_post)
* .setLargeIcon(aBitmap))
* .bigPicture(aBigBitmap)
* .build();
* </pre>
+ *
+ * @see Notification#bigContentView
*/
public static class BigPictureStyle {
private Builder mBuilder;
@@ -1656,13 +1651,9 @@ public class Notification implements Parcelable
}
/**
- * @hide because this API is still very rough
- *
- * This is a "rebuilder": It consumes a Builder object and modifies its output.
+ * Helper class for generating large-format notifications that include a lot of text.
*
- * This represents the "big text" style notification, with more area for the main content text to be read in its entirety.
- *
- * Usage:
+ * This class is a "rebuilder": It consumes a Builder object and modifies its behavior, like so:
* <pre class="prettyprint">
* Notification noti = new Notification.BigPictureStyle(
* new Notification.Builder()
@@ -1673,6 +1664,8 @@ public class Notification implements Parcelable
* .bigText(aVeryLongString)
* .build();
* </pre>
+ *
+ * @see Notification#bigContentView
*/
public static class BigTextStyle {
private Builder mBuilder;
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index a655dd4..1866830 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -21,7 +21,12 @@ import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.Html;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
import android.text.TextUtils;
+import android.text.style.URLSpan;
import android.util.Log;
import java.io.FileInputStream;
@@ -144,6 +149,8 @@ import java.util.ArrayList;
public class ClipData implements Parcelable {
static final String[] MIMETYPES_TEXT_PLAIN = new String[] {
ClipDescription.MIMETYPE_TEXT_PLAIN };
+ static final String[] MIMETYPES_TEXT_HTML = new String[] {
+ ClipDescription.MIMETYPE_TEXT_HTML };
static final String[] MIMETYPES_TEXT_URILIST = new String[] {
ClipDescription.MIMETYPE_TEXT_URILIST };
static final String[] MIMETYPES_TEXT_INTENT = new String[] {
@@ -176,6 +183,7 @@ public class ClipData implements Parcelable {
*/
public static class Item {
final CharSequence mText;
+ final String mHtmlText;
final Intent mIntent;
final Uri mUri;
@@ -184,6 +192,20 @@ public class ClipData implements Parcelable {
*/
public Item(CharSequence text) {
mText = text;
+ mHtmlText = null;
+ mIntent = null;
+ mUri = null;
+ }
+
+ /**
+ * Create an Item consisting of a single block of (possibly styled) text,
+ * with an alternative HTML formatted representation. You <em>must</em>
+ * supply a plain text representation in addition to HTML text; coercion
+ * will not be done from HTML formated text into plain text.
+ */
+ public Item(CharSequence text, String htmlText) {
+ mText = text;
+ mHtmlText = htmlText;
mIntent = null;
mUri = null;
}
@@ -193,6 +215,7 @@ public class ClipData implements Parcelable {
*/
public Item(Intent intent) {
mText = null;
+ mHtmlText = null;
mIntent = intent;
mUri = null;
}
@@ -202,16 +225,35 @@ public class ClipData implements Parcelable {
*/
public Item(Uri uri) {
mText = null;
+ mHtmlText = null;
mIntent = null;
mUri = uri;
}
/**
* Create a complex Item, containing multiple representations of
- * text, intent, and/or URI.
+ * text, Intent, and/or URI.
*/
public Item(CharSequence text, Intent intent, Uri uri) {
mText = text;
+ mHtmlText = null;
+ mIntent = intent;
+ mUri = uri;
+ }
+
+ /**
+ * Create a complex Item, containing multiple representations of
+ * text, HTML text, Intent, and/or URI. If providing HTML text, you
+ * <em>must</em> supply a plain text representation as well; coercion
+ * will not be done from HTML formated text into plain text.
+ */
+ public Item(CharSequence text, String htmlText, Intent intent, Uri uri) {
+ if (htmlText != null && text == null) {
+ throw new IllegalArgumentException(
+ "Plain text must be supplied if HTML text is supplied");
+ }
+ mText = text;
+ mHtmlText = htmlText;
mIntent = intent;
mUri = uri;
}
@@ -224,6 +266,13 @@ public class ClipData implements Parcelable {
}
/**
+ * Retrieve the raw HTML text contained in this Item.
+ */
+ public String getHtmlText() {
+ return mHtmlText;
+ }
+
+ /**
* Retrieve the raw Intent contained in this Item.
*/
public Intent getIntent() {
@@ -250,7 +299,7 @@ public class ClipData implements Parcelable {
* the content provider does not supply a text representation, return
* the raw URI as a string.
* <li> If {@link #getIntent} is non-null, convert that to an intent:
- * URI and returnit.
+ * URI and return it.
* <li> Otherwise, return an empty string.
* </ul>
*
@@ -261,12 +310,14 @@ public class ClipData implements Parcelable {
//BEGIN_INCLUDE(coerceToText)
public CharSequence coerceToText(Context context) {
// If this Item has an explicit textual value, simply return that.
- if (mText != null) {
- return mText;
+ CharSequence text = getText();
+ if (text != null) {
+ return text;
}
// If this Item has a URI value, try using that.
- if (mUri != null) {
+ Uri uri = getUri();
+ if (uri != null) {
// First see if the URI can be opened as a plain text stream
// (of any sub-type). If so, this is the best textual
@@ -275,7 +326,7 @@ public class ClipData implements Parcelable {
try {
// Ask for a stream of the desired type.
AssetFileDescriptor descr = context.getContentResolver()
- .openTypedAssetFileDescriptor(mUri, "text/*", null);
+ .openTypedAssetFileDescriptor(uri, "text/*", null);
stream = descr.createInputStream();
InputStreamReader reader = new InputStreamReader(stream, "UTF-8");
@@ -308,13 +359,14 @@ public class ClipData implements Parcelable {
// If we couldn't open the URI as a stream, then the URI itself
// probably serves fairly well as a textual representation.
- return mUri.toString();
+ return uri.toString();
}
// Finally, if all we have is an Intent, then we can just turn that
// into text. Not the most user-friendly thing, but it's something.
- if (mIntent != null) {
- return mIntent.toUri(Intent.URI_INTENT_SCHEME);
+ Intent intent = getIntent();
+ if (intent != null) {
+ return intent.toUri(Intent.URI_INTENT_SCHEME);
}
// Shouldn't get here, but just in case...
@@ -322,6 +374,210 @@ public class ClipData implements Parcelable {
}
//END_INCLUDE(coerceToText)
+ /**
+ * Like {@link #coerceToHtmlText(Context)}, but any text that would
+ * be returned as HTML formatting will be returned as text with
+ * style spans.
+ * @param context The caller's Context, from which its ContentResolver
+ * and other things can be retrieved.
+ * @return Returns the item's textual representation.
+ */
+ public CharSequence coerceToStyledText(Context context) {
+ CharSequence text = getText();
+ if (text instanceof Spanned) {
+ return text;
+ }
+ String htmlText = getHtmlText();
+ if (htmlText != null) {
+ try {
+ CharSequence newText = Html.fromHtml(htmlText);
+ if (newText != null) {
+ return newText;
+ }
+ } catch (RuntimeException e) {
+ // If anything bad happens, we'll fall back on the plain text.
+ }
+ }
+
+ if (text != null) {
+ return text;
+ }
+ return coerceToHtmlOrStyledText(context, true);
+ }
+
+ /**
+ * Turn this item into HTML text, regardless of the type of data it
+ * actually contains.
+ *
+ * <p>The algorithm for deciding what text to return is:
+ * <ul>
+ * <li> If {@link #getHtmlText} is non-null, return that.
+ * <li> If {@link #getText} is non-null, return that, converting to
+ * valid HTML text. If this text contains style spans,
+ * {@link Html#toHtml(Spanned) Html.toHtml(Spanned)} is used to
+ * convert them to HTML formatting.
+ * <li> If {@link #getUri} is non-null, try to retrieve its data
+ * as a text stream from its content provider. If the provider can
+ * supply text/html data, that will be preferred and returned as-is.
+ * Otherwise, any text/* data will be returned and escaped to HTML.
+ * If it is not a content: URI or the content provider does not supply
+ * a text representation, HTML text containing a link to the URI
+ * will be returned.
+ * <li> If {@link #getIntent} is non-null, convert that to an intent:
+ * URI and return as an HTML link.
+ * <li> Otherwise, return an empty string.
+ * </ul>
+ *
+ * @param context The caller's Context, from which its ContentResolver
+ * and other things can be retrieved.
+ * @return Returns the item's representation as HTML text.
+ */
+ public String coerceToHtmlText(Context context) {
+ // If the item has an explicit HTML value, simply return that.
+ String htmlText = getHtmlText();
+ if (htmlText != null) {
+ return htmlText;
+ }
+
+ // If this Item has a plain text value, return it as HTML.
+ CharSequence text = getText();
+ if (text != null) {
+ if (text instanceof Spanned) {
+ return Html.toHtml((Spanned)text);
+ }
+ return Html.escapeHtml(text);
+ }
+
+ text = coerceToHtmlOrStyledText(context, false);
+ return text != null ? text.toString() : null;
+ }
+
+ private CharSequence coerceToHtmlOrStyledText(Context context, boolean styled) {
+ // If this Item has a URI value, try using that.
+ if (mUri != null) {
+
+ // Check to see what data representations the content
+ // provider supports. We would like HTML text, but if that
+ // is not possible we'll live with plan text.
+ String[] types = context.getContentResolver().getStreamTypes(mUri, "text/*");
+ boolean hasHtml = false;
+ boolean hasText = false;
+ if (types != null) {
+ for (String type : types) {
+ if ("text/html".equals(type)) {
+ hasHtml = true;
+ } else if (type.startsWith("text/")) {
+ hasText = true;
+ }
+ }
+ }
+
+ // If the provider can serve data we can use, open and load it.
+ if (hasHtml || hasText) {
+ FileInputStream stream = null;
+ try {
+ // Ask for a stream of the desired type.
+ AssetFileDescriptor descr = context.getContentResolver()
+ .openTypedAssetFileDescriptor(mUri,
+ hasHtml ? "text/html" : "text/plain", null);
+ stream = descr.createInputStream();
+ InputStreamReader reader = new InputStreamReader(stream, "UTF-8");
+
+ // Got it... copy the stream into a local string and return it.
+ StringBuilder builder = new StringBuilder(128);
+ char[] buffer = new char[8192];
+ int len;
+ while ((len=reader.read(buffer)) > 0) {
+ builder.append(buffer, 0, len);
+ }
+ String text = builder.toString();
+ if (hasHtml) {
+ if (styled) {
+ // We loaded HTML formatted text and the caller
+ // want styled text, convert it.
+ try {
+ CharSequence newText = Html.fromHtml(text);
+ return newText != null ? newText : text;
+ } catch (RuntimeException e) {
+ return text;
+ }
+ } else {
+ // We loaded HTML formatted text and that is what
+ // the caller wants, just return it.
+ return text.toString();
+ }
+ }
+ if (styled) {
+ // We loaded plain text and the caller wants styled
+ // text, that is all we have so return it.
+ return text;
+ } else {
+ // We loaded plain text and the caller wants HTML
+ // text, escape it for HTML.
+ return Html.escapeHtml(text);
+ }
+
+ } catch (FileNotFoundException e) {
+ // Unable to open content URI as text... not really an
+ // error, just something to ignore.
+
+ } catch (IOException e) {
+ // Something bad has happened.
+ Log.w("ClippedData", "Failure loading text", e);
+ return Html.escapeHtml(e.toString());
+
+ } finally {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ // If we couldn't open the URI as a stream, then we can build
+ // some HTML text with the URI itself.
+ // probably serves fairly well as a textual representation.
+ if (styled) {
+ return uriToStyledText(mUri.toString());
+ } else {
+ return uriToHtml(mUri.toString());
+ }
+ }
+
+ // Finally, if all we have is an Intent, then we can just turn that
+ // into text. Not the most user-friendly thing, but it's something.
+ if (mIntent != null) {
+ if (styled) {
+ return uriToStyledText(mIntent.toUri(Intent.URI_INTENT_SCHEME));
+ } else {
+ return uriToHtml(mIntent.toUri(Intent.URI_INTENT_SCHEME));
+ }
+ }
+
+ // Shouldn't get here, but just in case...
+ return "";
+ }
+
+ private String uriToHtml(String uri) {
+ StringBuilder builder = new StringBuilder(256);
+ builder.append("<a href=\"");
+ builder.append(uri);
+ builder.append("\">");
+ builder.append(Html.escapeHtml(uri));
+ builder.append("</a>");
+ return builder.toString();
+ }
+
+ private CharSequence uriToStyledText(String uri) {
+ SpannableStringBuilder builder = new SpannableStringBuilder();
+ builder.append(uri);
+ builder.setSpan(new URLSpan(uri), 0, builder.length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ return builder;
+ }
+
@Override
public String toString() {
StringBuilder b = new StringBuilder(128);
@@ -335,7 +591,10 @@ public class ClipData implements Parcelable {
/** @hide */
public void toShortString(StringBuilder b) {
- if (mText != null) {
+ if (mHtmlText != null) {
+ b.append("H:");
+ b.append(mHtmlText);
+ } else if (mText != null) {
b.append("T:");
b.append(mText);
} else if (mUri != null) {
@@ -409,6 +668,22 @@ public class ClipData implements Parcelable {
}
/**
+ * Create a new ClipData holding data of the type
+ * {@link ClipDescription#MIMETYPE_TEXT_HTML}.
+ *
+ * @param label User-visible label for the clip data.
+ * @param text The text of clip as plain text, for receivers that don't
+ * handle HTML. This is required.
+ * @param htmlText The actual HTML text in the clip.
+ * @return Returns a new ClipData containing the specified data.
+ */
+ static public ClipData newHtmlText(CharSequence label, CharSequence text,
+ String htmlText) {
+ Item item = new Item(text, htmlText);
+ return new ClipData(label, MIMETYPES_TEXT_HTML, item);
+ }
+
+ /**
* Create a new ClipData holding an Intent with MIME type
* {@link ClipDescription#MIMETYPE_TEXT_INTENT}.
*
@@ -574,6 +849,7 @@ public class ClipData implements Parcelable {
for (int i=0; i<N; i++) {
Item item = mItems.get(i);
TextUtils.writeToParcel(item.mText, dest, flags);
+ dest.writeString(item.mHtmlText);
if (item.mIntent != null) {
dest.writeInt(1);
item.mIntent.writeToParcel(dest, flags);
@@ -600,9 +876,10 @@ public class ClipData implements Parcelable {
final int N = in.readInt();
for (int i=0; i<N; i++) {
CharSequence text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ String htmlText = in.readString();
Intent intent = in.readInt() != 0 ? Intent.CREATOR.createFromParcel(in) : null;
Uri uri = in.readInt() != 0 ? Uri.CREATOR.createFromParcel(in) : null;
- mItems.add(new Item(text, intent, uri));
+ mItems.add(new Item(text, htmlText, intent, uri));
}
}
diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java
index c6b51ef..5cb6e77 100644
--- a/core/java/android/content/ClipDescription.java
+++ b/core/java/android/content/ClipDescription.java
@@ -41,6 +41,11 @@ public class ClipDescription implements Parcelable {
public static final String MIMETYPE_TEXT_PLAIN = "text/plain";
/**
+ * The MIME type for a clip holding HTML text.
+ */
+ public static final String MIMETYPE_TEXT_HTML = "text/html";
+
+ /**
* The MIME type for a clip holding one or more URIs. This should be
* used for URIs that are meaningful to a user (such as an http: URI).
* It should <em>not</em> be used for a content: URI that references some
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 2930998..722fdc6 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -248,7 +248,7 @@ public abstract class ContentResolver {
* @param mimeTypeFilter The desired MIME type. This may be a pattern,
* such as *\/*, to query for all available MIME types that match the
* pattern.
- * @return Returns an array of MIME type strings for all availablle
+ * @return Returns an array of MIME type strings for all available
* data streams that match the given mimeTypeFilter. If there are none,
* null is returned.
*/
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 18d682d..19e4372 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -954,7 +954,18 @@ public class Intent implements Parcelable, Cloneable {
* using EXTRA_TEXT, the MIME type should be "text/plain"; otherwise it
* should be the MIME type of the data in EXTRA_STREAM. Use {@literal *}/*
* if the MIME type is unknown (this will only allow senders that can
- * handle generic data streams).
+ * handle generic data streams). If using {@link #EXTRA_TEXT}, you can
+ * also optionally supply {@link #EXTRA_HTML_TEXT} for clients to retrieve
+ * your text with HTML formatting.
+ * <p>
+ * As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}, the data
+ * being sent can be supplied through {@link #setClipData(ClipData)}. This
+ * allows you to use {@link #FLAG_GRANT_READ_URI_PERMISSION} when sharing
+ * content: URIs and other advanced features of {@link ClipData}. If
+ * using this approach, you still must supply the same data through the
+ * {@link #EXTRA_TEXT} or {@link #EXTRA_STREAM} fields described below
+ * for compatibility with old applications. If you don't set a ClipData,
+ * it will be copied there for you when calling {@link Context#startActivity(Intent)}.
* <p>
* Optional standard extras, which may be interpreted by some recipients as
* appropriate, are: {@link #EXTRA_EMAIL}, {@link #EXTRA_CC},
@@ -967,11 +978,13 @@ public class Intent implements Parcelable, Cloneable {
/**
* Activity Action: Deliver multiple data to someone else.
* <p>
- * Like ACTION_SEND, except the data is multiple.
+ * Like {@link #ACTION_SEND}, except the data is multiple.
* <p>
* Input: {@link #getType} is the MIME type of the data being sent.
* get*ArrayListExtra can have either a {@link #EXTRA_TEXT} or {@link
- * #EXTRA_STREAM} field, containing the data to be sent.
+ * #EXTRA_STREAM} field, containing the data to be sent. If using
+ * {@link #EXTRA_TEXT}, you can also optionally supply {@link #EXTRA_HTML_TEXT}
+ * for clients to retrieve your text with HTML formatting.
* <p>
* Multiple types are supported, and receivers should handle mixed types
* whenever possible. The right way for the receiver to check them is to
@@ -983,6 +996,15 @@ public class Intent implements Parcelable, Cloneable {
* be image/jpg, but if you are sending image/jpg and image/png, then the
* intent's type should be image/*.
* <p>
+ * As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}, the data
+ * being sent can be supplied through {@link #setClipData(ClipData)}. This
+ * allows you to use {@link #FLAG_GRANT_READ_URI_PERMISSION} when sharing
+ * content: URIs and other advanced features of {@link ClipData}. If
+ * using this approach, you still must supply the same data through the
+ * {@link #EXTRA_TEXT} or {@link #EXTRA_STREAM} fields described below
+ * for compatibility with old applications. If you don't set a ClipData,
+ * it will be copied there for you when calling {@link Context#startActivity(Intent)}.
+ * <p>
* Optional standard extras, which may be interpreted by some recipients as
* appropriate, are: {@link #EXTRA_EMAIL}, {@link #EXTRA_CC},
* {@link #EXTRA_BCC}, {@link #EXTRA_SUBJECT}.
@@ -2501,6 +2523,14 @@ public class Intent implements Parcelable, Cloneable {
public static final String EXTRA_TEXT = "android.intent.extra.TEXT";
/**
+ * A constant String that is associated with the Intent, used with
+ * {@link #ACTION_SEND} to supply an alternative to {@link #EXTRA_TEXT}
+ * as HTML formatted text. Note that you <em>must</em> also supply
+ * {@link #EXTRA_TEXT}.
+ */
+ public static final String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT";
+
+ /**
* A content: URI holding a stream of data associated with the Intent,
* used with {@link #ACTION_SEND} to supply the data being sent.
*/
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 56fd5f8..9b8454a 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -373,6 +373,6 @@ interface IPackageManager {
List<UserInfo> getUsers();
UserInfo getUser(int userId);
- void setPermissionEnforcement(String permission, int enforcement);
- int getPermissionEnforcement(String permission);
+ void setPermissionEnforced(String permission, boolean enforced);
+ boolean isPermissionEnforced(String permission);
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 5d890d4..675f77e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -28,6 +28,7 @@ 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;
@@ -1091,21 +1092,7 @@ public abstract class PackageManager {
= "android.content.pm.extra.VERIFICATION_INSTALL_FLAGS";
/** {@hide} */
- public static final int ENFORCEMENT_DEFAULT = 0;
- /** {@hide} */
- public static final int ENFORCEMENT_YES = 1;
-
- /** {@hide} */
- public static String enforcementToString(int enforcement) {
- switch (enforcement) {
- case ENFORCEMENT_DEFAULT:
- return "DEFAULT";
- case ENFORCEMENT_YES:
- return "YES";
- default:
- return Integer.toString(enforcement);
- }
- }
+ public static final boolean DEFAULT_ENFORCE_READ_EXTERNAL_STORAGE = !"user".equals(Build.TYPE);
/**
* Retrieve overall information about an application package that is
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 2af58be..c682852 100755
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -32,7 +32,6 @@ import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Slog;
-import android.util.SparseArray;
import android.util.TypedValue;
import android.util.LongSparseArray;
@@ -86,8 +85,8 @@ public class Resources {
// single-threaded, and after that these are immutable.
private static final LongSparseArray<Drawable.ConstantState> sPreloadedDrawables
= new LongSparseArray<Drawable.ConstantState>();
- private static final SparseArray<ColorStateList> mPreloadedColorStateLists
- = new SparseArray<ColorStateList>();
+ private static final LongSparseArray<ColorStateList> sPreloadedColorStateLists
+ = new LongSparseArray<ColorStateList>();
private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables
= new LongSparseArray<Drawable.ConstantState>();
private static boolean mPreloaded;
@@ -98,8 +97,8 @@ public class Resources {
// These are protected by the mTmpValue lock.
private final LongSparseArray<WeakReference<Drawable.ConstantState> > mDrawableCache
= new LongSparseArray<WeakReference<Drawable.ConstantState> >();
- private final SparseArray<WeakReference<ColorStateList> > mColorStateListCache
- = new SparseArray<WeakReference<ColorStateList> >();
+ private final LongSparseArray<WeakReference<ColorStateList> > mColorStateListCache
+ = new LongSparseArray<WeakReference<ColorStateList> >();
private final LongSparseArray<WeakReference<Drawable.ConstantState> > mColorDrawableCache
= new LongSparseArray<WeakReference<Drawable.ConstantState> >();
private boolean mPreloading;
@@ -118,22 +117,6 @@ public class Resources {
private CompatibilityInfo mCompatibilityInfo;
- private static final LongSparseArray<Object> EMPTY_ARRAY = new LongSparseArray<Object>(0) {
- @Override
- public void put(long k, Object o) {
- throw new UnsupportedOperationException();
- }
- @Override
- public void append(long k, Object o) {
- throw new UnsupportedOperationException();
- }
- };
-
- @SuppressWarnings("unchecked")
- private static <T> LongSparseArray<T> emptySparseArray() {
- return (LongSparseArray<T>) EMPTY_ARRAY;
- }
-
/** @hide */
public static int selectDefaultTheme(int curTheme, int targetSdkVersion) {
return selectSystemTheme(curTheme, targetSdkVersion,
@@ -180,9 +163,8 @@ public class Resources {
* @param config Desired device configuration to consider when
* selecting/computing resource values (optional).
*/
- public Resources(AssetManager assets, DisplayMetrics metrics,
- Configuration config) {
- this(assets, metrics, config, (CompatibilityInfo) null);
+ public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
+ this(assets, metrics, config, null);
}
/**
@@ -1883,7 +1865,8 @@ public class Resources {
return dr;
}
- Drawable.ConstantState cs = isColorDrawable ? sPreloadedColorDrawables.get(key) : sPreloadedDrawables.get(key);
+ Drawable.ConstantState cs = isColorDrawable ?
+ sPreloadedColorDrawables.get(key) : sPreloadedDrawables.get(key);
if (cs != null) {
dr = cs.newDrawable(this);
} else {
@@ -2005,21 +1988,21 @@ public class Resources {
}
}
- final int key = (value.assetCookie << 24) | value.data;
+ final long key = (((long) value.assetCookie) << 32) | value.data;
ColorStateList csl;
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
- csl = mPreloadedColorStateLists.get(key);
+ csl = sPreloadedColorStateLists.get(key);
if (csl != null) {
return csl;
}
csl = ColorStateList.valueOf(value.data);
if (mPreloading) {
- mPreloadedColorStateLists.put(key, csl);
+ sPreloadedColorStateLists.put(key, csl);
}
return csl;
@@ -2030,7 +2013,7 @@ public class Resources {
return csl;
}
- csl = mPreloadedColorStateLists.get(key);
+ csl = sPreloadedColorStateLists.get(key);
if (csl != null) {
return csl;
}
@@ -2063,14 +2046,13 @@ public class Resources {
if (csl != null) {
if (mPreloading) {
- mPreloadedColorStateLists.put(key, csl);
+ sPreloadedColorStateLists.put(key, csl);
} else {
synchronized (mTmpValue) {
//Log.i(TAG, "Saving cached color state list @ #" +
// Integer.toHexString(key.intValue())
// + " in " + this + ": " + csl);
- mColorStateListCache.put(
- key, new WeakReference<ColorStateList>(csl));
+ mColorStateListCache.put(key, new WeakReference<ColorStateList>(csl));
}
}
}
@@ -2078,7 +2060,7 @@ public class Resources {
return csl;
}
- private ColorStateList getCachedColorStateList(int key) {
+ private ColorStateList getCachedColorStateList(long key) {
synchronized (mTmpValue) {
WeakReference<ColorStateList> wr = mColorStateListCache.get(key);
if (wr != null) { // we have the key
@@ -2088,8 +2070,7 @@ public class Resources {
// Integer.toHexString(((Integer)key).intValue())
// + " in " + this + ": " + entry);
return entry;
- }
- else { // our entry has been purged
+ } else { // our entry has been purged
mColorStateListCache.delete(key);
}
}
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 83b6986..640b47b 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -950,11 +950,10 @@ public class Camera {
/**
* Callback interface used to notify on auto focus start and stop.
*
- * <p>This is useful for continuous autofocus -- {@link Parameters#FOCUS_MODE_CONTINUOUS_VIDEO}
- * and {@link Parameters#FOCUS_MODE_CONTINUOUS_PICTURE}. Applications can
- * show autofocus animation.</p>
- *
- * @hide
+ * <p>This is only supported in continuous autofocus modes -- {@link
+ * Parameters#FOCUS_MODE_CONTINUOUS_VIDEO} and {@link
+ * Parameters#FOCUS_MODE_CONTINUOUS_PICTURE}. Applications can show
+ * autofocus animation based on this.</p>
*/
public interface AutoFocusMoveCallback
{
@@ -962,7 +961,7 @@ public class Camera {
* Called when the camera auto focus starts or stops.
*
* @param start true if focus starts to move, false if focus stops to move
- * @param camera the Camera service object
+ * @param camera the Camera service object
*/
void onAutoFocusMoving(boolean start, Camera camera);
}
@@ -971,7 +970,6 @@ public class Camera {
* Sets camera auto-focus move callback.
*
* @param cb the callback to run
- * @hide
*/
public void setAutoFocusMoveCallback(AutoFocusMoveCallback cb) {
mAutoFocusMoveCallback = cb;
diff --git a/core/java/android/hardware/input/IInputDevicesChangedListener.aidl b/core/java/android/hardware/input/IInputDevicesChangedListener.aidl
new file mode 100644
index 0000000..5d8ada1
--- /dev/null
+++ b/core/java/android/hardware/input/IInputDevicesChangedListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.hardware.input;
+
+/** @hide */
+interface IInputDevicesChangedListener {
+ /* Called when input devices changed, such as a device being added,
+ * removed or changing configuration.
+ *
+ * The parameter is an array of pairs (deviceId, generation) indicating the current
+ * device id and generation of all input devices. The client can determine what
+ * has happened by comparing the result to its prior observations.
+ */
+ oneway void onInputDevicesChanged(in int[] deviceIdAndGeneration);
+}
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 47e0d1e..3137947 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -17,6 +17,8 @@
package android.hardware.input;
import android.hardware.input.KeyboardLayout;
+import android.hardware.input.IInputDevicesChangedListener;
+import android.os.IBinder;
import android.view.InputDevice;
import android.view.InputEvent;
@@ -42,4 +44,11 @@ interface IInputManager {
String getKeyboardLayoutForInputDevice(String inputDeviceDescriptor);
void setKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
String keyboardLayoutDescriptor);
+
+ // Registers an input devices changed listener.
+ void registerInputDevicesChangedListener(IInputDevicesChangedListener listener);
+
+ // Input device vibrator control.
+ void vibrate(int deviceId, in long[] pattern, int repeat, IBinder token);
+ void cancelVibrate(int deviceId, IBinder token);
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 3b3c237..b39b823 100755
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -19,9 +19,14 @@ package android.hardware.input;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.Vibrator;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.util.Log;
@@ -29,6 +34,8 @@ import android.util.SparseArray;
import android.view.InputDevice;
import android.view.InputEvent;
+import java.util.ArrayList;
+
/**
* Provides information about input devices and available key layouts.
* <p>
@@ -40,11 +47,22 @@ import android.view.InputEvent;
*/
public final class InputManager {
private static final String TAG = "InputManager";
+ private static final boolean DEBUG = false;
+
+ private static final int MSG_DEVICE_ADDED = 1;
+ private static final int MSG_DEVICE_REMOVED = 2;
+ private static final int MSG_DEVICE_CHANGED = 3;
private static InputManager sInstance;
private final IInputManager mIm;
- private final SparseArray<InputDevice> mInputDevices = new SparseArray<InputDevice>();
+
+ // Guarded by mInputDevicesLock
+ private final Object mInputDevicesLock = new Object();
+ private SparseArray<InputDevice> mInputDevices;
+ private InputDevicesChangedListener mInputDevicesChangedListener;
+ private final ArrayList<InputDeviceListenerDelegate> mInputDeviceListeners =
+ new ArrayList<InputDeviceListenerDelegate>();
/**
* Broadcast Action: Query available keyboard layouts.
@@ -169,6 +187,103 @@ public final class InputManager {
}
/**
+ * Gets information about the input device with the specified id.
+ * @param id The device id.
+ * @return The input device or null if not found.
+ */
+ public InputDevice getInputDevice(int id) {
+ synchronized (mInputDevicesLock) {
+ populateInputDevicesLocked();
+
+ int index = mInputDevices.indexOfKey(id);
+ if (index < 0) {
+ return null;
+ }
+
+ InputDevice inputDevice = mInputDevices.valueAt(index);
+ if (inputDevice == null) {
+ try {
+ inputDevice = mIm.getInputDevice(id);
+ } catch (RemoteException ex) {
+ throw new RuntimeException("Could not get input device information.", ex);
+ }
+ }
+ mInputDevices.setValueAt(index, inputDevice);
+ return inputDevice;
+ }
+ }
+
+ /**
+ * Gets the ids of all input devices in the system.
+ * @return The input device ids.
+ */
+ public int[] getInputDeviceIds() {
+ synchronized (mInputDevicesLock) {
+ populateInputDevicesLocked();
+
+ final int count = mInputDevices.size();
+ final int[] ids = new int[count];
+ for (int i = 0; i < count; i++) {
+ ids[i] = mInputDevices.keyAt(i);
+ }
+ return ids;
+ }
+ }
+
+ /**
+ * Registers an input device listener to receive notifications about when
+ * input devices are added, removed or changed.
+ *
+ * @param listener The listener to register.
+ * @param handler The handler on which the listener should be invoked, or null
+ * if the listener should be invoked on the calling thread's looper.
+ *
+ * @see #unregisterInputDeviceListener
+ */
+ public void registerInputDeviceListener(InputDeviceListener listener, Handler handler) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+
+ synchronized (mInputDevicesLock) {
+ int index = findInputDeviceListenerLocked(listener);
+ if (index < 0) {
+ mInputDeviceListeners.add(new InputDeviceListenerDelegate(listener, handler));
+ }
+ }
+ }
+
+ /**
+ * Unregisters an input device listener.
+ *
+ * @param listener The listener to unregister.
+ *
+ * @see #registerInputDeviceListener
+ */
+ public void unregisterInputDeviceListener(InputDeviceListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+
+ synchronized (mInputDevicesLock) {
+ int index = findInputDeviceListenerLocked(listener);
+ if (index >= 0) {
+ mInputDeviceListeners.remove(index);
+ }
+ }
+ }
+
+ private int findInputDeviceListenerLocked(InputDeviceListener listener) {
+ final int numListeners = mInputDeviceListeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ if (mInputDeviceListeners.get(i).mListener == listener) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
* Gets information about all supported keyboard layouts.
* <p>
* The input manager consults the built-in keyboard layouts as well
@@ -327,50 +442,6 @@ public final class InputManager {
}
/**
- * Gets information about the input device with the specified id.
- * @param id The device id.
- * @return The input device or null if not found.
- *
- * @hide
- */
- public InputDevice getInputDevice(int id) {
- synchronized (mInputDevices) {
- InputDevice inputDevice = mInputDevices.get(id);
- if (inputDevice != null) {
- return inputDevice;
- }
- }
- final InputDevice newInputDevice;
- try {
- newInputDevice = mIm.getInputDevice(id);
- } catch (RemoteException ex) {
- throw new RuntimeException("Could not get input device information.", ex);
- }
- synchronized (mInputDevices) {
- InputDevice inputDevice = mInputDevices.get(id);
- if (inputDevice != null) {
- return inputDevice;
- }
- mInputDevices.put(id, newInputDevice);
- return newInputDevice;
- }
- }
-
- /**
- * Gets the ids of all input devices in the system.
- * @return The input device ids.
- *
- * @hide
- */
- public int[] getInputDeviceIds() {
- try {
- return mIm.getInputDeviceIds();
- } catch (RemoteException ex) {
- throw new RuntimeException("Could not get input device ids.", ex);
- }
- }
-
- /**
* Queries the framework about whether any physical keys exist on the
* any keyboard attached to the device that are capable of producing the given
* array of key codes.
@@ -429,4 +500,201 @@ public final class InputManager {
return false;
}
}
+
+ private void populateInputDevicesLocked() {
+ if (mInputDevicesChangedListener == null) {
+ final InputDevicesChangedListener listener = new InputDevicesChangedListener();
+ try {
+ mIm.registerInputDevicesChangedListener(listener);
+ } catch (RemoteException ex) {
+ throw new RuntimeException(
+ "Could not get register input device changed listener", ex);
+ }
+ mInputDevicesChangedListener = listener;
+ }
+
+ if (mInputDevices == null) {
+ final int[] ids;
+ try {
+ ids = mIm.getInputDeviceIds();
+ } catch (RemoteException ex) {
+ throw new RuntimeException("Could not get input device ids.", ex);
+ }
+
+ mInputDevices = new SparseArray<InputDevice>();
+ for (int i = 0; i < ids.length; i++) {
+ mInputDevices.put(ids[i], null);
+ }
+ }
+ }
+
+ private void onInputDevicesChanged(int[] deviceIdAndGeneration) {
+ if (DEBUG) {
+ Log.d(TAG, "Received input devices changed.");
+ }
+
+ synchronized (mInputDevicesLock) {
+ for (int i = mInputDevices.size(); --i > 0; ) {
+ final int deviceId = mInputDevices.keyAt(i);
+ if (!containsDeviceId(deviceIdAndGeneration, deviceId)) {
+ if (DEBUG) {
+ Log.d(TAG, "Device removed: " + deviceId);
+ }
+ mInputDevices.removeAt(i);
+ sendMessageToInputDeviceListenersLocked(MSG_DEVICE_REMOVED, deviceId);
+ }
+ }
+
+ for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
+ final int deviceId = deviceIdAndGeneration[i];
+ int index = mInputDevices.indexOfKey(deviceId);
+ if (index >= 0) {
+ final InputDevice device = mInputDevices.valueAt(index);
+ if (device != null) {
+ final int generation = deviceIdAndGeneration[i + 1];
+ if (device.getGeneration() != generation) {
+ if (DEBUG) {
+ Log.d(TAG, "Device changed: " + deviceId);
+ }
+ mInputDevices.setValueAt(index, null);
+ sendMessageToInputDeviceListenersLocked(MSG_DEVICE_CHANGED, deviceId);
+ }
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Device added: " + deviceId);
+ }
+ mInputDevices.put(deviceId, null);
+ sendMessageToInputDeviceListenersLocked(MSG_DEVICE_ADDED, deviceId);
+ }
+ }
+ }
+ }
+
+ private void sendMessageToInputDeviceListenersLocked(int what, int deviceId) {
+ final int numListeners = mInputDeviceListeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ InputDeviceListenerDelegate listener = mInputDeviceListeners.get(i);
+ listener.sendMessage(listener.obtainMessage(what, deviceId, 0));
+ }
+ }
+
+ private static boolean containsDeviceId(int[] deviceIdAndGeneration, int deviceId) {
+ for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
+ if (deviceIdAndGeneration[i] == deviceId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Gets a vibrator service associated with an input device, assuming it has one.
+ * @return The vibrator, never null.
+ * @hide
+ */
+ public Vibrator getInputDeviceVibrator(int deviceId) {
+ return new InputDeviceVibrator(deviceId);
+ }
+
+ /**
+ * Listens for changes in input devices.
+ */
+ public interface InputDeviceListener {
+ /**
+ * Called whenever an input device has been added to the system.
+ * Use {@link InputManager#getInputDevice} to get more information about the device.
+ *
+ * @param deviceId The id of the input device that was added.
+ */
+ void onInputDeviceAdded(int deviceId);
+
+ /**
+ * Called whenever an input device has been removed from the system.
+ *
+ * @param deviceId The id of the input device that was removed.
+ */
+ void onInputDeviceRemoved(int deviceId);
+
+ /**
+ * Called whenever the properties of an input device have changed since they
+ * were last queried. Use {@link InputManager#getInputDevice} to get
+ * a fresh {@link InputDevice} object with the new properties.
+ *
+ * @param deviceId The id of the input device that changed.
+ */
+ void onInputDeviceChanged(int deviceId);
+ }
+
+ private final class InputDevicesChangedListener extends IInputDevicesChangedListener.Stub {
+ @Override
+ public void onInputDevicesChanged(int[] deviceIdAndGeneration) throws RemoteException {
+ InputManager.this.onInputDevicesChanged(deviceIdAndGeneration);
+ }
+ }
+
+ private static final class InputDeviceListenerDelegate extends Handler {
+ public final InputDeviceListener mListener;
+
+ public InputDeviceListenerDelegate(InputDeviceListener listener, Handler handler) {
+ super(handler != null ? handler.getLooper() : Looper.myLooper());
+ mListener = listener;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_DEVICE_ADDED:
+ mListener.onInputDeviceAdded(msg.arg1);
+ break;
+ case MSG_DEVICE_REMOVED:
+ mListener.onInputDeviceRemoved(msg.arg1);
+ break;
+ case MSG_DEVICE_CHANGED:
+ mListener.onInputDeviceChanged(msg.arg1);
+ break;
+ }
+ }
+ }
+
+ private final class InputDeviceVibrator extends Vibrator {
+ private final int mDeviceId;
+ private final Binder mToken;
+
+ public InputDeviceVibrator(int deviceId) {
+ mDeviceId = deviceId;
+ mToken = new Binder();
+ }
+
+ @Override
+ public boolean hasVibrator() {
+ return true;
+ }
+
+ @Override
+ public void vibrate(long milliseconds) {
+ vibrate(new long[] { 0, milliseconds}, -1);
+ }
+
+ @Override
+ public void vibrate(long[] pattern, int repeat) {
+ if (repeat >= pattern.length) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ try {
+ mIm.vibrate(mDeviceId, pattern, repeat, mToken);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Failed to vibrate.", ex);
+ }
+ }
+
+ @Override
+ public void cancel() {
+ try {
+ mIm.cancelVibrate(mDeviceId, mToken);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Failed to cancel vibration.", ex);
+ }
+ }
+ }
}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index de16985..ef4209f 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -373,10 +373,11 @@ public class ConnectivityManager {
}
/**
- * Gets you info about the current data network.
- * Call {@link NetworkInfo#isConnected()} on the returned {@link NetworkInfo}
- * to check if the device has a data connection.
- */
+ * Returns details about the currently active data network. When connected,
+ * this network is the default route for outgoing connections. You should
+ * always check {@link NetworkInfo#isConnected()} before initiating network
+ * traffic. This may return {@code null} when no networks are available.
+ */
public NetworkInfo getActiveNetworkInfo() {
try {
return mService.getActiveNetworkInfo();
@@ -856,4 +857,19 @@ public class ConnectivityManager {
} catch (RemoteException e) {}
return false;
}
+
+ /**
+ * Returns if the currently active data network is metered. A network is
+ * classified as metered when the user is sensitive to heavy data usage on
+ * that connection. You should check this before doing large data transfers,
+ * and warn the user or delay the operation until another network is
+ * available.
+ */
+ public boolean isActiveNetworkMetered() {
+ try {
+ return mService.isActiveNetworkMetered();
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 7046008..92aeff2 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -51,6 +51,7 @@ interface IConnectivityManager
NetworkState[] getAllNetworkState();
NetworkQuotaInfo getActiveNetworkQuotaInfo();
+ boolean isActiveNetworkMetered();
boolean setRadios(boolean onOff);
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 89c9c36..3250ae7 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -32,6 +32,7 @@ interface INetworkPolicyManager {
/** Control UID policies. */
void setAppPolicy(int appId, int policy);
int getAppPolicy(int appId);
+ int[] getAppsWithPolicy(int policy);
boolean isUidForeground(int uid);
@@ -50,5 +51,6 @@ interface INetworkPolicyManager {
boolean getRestrictBackground();
NetworkQuotaInfo getNetworkQuotaInfo(in NetworkState state);
+ boolean isNetworkMetered(in NetworkState state);
}
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 2b36131..07bfd4b 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -92,6 +92,14 @@ public class NetworkPolicyManager {
}
}
+ public int[] getAppsWithPolicy(int policy) {
+ try {
+ return mService.getAppsWithPolicy(policy);
+ } catch (RemoteException e) {
+ return new int[0];
+ }
+ }
+
public void registerListener(INetworkPolicyListener listener) {
try {
mService.registerListener(listener);
diff --git a/core/java/android/net/nsd/DnsSdServiceInfo.java b/core/java/android/net/nsd/DnsSdServiceInfo.java
index 47d6ec6..33c3eb9 100644
--- a/core/java/android/net/nsd/DnsSdServiceInfo.java
+++ b/core/java/android/net/nsd/DnsSdServiceInfo.java
@@ -19,6 +19,8 @@ package android.net.nsd;
import android.os.Parcelable;
import android.os.Parcel;
+import java.net.InetAddress;
+
/**
* Defines a service based on DNS service discovery
* {@hide}
@@ -27,20 +29,20 @@ public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable {
private String mServiceName;
- private String mRegistrationType;
+ private String mServiceType;
private DnsSdTxtRecord mTxtRecord;
- private String mHostname;
+ private InetAddress mHost;
private int mPort;
- DnsSdServiceInfo() {
+ public DnsSdServiceInfo() {
}
- DnsSdServiceInfo(String sn, String rt, DnsSdTxtRecord tr) {
+ public DnsSdServiceInfo(String sn, String rt, DnsSdTxtRecord tr) {
mServiceName = sn;
- mRegistrationType = rt;
+ mServiceType = rt;
mTxtRecord = tr;
}
@@ -59,13 +61,13 @@ public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable {
@Override
/** @hide */
public String getServiceType() {
- return mRegistrationType;
+ return mServiceType;
}
@Override
/** @hide */
public void setServiceType(String s) {
- mRegistrationType = s;
+ mServiceType = s;
}
public DnsSdTxtRecord getTxtRecord() {
@@ -76,12 +78,12 @@ public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable {
mTxtRecord = new DnsSdTxtRecord(t);
}
- public String getHostName() {
- return mHostname;
+ public InetAddress getHost() {
+ return mHost;
}
- public void setHostName(String s) {
- mHostname = s;
+ public void setHost(InetAddress s) {
+ mHost = s;
}
public int getPort() {
@@ -96,7 +98,9 @@ public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable {
StringBuffer sb = new StringBuffer();
sb.append("name: ").append(mServiceName).
- append("type: ").append(mRegistrationType).
+ append("type: ").append(mServiceType).
+ append("host: ").append(mHost).
+ append("port: ").append(mPort).
append("txtRecord: ").append(mTxtRecord);
return sb.toString();
}
@@ -109,9 +113,14 @@ public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable {
/** Implement the Parcelable interface */
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mServiceName);
- dest.writeString(mRegistrationType);
+ dest.writeString(mServiceType);
dest.writeParcelable(mTxtRecord, flags);
- dest.writeString(mHostname);
+ if (mHost != null) {
+ dest.writeByte((byte)1);
+ dest.writeByteArray(mHost.getAddress());
+ } else {
+ dest.writeByte((byte)0);
+ }
dest.writeInt(mPort);
}
@@ -121,9 +130,15 @@ public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable {
public DnsSdServiceInfo createFromParcel(Parcel in) {
DnsSdServiceInfo info = new DnsSdServiceInfo();
info.mServiceName = in.readString();
- info.mRegistrationType = in.readString();
+ info.mServiceType = in.readString();
info.mTxtRecord = in.readParcelable(null);
- info.mHostname = in.readString();
+
+ if (in.readByte() == 1) {
+ try {
+ info.mHost = InetAddress.getByAddress(in.createByteArray());
+ } catch (java.net.UnknownHostException e) {}
+ }
+
info.mPort = in.readInt();
return info;
}
diff --git a/core/java/android/net/nsd/DnsSdTxtRecord.java b/core/java/android/net/nsd/DnsSdTxtRecord.java
index 6d4342c..952e02f 100644
--- a/core/java/android/net/nsd/DnsSdTxtRecord.java
+++ b/core/java/android/net/nsd/DnsSdTxtRecord.java
@@ -24,6 +24,8 @@ package android.net.nsd;
import android.os.Parcelable;
import android.os.Parcel;
+import java.util.Arrays;
+
/**
* This class handles TXT record data for DNS based service discovery as specified at
* http://tools.ietf.org/html/draft-cheshire-dnsext-dns-sd-11
@@ -160,7 +162,7 @@ public class DnsSdTxtRecord implements Parcelable {
/* Gets the raw data in bytes */
public byte[] getRawData() {
- return mData;
+ return (byte[]) mData.clone();
}
private void insert(byte[] keyBytes, byte[] value, int index) {
@@ -279,6 +281,24 @@ public class DnsSdTxtRecord implements Parcelable {
return result != null ? result : "";
}
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof DnsSdTxtRecord)) {
+ return false;
+ }
+
+ DnsSdTxtRecord record = (DnsSdTxtRecord)o;
+ return Arrays.equals(record.mData, mData);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(mData);
+ }
+
/** Implement the Parcelable interface */
public int describeContents() {
return 0;
diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java
index a109a98..505f11b 100644
--- a/core/java/android/net/nsd/NsdManager.java
+++ b/core/java/android/net/nsd/NsdManager.java
@@ -93,6 +93,15 @@ public class NsdManager {
/** @hide */
public static final int RESOLVE_SERVICE_SUCCEEDED = BASE + 17;
+ /** @hide */
+ public static final int STOP_RESOLVE = BASE + 18;
+ /** @hide */
+ public static final int STOP_RESOLVE_FAILED = BASE + 19;
+ /** @hide */
+ public static final int STOP_RESOLVE_SUCCEEDED = BASE + 20;
+
+
+
/**
* Create a new Nsd instance. Applications use
* {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
@@ -117,10 +126,23 @@ public class NsdManager {
/**
* Indicates that the operation failed because the framework is busy and
- * unable to service the request
+ * unable to service the request.
*/
public static final int BUSY = 2;
+ /**
+ * Indicates that the operation failed because it is already active.
+ */
+ public static final int ALREADY_ACTIVE = 3;
+
+ /**
+ * Indicates that the operation failed because maximum limit on
+ * service registrations has reached.
+ */
+ public static final int MAX_REGS_REACHED = 4;
+
+
+
/** Interface for callback invocation when framework channel is connected or lost */
public interface ChannelListener {
public void onChannelConnected(Channel c);
@@ -188,6 +210,7 @@ public class NsdManager {
private DnsSdRegisterListener mDnsSdRegisterListener;
private DnsSdUpdateRegistrationListener mDnsSdUpdateListener;
private DnsSdResolveListener mDnsSdResolveListener;
+ private ActionListener mDnsSdStopResolveListener;
AsyncChannel mAsyncChannel;
ServiceHandler mHandler;
@@ -278,6 +301,16 @@ public class NsdManager {
(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;
@@ -345,6 +378,14 @@ public class NsdManager {
c.mDnsSdResolveListener = b;
}
+ /**
+ * Set the listener for stopping service resolution. Can be null.
+ */
+ public void setStopResolveListener(Channel c, ActionListener b) {
+ if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
+ c.mDnsSdStopResolveListener = b;
+ }
+
public void registerService(Channel c, DnsSdServiceInfo serviceInfo) {
if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
if (serviceInfo == null) throw new IllegalArgumentException("Null serviceInfo");
@@ -378,6 +419,13 @@ public class NsdManager {
c.mAsyncChannel.sendMessage(RESOLVE_SERVICE, serviceInfo);
}
+ public void stopServiceResolve(Channel c) {
+ if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
+ if (c.mDnsSdResolveListener == null) throw new
+ IllegalStateException("Resolve listener needs to be set first");
+ c.mAsyncChannel.sendMessage(STOP_RESOLVE);
+ }
+
/**
* Get a reference to NetworkService handler. This is used to establish
* an AsyncChannel communication with the service
diff --git a/core/java/android/nfc/INdefPushCallback.aidl b/core/java/android/nfc/INdefPushCallback.aidl
index 4e79822..1c6d5d0 100644
--- a/core/java/android/nfc/INdefPushCallback.aidl
+++ b/core/java/android/nfc/INdefPushCallback.aidl
@@ -25,7 +25,6 @@ import android.net.Uri;
interface INdefPushCallback
{
NdefMessage createMessage();
- Uri getUri();
- String getMimeType();
+ Uri[] getUris();
void onNdefPushComplete();
}
diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java
index f80dae4..7ffa575 100644
--- a/core/java/android/nfc/NfcActivityManager.java
+++ b/core/java/android/nfc/NfcActivityManager.java
@@ -108,8 +108,8 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
NdefMessage ndefMessage = null; // static NDEF message
NfcAdapter.CreateNdefMessageCallback ndefMessageCallback = null;
NfcAdapter.OnNdefPushCompleteCallback onNdefPushCompleteCallback = null;
- Uri uri = null;
- String mimeType = null;
+ NfcAdapter.CreateBeamUrisCallback uriCallback = null;
+ Uri[] uris = null;
public NfcActivityState(Activity activity) {
if (activity.getWindow().isDestroyed()) {
throw new IllegalStateException("activity is already destroyed");
@@ -128,14 +128,19 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
ndefMessage = null;
ndefMessageCallback = null;
onNdefPushCompleteCallback = null;
- uri = null;
- mimeType = null;
+ uriCallback = null;
+ uris = null;
}
@Override
public String toString() {
StringBuilder s = new StringBuilder("[").append(" ");
s.append(ndefMessage).append(" ").append(ndefMessageCallback).append(" ");
- s.append(onNdefPushCompleteCallback).append(" ").append(uri).append("]");
+ s.append(uriCallback).append(" ");
+ if (uris != null) {
+ for (Uri uri : uris) {
+ s.append(onNdefPushCompleteCallback).append(" ").append(uri).append("]");
+ }
+ }
return s.toString();
}
}
@@ -184,12 +189,25 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
mDefaultEvent = new NfcEvent(mAdapter);
}
- public void setNdefPushContentUri(Activity activity, String mimeType, Uri uri) {
+ public void setNdefPushContentUri(Activity activity, Uri[] uris) {
+ boolean isResumed;
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = getActivityState(activity);
+ state.uris = uris;
+ isResumed = state.resumed;
+ }
+ if (isResumed) {
+ requestNfcServiceCallback(true);
+ }
+ }
+
+
+ public void setNdefPushContentUriCallback(Activity activity,
+ NfcAdapter.CreateBeamUrisCallback callback) {
boolean isResumed;
synchronized (NfcActivityManager.this) {
NfcActivityState state = getActivityState(activity);
- state.uri = uri;
- state.mimeType = mimeType;
+ state.uriCallback = callback;
isResumed = state.resumed;
}
if (isResumed) {
@@ -271,24 +289,22 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
/** Callback from NFC service, usually on binder thread */
@Override
- public Uri getUri() {
+ public Uri[] getUris() {
+ Uri[] uris;
+ NfcAdapter.CreateBeamUrisCallback callback;
synchronized (NfcActivityManager.this) {
NfcActivityState state = findResumedActivityState();
if (state == null) return null;
-
- return state.uri;
+ uris = state.uris;
+ callback = state.uriCallback;
}
- }
- /** Callback from NFC service, usually on binder thread */
- @Override
- public String getMimeType() {
- synchronized (NfcActivityManager.this) {
- NfcActivityState state = findResumedActivityState();
- if (state == null) return null;
-
- return state.mimeType;
+ if (callback != null) {
+ return callback.createBeamUris(mDefaultEvent);
+ } else {
+ return uris;
}
}
+
/** Callback from NFC service, usually on binder thread */
@Override
public void onNdefPushComplete() {
@@ -358,4 +374,5 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
}
}
}
+
}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 917751c..90f5bef 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -203,6 +203,27 @@ public final class NfcAdapter {
/** @hide */
public static final int STATE_TURNING_OFF = 4;
+ /** @hide */
+ public static final String ACTION_HANDOVER_TRANSFER_STARTED =
+ "android.nfc.action.HANDOVER_TRANSFER_STARTED";
+
+ /** @hide */
+ public static final String ACTION_HANDOVER_TRANSFER_DONE =
+ "android.nfc.action.HANDOVER_TRANSFER_DONE";
+
+ /** @hide */
+ public static final String EXTRA_HANDOVER_TRANSFER_STATUS =
+ "android.nfc.extra.HANDOVER_TRANSFER_STATUS";
+
+ /** @hide */
+ public static final int HANDOVER_TRANSFER_STATUS_SUCCESS = 0;
+ /** @hide */
+ public static final int HANDOVER_TRANSFER_STATUS_FAILURE = 1;
+
+ /** @hide */
+ public static final String EXTRA_HANDOVER_TRANSFER_URI =
+ "android.nfc.extra.HANDOVER_TRANSFER_URI";
+
// Guarded by NfcAdapter.class
static boolean sIsInitialized = false;
@@ -281,6 +302,12 @@ public final class NfcAdapter {
public NdefMessage createNdefMessage(NfcEvent event);
}
+
+ // TODO javadoc
+ public interface CreateBeamUrisCallback {
+ public Uri[] createBeamUris(NfcEvent event);
+ }
+
/**
* Helper to check if this device has FEATURE_NFC, but without using
* a context.
@@ -556,16 +583,22 @@ public final class NfcAdapter {
}
}
- //TODO: Consider a callback alternative
- //TOOD: See if we get rid of mimeType
//TODO: make sure NFC service has permission for URI
+ //TODO: see if we will eventually support multiple URIs
//TODO: javadoc
- /** @hide */
- public void setBeamPushUri(String mimeType, Uri uri, Activity activity) {
+ public void setBeamPushUris(Uri[] uris, Activity activity) {
+ if (activity == null) {
+ throw new NullPointerException("activity cannot be null");
+ }
+ mNfcActivityManager.setNdefPushContentUri(activity, uris);
+ }
+
+ // TODO javadoc
+ public void setBeamPushUrisCallback(CreateBeamUrisCallback callback, Activity activity) {
if (activity == null) {
throw new NullPointerException("activity cannot be null");
}
- mNfcActivityManager.setNdefPushContentUri(activity, mimeType, uri);
+ mNfcActivityManager.setNdefPushContentUriCallback(activity, callback);
}
/**
diff --git a/core/java/android/os/NullVibrator.java b/core/java/android/os/NullVibrator.java
new file mode 100644
index 0000000..8de4e06
--- /dev/null
+++ b/core/java/android/os/NullVibrator.java
@@ -0,0 +1,55 @@
+/*
+ * 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.os;
+
+import android.util.Log;
+
+/**
+ * Vibrator implementation that does nothing.
+ *
+ * @hide
+ */
+public class NullVibrator extends Vibrator {
+ private static final NullVibrator sInstance = new NullVibrator();
+
+ private NullVibrator() {
+ }
+
+ public static NullVibrator getInstance() {
+ return sInstance;
+ }
+
+ @Override
+ public boolean hasVibrator() {
+ return false;
+ }
+
+ @Override
+ public void vibrate(long milliseconds) {
+ }
+
+ @Override
+ public void vibrate(long[] pattern, int repeat) {
+ if (repeat >= pattern.length) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ }
+
+ @Override
+ public void cancel() {
+ }
+}
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
new file mode 100644
index 0000000..7c5a47e
--- /dev/null
+++ b/core/java/android/os/SystemVibrator.java
@@ -0,0 +1,94 @@
+/*
+ * 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.os;
+
+import android.util.Log;
+
+/**
+ * Vibrator implementation that controls the main system vibrator.
+ *
+ * @hide
+ */
+public class SystemVibrator extends Vibrator {
+ private static final String TAG = "Vibrator";
+
+ private final IVibratorService mService;
+ private final Binder mToken = new Binder();
+
+ public SystemVibrator() {
+ mService = IVibratorService.Stub.asInterface(
+ ServiceManager.getService("vibrator"));
+ }
+
+ @Override
+ public boolean hasVibrator() {
+ if (mService == null) {
+ Log.w(TAG, "Failed to vibrate; no vibrator service.");
+ return false;
+ }
+ try {
+ return mService.hasVibrator();
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ @Override
+ public void vibrate(long milliseconds) {
+ if (mService == null) {
+ Log.w(TAG, "Failed to vibrate; no vibrator service.");
+ return;
+ }
+ try {
+ mService.vibrate(milliseconds, mToken);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to vibrate.", e);
+ }
+ }
+
+ @Override
+ public void vibrate(long[] pattern, int repeat) {
+ if (mService == null) {
+ Log.w(TAG, "Failed to vibrate; no vibrator service.");
+ return;
+ }
+ // catch this here because the server will do nothing. pattern may
+ // not be null, let that be checked, because the server will drop it
+ // anyway
+ if (repeat < pattern.length) {
+ try {
+ mService.vibratePattern(pattern, repeat, mToken);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to vibrate.", e);
+ }
+ } else {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ }
+
+ @Override
+ public void cancel() {
+ if (mService == null) {
+ return;
+ }
+ try {
+ mService.cancelVibrate(mToken);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to cancel vibration.", e);
+ }
+ }
+}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 3769cfe..3f783c9 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -16,61 +16,37 @@
package android.os;
-import android.util.Log;
+import android.content.Context;
/**
* Class that operates the vibrator on the device.
* <p>
* If your process exits, any vibration you started with will stop.
* </p>
+ *
+ * To obtain an instance of the system vibrator, call
+ * {@link Context#getSystemService} with {@link Context#VIBRATOR_SERVICE} as argument.
*/
-public class Vibrator
-{
- private static final String TAG = "Vibrator";
-
- IVibratorService mService;
- private final Binder mToken = new Binder();
-
- /** @hide */
- public Vibrator()
- {
- mService = IVibratorService.Stub.asInterface(
- ServiceManager.getService("vibrator"));
+public abstract class Vibrator {
+ /**
+ * @hide to prevent subclassing from outside of the framework
+ */
+ public Vibrator() {
}
/**
- * Check whether the hardware has a vibrator. Returns true if a vibrator
- * exists, else false.
+ * Check whether the hardware has a vibrator.
+ *
+ * @return True if the hardware has a vibrator, else false.
*/
- public boolean hasVibrator() {
- if (mService == null) {
- Log.w(TAG, "Failed to vibrate; no vibrator service.");
- return false;
- }
- try {
- return mService.hasVibrator();
- } catch (RemoteException e) {
- }
- return false;
- }
+ public abstract boolean hasVibrator();
/**
- * Turn the vibrator on.
+ * Vibrate constantly for the specified period of time.
*
* @param milliseconds The number of milliseconds to vibrate.
*/
- public void vibrate(long milliseconds)
- {
- if (mService == null) {
- Log.w(TAG, "Failed to vibrate; no vibrator service.");
- return;
- }
- try {
- mService.vibrate(milliseconds, mToken);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to vibrate.", e);
- }
- }
+ public abstract void vibrate(long milliseconds);
/**
* Vibrate with a given pattern.
@@ -90,38 +66,10 @@ public class Vibrator
* @param repeat the index into pattern at which to repeat, or -1 if
* you don't want to repeat.
*/
- public void vibrate(long[] pattern, int repeat)
- {
- if (mService == null) {
- Log.w(TAG, "Failed to vibrate; no vibrator service.");
- return;
- }
- // catch this here because the server will do nothing. pattern may
- // not be null, let that be checked, because the server will drop it
- // anyway
- if (repeat < pattern.length) {
- try {
- mService.vibratePattern(pattern, repeat, mToken);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to vibrate.", e);
- }
- } else {
- throw new ArrayIndexOutOfBoundsException();
- }
- }
+ public abstract void vibrate(long[] pattern, int repeat);
/**
* Turn the vibrator off.
*/
- public void cancel()
- {
- if (mService == null) {
- return;
- }
- try {
- mService.cancelVibrate(mToken);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to cancel vibration.", e);
- }
- }
+ public abstract void cancel();
}
diff --git a/core/java/android/provider/BrowserContract.java b/core/java/android/provider/BrowserContract.java
index d678205..118b5eb 100644
--- a/core/java/android/provider/BrowserContract.java
+++ b/core/java/android/provider/BrowserContract.java
@@ -30,6 +30,15 @@ import android.os.RemoteException;
import android.util.Pair;
/**
+ * <p>
+ * The contract between the browser provider and applications. Contains the definition
+ * for the supported URIS and columns.
+ * </p>
+ * <h3>Overview</h3>
+ * <p>
+ * BrowserContract defines an database of browser-related information which are bookmarks,
+ * history, images and the mapping between the image and URL.
+ * </p>
* @hide
*/
public class BrowserContract {
@@ -45,12 +54,14 @@ public class BrowserContract {
* the dirty flag is not automatically set and the "syncToNetwork" parameter
* is set to false when calling
* {@link ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)}.
+ * @hide
*/
public static final String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter";
/**
* A parameter for use when querying any table that allows specifying a limit on the number
* of rows returned.
+ * @hide
*/
public static final String PARAM_LIMIT = "limit";
@@ -58,6 +69,8 @@ public class BrowserContract {
* Generic columns for use by sync adapters. The specific functions of
* these columns are private to the sync adapter. Other clients of the API
* should not attempt to either read or write these columns.
+ *
+ * @hide
*/
interface BaseSyncColumns {
/** Generic column for use by sync adapters. */
@@ -74,6 +87,7 @@ public class BrowserContract {
/**
* Convenience definitions for use in implementing chrome bookmarks sync in the Bookmarks table.
+ * @hide
*/
public static final class ChromeSyncColumns {
private ChromeSyncColumns() {}
@@ -93,6 +107,7 @@ public class BrowserContract {
/**
* Columns that appear when each row of a table belongs to a specific
* account, including sync information that an account may need.
+ * @hide
*/
interface SyncColumns extends BaseSyncColumns {
/**
@@ -144,13 +159,14 @@ public class BrowserContract {
public static final String _ID = "_id";
/**
- * The URL of the bookmark.
+ * This column is valid when the row is a URL. The history table's URL
+ * can not be updated.
* <P>Type: TEXT (URL)</P>
*/
public static final String URL = "url";
/**
- * The user visible title of the bookmark.
+ * The user visible title.
* <P>Type: TEXT</P>
*/
public static final String TITLE = "title";
@@ -159,10 +175,14 @@ public class BrowserContract {
* The time that this row was created on its originating client (msecs
* since the epoch).
* <P>Type: INTEGER</P>
+ * @hide
*/
public static final String DATE_CREATED = "created";
}
+ /**
+ * @hide
+ */
interface ImageColumns {
/**
* The favicon of the bookmark, may be NULL.
@@ -182,7 +202,6 @@ public class BrowserContract {
* The touch icon for the web page, may be NULL.
* Must decode via {@link BitmapFactory#decodeByteArray}.
* <p>Type: BLOB (image)</p>
- * @hide
*/
public static final String TOUCH_ICON = "touch_icon";
}
@@ -200,9 +219,26 @@ public class BrowserContract {
*/
public static final String VISITS = "visits";
+ /**
+ * @hide
+ */
public static final String USER_ENTERED = "user_entered";
}
+ interface ImageMappingColumns {
+ /**
+ * The ID of the image in Images. One image can map onto the multiple URLs.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String IMAGE_ID = "image_id";
+
+ /**
+ * The URL. The URL can map onto the different type of images.
+ * <P>Type: TEXT (URL)</P>
+ */
+ public static final String URL = "url";
+ }
+
/**
* The bookmarks table, which holds the user's browser bookmarks.
*/
@@ -218,24 +254,71 @@ public class BrowserContract {
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "bookmarks");
/**
+ * Used in {@link Bookmarks#TYPE} column and indicats the row is a bookmark.
+ */
+ public static final int BOOKMARK_TYPE_BOOKMARK = 1;
+
+ /**
+ * Used in {@link Bookmarks#TYPE} column and indicats the row is a folder.
+ */
+ public static final int BOOKMARK_TYPE_FOLDER = 2;
+
+ /**
+ * Used in {@link Bookmarks#TYPE} column and indicats the row is the bookmark bar folder.
+ */
+ public static final int BOOKMARK_TYPE_BOOKMARK_BAR_FOLDER = 3;
+
+ /**
+ * Used in {@link Bookmarks#TYPE} column and indicats the row is other folder and
+ */
+ public static final int BOOKMARK_TYPE_OTHER_FOLDER = 4;
+
+ /**
+ * Used in {@link Bookmarks#TYPE} column and indicats the row is other folder, .
+ */
+ public static final int BOOKMARK_TYPE_MOBILE_FOLDER = 5;
+
+ /**
+ * The type of the item.
+ * <P>Type: INTEGER</P>
+ * <p>Allowed values are:</p>
+ * <p>
+ * <ul>
+ * <li>{@link #BOOKMARK_TYPE_BOOKMARK}</li>
+ * <li>{@link #BOOKMARK_TYPE_FOLDER}</li>
+ * <li>{@link #BOOKMARK_TYPE_BOOKMARK_BAR_FOLDER}</li>
+ * <li>{@link #BOOKMARK_TYPE_OTHER_FOLDER}</li>
+ * <li>{@link #BOOKMARK_TYPE_MOBILE_FOLDER}</li>
+ * </ul>
+ * </p>
+ * <p> The TYPE_BOOKMARK_BAR_FOLDER, TYPE_OTHER_FOLDER and TYPE_MOBILE_FOLDER
+ * can not be updated or deleted.</p>
+ */
+ public static final String TYPE = "type";
+
+ /**
* The content:// style URI for the default folder
+ * @hide
*/
public static final Uri CONTENT_URI_DEFAULT_FOLDER =
Uri.withAppendedPath(CONTENT_URI, "folder");
/**
* Query parameter used to specify an account name
+ * @hide
*/
public static final String PARAM_ACCOUNT_NAME = "acct_name";
/**
* Query parameter used to specify an account type
+ * @hide
*/
public static final String PARAM_ACCOUNT_TYPE = "acct_type";
/**
* Builds a URI that points to a specific folder.
* @param folderId the ID of the folder to point to
+ * @hide
*/
public static final Uri buildFolderUri(long folderId) {
return ContentUris.withAppendedId(CONTENT_URI_DEFAULT_FOLDER, folderId);
@@ -255,6 +338,7 @@ public class BrowserContract {
* Query parameter to use if you want to see deleted bookmarks that are still
* around on the device and haven't been synced yet.
* @see #IS_DELETED
+ * @hide
*/
public static final String QUERY_PARAMETER_SHOW_DELETED = "show_deleted";
@@ -262,6 +346,7 @@ public class BrowserContract {
* Flag indicating if an item is a folder or bookmark. Non-zero values indicate
* a folder and zero indicates a bookmark.
* <P>Type: INTEGER (boolean)</P>
+ * @hide
*/
public static final String IS_FOLDER = "folder";
@@ -274,6 +359,7 @@ public class BrowserContract {
/**
* The source ID for an item's parent. Read-only.
* @see #PARENT
+ * @hide
*/
public static final String PARENT_SOURCE_ID = "parent_source";
@@ -281,6 +367,7 @@ public class BrowserContract {
* The position of the bookmark in relation to it's siblings that share the same
* {@link #PARENT}. May be negative.
* <P>Type: INTEGER</P>
+ * @hide
*/
public static final String POSITION = "position";
@@ -288,6 +375,7 @@ public class BrowserContract {
* The item that the bookmark should be inserted after.
* May be negative.
* <P>Type: INTEGER</P>
+ * @hide
*/
public static final String INSERT_AFTER = "insert_after";
@@ -296,6 +384,7 @@ public class BrowserContract {
* May be negative.
* <P>Type: INTEGER</P>
* @see #INSERT_AFTER
+ * @hide
*/
public static final String INSERT_AFTER_SOURCE_ID = "insert_after_source";
@@ -305,12 +394,14 @@ public class BrowserContract {
* to the URI when performing your query.
* <p>Type: INTEGER (non-zero if the item has been deleted, zero if it hasn't)
* @see #QUERY_PARAMETER_SHOW_DELETED
+ * @hide
*/
public static final String IS_DELETED = "deleted";
}
/**
* Read-only table that lists all the accounts that are used to provide bookmarks.
+ * @hide
*/
public static final class Accounts {
/**
@@ -410,6 +501,7 @@ public class BrowserContract {
* A table provided for sync adapters to use for storing private sync state data.
*
* @see SyncStateContract
+ * @hide
*/
public static final class SyncState implements SyncStateContract.Columns {
/**
@@ -459,8 +551,18 @@ public class BrowserContract {
}
/**
- * Stores images for URLs. Only support query() and update().
- * @hide
+ * <p>
+ * Stores images for URLs.
+ * </p>
+ * <p>
+ * The rows in this table can not be updated since there might have multiple URLs mapping onto
+ * the same image. If you want to update a URL's image, you need to add the new image in this
+ * table, then update the mapping onto the added image.
+ * </p>
+ * <p>
+ * Every image should be at least associated with one URL, otherwise it will be removed after a
+ * while.
+ * </p>
*/
public static final class Images implements ImageColumns {
/**
@@ -474,15 +576,93 @@ public class BrowserContract {
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "images");
/**
+ * The MIME type of {@link #CONTENT_URI} providing a directory of images.
+ */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/images";
+
+ /**
+ * The MIME type of a {@link #CONTENT_URI} of a single image.
+ */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/images";
+
+ /**
+ * Used in {@link Images#TYPE} column and indicats the row is a favicon.
+ */
+ public static final int IMAGE_TYPE_FAVICON = 1;
+
+ /**
+ * Used in {@link Images#TYPE} column and indicats the row is a precomposed touch icon.
+ */
+ public static final int IMAGE_TYPE_PRECOMPOSED_TOUCH_ICON = 2;
+
+ /**
+ * Used in {@link Images#TYPE} column and indicats the row is a touch icon.
+ */
+ public static final int IMAGE_TYPE_TOUCH_ICON = 4;
+
+ /**
+ * The type of item in the table.
+ * <P>Type: INTEGER</P>
+ * <p>Allowed values are:</p>
+ * <p>
+ * <ul>
+ * <li>{@link #IMAGE_TYPE_FAVICON}</li>
+ * <li>{@link #IMAGE_TYPE_PRECOMPOSED_TOUCH_ICON}</li>
+ * <li>{@link #IMAGE_TYPE_TOUCH_ICON}</li>
+ * </ul>
+ * </p>
+ */
+ public static final String TYPE = "type";
+
+ /**
+ * The image data.
+ * <p>Type: BLOB (image)</p>
+ */
+ public static final String DATA = "data";
+
+ /**
* The URL the images came from.
* <P>Type: TEXT (URL)</P>
+ * @hide
*/
public static final String URL = "url_key";
}
/**
+ * <p>
+ * A table that stores the mappings between the image and the URL.
+ * </p>
+ * <p>
+ * Deleting or Updating a mapping might also deletes the mapped image if there is no other URL
+ * maps onto it.
+ * </p>
+ */
+ public static final class ImageMappings implements ImageMappingColumns {
+ /**
+ * This utility class cannot be instantiated
+ */
+ private ImageMappings() {}
+
+ /**
+ * The content:// style URI for this table
+ */
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "image_mappings");
+
+ /**
+ * The MIME type of {@link #CONTENT_URI} providing a directory of image mappings.
+ */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/image_mappings";
+
+ /**
+ * The MIME type of a {@link #CONTENT_URI} of a single image mapping.
+ */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/image_mappings";
+ }
+
+ /**
* A combined view of bookmarks and history. All bookmarks in all folders are included and
* no folders are included.
+ * @hide
*/
public static final class Combined implements CommonColumns, HistoryColumns, ImageColumns {
/**
@@ -505,6 +685,7 @@ public class BrowserContract {
/**
* A table that stores settings specific to the browser. Only support query and insert.
+ * @hide
*/
public static final class Settings {
/**
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 0e9306b..035d8c4 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -1536,7 +1536,11 @@ public final class ContactsContract {
*
* @param resolver the ContentResolver to use
* @param contactId the person who was contacted
+ *
+ * @deprecated The class DataUsageStatUpdater of the Android support library should
+ * be used instead.
*/
+ @Deprecated
public static void markAsContacted(ContentResolver resolver, long contactId) {
Uri uri = ContentUris.withAppendedId(CONTENT_URI, contactId);
ContentValues values = new ContentValues();
@@ -7452,7 +7456,7 @@ public final class ContactsContract {
/**
* <p>
* API allowing applications to send usage information for each {@link Data} row to the
- * Contacts Provider.
+ * Contacts Provider. Applications can also clear all usage information.
* </p>
* <p>
* With the feedback, Contacts Provider may return more contextually appropriate results for
@@ -7497,6 +7501,12 @@ public final class ContactsContract {
* boolean successful = resolver.update(uri, new ContentValues(), null, null) > 0;
* </pre>
* </p>
+ * <p>
+ * Applications can also clear all usage information with:
+ * <pre>
+ * boolean successful = resolver.delete(DataUsageFeedback.DELETE_USAGE_URI, null, null) > 0;
+ * </pre>
+ * </p>
*/
public static final class DataUsageFeedback {
@@ -7508,6 +7518,14 @@ public final class ContactsContract {
Uri.withAppendedPath(Data.CONTENT_URI, "usagefeedback");
/**
+ * The content:// style URI for deleting all usage information.
+ * Must be used with {@link ContentResolver#delete(Uri, String, String[])}.
+ * The {@code where} and {@code selectionArgs} parameters are ignored.
+ */
+ public static final Uri DELETE_USAGE_URI =
+ Uri.withAppendedPath(Contacts.CONTENT_URI, "delete_usage");
+
+ /**
* <p>
* Name for query parameter specifying the type of data usage.
* </p>
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index bd6170b..cd8d51f 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -704,6 +704,37 @@ public final class Downloads {
*/
public static final int STATUS_BLOCKED = 498;
+ /** {@hide} */
+ public static String statusToString(int status) {
+ switch (status) {
+ case STATUS_PENDING: return "PENDING";
+ case STATUS_RUNNING: return "RUNNING";
+ case STATUS_PAUSED_BY_APP: return "PAUSED_BY_APP";
+ case STATUS_WAITING_TO_RETRY: return "WAITING_TO_RETRY";
+ case STATUS_WAITING_FOR_NETWORK: return "WAITING_FOR_NETWORK";
+ case STATUS_QUEUED_FOR_WIFI: return "QUEUED_FOR_WIFI";
+ case STATUS_INSUFFICIENT_SPACE_ERROR: return "INSUFFICIENT_SPACE_ERROR";
+ case STATUS_DEVICE_NOT_FOUND_ERROR: return "DEVICE_NOT_FOUND_ERROR";
+ case STATUS_SUCCESS: return "SUCCESS";
+ case STATUS_BAD_REQUEST: return "BAD_REQUEST";
+ case STATUS_NOT_ACCEPTABLE: return "NOT_ACCEPTABLE";
+ case STATUS_LENGTH_REQUIRED: return "LENGTH_REQUIRED";
+ case STATUS_PRECONDITION_FAILED: return "PRECONDITION_FAILED";
+ case STATUS_FILE_ALREADY_EXISTS_ERROR: return "FILE_ALREADY_EXISTS_ERROR";
+ case STATUS_CANNOT_RESUME: return "CANNOT_RESUME";
+ case STATUS_CANCELED: return "CANCELED";
+ case STATUS_UNKNOWN_ERROR: return "UNKNOWN_ERROR";
+ case STATUS_FILE_ERROR: return "FILE_ERROR";
+ case STATUS_UNHANDLED_REDIRECT: return "UNHANDLED_REDIRECT";
+ case STATUS_UNHANDLED_HTTP_CODE: return "UNHANDLED_HTTP_CODE";
+ case STATUS_HTTP_DATA_ERROR: return "HTTP_DATA_ERROR";
+ case STATUS_HTTP_EXCEPTION: return "HTTP_EXCEPTION";
+ case STATUS_TOO_MANY_REDIRECTS: return "TOO_MANY_REDIRECTS";
+ case STATUS_BLOCKED: return "BLOCKED";
+ default: return Integer.toString(status);
+ }
+ }
+
/**
* This download is visible but only shows in the notifications
* while it's in progress.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2aaf548..6dfbb2f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1464,6 +1464,20 @@ public final class Settings {
public static final String VIBRATE_ON = "vibrate_on";
/**
+ * If 1, redirects the system vibrator to all currently attached input devices
+ * that support vibration. If there are no such input devices, then the system
+ * vibrator is used instead.
+ * If 0, does not register the system vibrator.
+ *
+ * This setting is mainly intended to provide a compatibility mechanism for
+ * applications that only know about the system vibrator and do not use the
+ * input device vibrator API.
+ *
+ * @hide
+ */
+ public static final String VIBRATE_INPUT_DEVICES = "vibrate_input_devices";
+
+ /**
* Ringer volume. This is used internally, changing this value will not
* change the volume. See AudioManager.
*/
@@ -1970,6 +1984,7 @@ public final class Settings {
SCREEN_BRIGHTNESS_MODE,
SCREEN_AUTO_BRIGHTNESS_ADJ,
VIBRATE_ON,
+ VIBRATE_INPUT_DEVICES,
MODE_RINGER,
MODE_RINGER_STREAMS_AFFECTED,
MUTE_STREAMS_AFFECTED,
diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java
index 8c97293..35e2e4a 100644
--- a/core/java/android/text/Html.java
+++ b/core/java/android/text/Html.java
@@ -147,6 +147,15 @@ public class Html {
return out.toString();
}
+ /**
+ * Returns an HTML escaped representation of the given plain text.
+ */
+ public static String escapeHtml(CharSequence text) {
+ StringBuilder out = new StringBuilder();
+ withinStyle(out, text, 0, text.length());
+ return out.toString();
+ }
+
private static void withinHtml(StringBuilder out, Spanned text) {
int len = text.length();
@@ -370,7 +379,7 @@ public class Html {
}
}
- private static void withinStyle(StringBuilder out, Spanned text,
+ private static void withinStyle(StringBuilder out, CharSequence text,
int start, int end) {
for (int i = start; i < end; i++) {
char c = text.charAt(i);
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index 6056c75..11c169e 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -75,7 +75,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
if (spans[i] instanceof NoCopySpan) {
continue;
}
-
+
int st = sp.getSpanStart(spans[i]) - start;
int en = sp.getSpanEnd(spans[i]) - start;
int fl = sp.getSpanFlags(spans[i]);
@@ -212,7 +212,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
if (mGapLength > 2 * length())
resizeFor(length());
-
+
return ret; // == this
}
@@ -220,7 +220,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
public void clear() {
replace(0, length(), "", 0, 0);
}
-
+
// Documentation from interface
public void clearSpans() {
for (int i = mSpanCount - 1; i >= 0; i--) {
@@ -257,45 +257,50 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
return append(String.valueOf(text));
}
- private void change(int start, int end, CharSequence tb, int tbstart, int tbend) {
- checkRange("replace", start, end);
+ private void change(int start, int end, CharSequence cs, int csStart, int csEnd) {
+ // Can be negative
+ final int nbNewChars = (csEnd - csStart) - (end - start);
for (int i = mSpanCount - 1; i >= 0; i--) {
- if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) {
- int st = mSpanStarts[i];
- if (st > mGapStart)
- st -= mGapLength;
+ int spanStart = mSpanStarts[i];
+ if (spanStart > mGapStart)
+ spanStart -= mGapLength;
- int en = mSpanEnds[i];
- if (en > mGapStart)
- en -= mGapLength;
+ int spanEnd = mSpanEnds[i];
+ if (spanEnd > mGapStart)
+ spanEnd -= mGapLength;
- int ost = st;
- int oen = en;
+ if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) {
+ int ost = spanStart;
+ int oen = spanEnd;
int clen = length();
- if (st > start && st <= end) {
- for (st = end; st < clen; st++)
- if (st > end && charAt(st - 1) == '\n')
+ if (spanStart > start && spanStart <= end) {
+ for (spanStart = end; spanStart < clen; spanStart++)
+ if (spanStart > end && charAt(spanStart - 1) == '\n')
break;
}
- if (en > start && en <= end) {
- for (en = end; en < clen; en++)
- if (en > end && charAt(en - 1) == '\n')
+ if (spanEnd > start && spanEnd <= end) {
+ for (spanEnd = end; spanEnd < clen; spanEnd++)
+ if (spanEnd > end && charAt(spanEnd - 1) == '\n')
break;
}
- if (st != ost || en != oen)
- setSpan(false, mSpans[i], st, en, mSpanFlags[i]);
+ if (spanStart != ost || spanEnd != oen)
+ setSpan(false, mSpans[i], spanStart, spanEnd, mSpanFlags[i]);
}
+
+ int flags = 0;
+ if (spanStart == start) flags |= SPAN_START_AT_START;
+ else if (spanStart == end + nbNewChars) flags |= SPAN_START_AT_END;
+ if (spanEnd == start) flags |= SPAN_END_AT_START;
+ else if (spanEnd == end + nbNewChars) flags |= SPAN_END_AT_END;
+ mSpanFlags[i] |= flags;
}
moveGapTo(end);
- // Can be negative
- final int nbNewChars = (tbend - tbstart) - (end - start);
-
if (nbNewChars >= mGapLength) {
resizeFor(mText.length + nbNewChars - mGapLength);
}
@@ -306,7 +311,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
if (mGapLength < 1)
new Exception("mGapLength < 1").printStackTrace();
- TextUtils.getChars(tb, tbstart, tbend, mText, start);
+ TextUtils.getChars(cs, csStart, csEnd, mText, start);
if (end > start) {
// no need for span fixup on pure insertion
@@ -340,21 +345,23 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
}
}
- if (tb instanceof Spanned) {
- Spanned sp = (Spanned) tb;
- Object[] spans = sp.getSpans(tbstart, tbend, Object.class);
+ mSpanCountBeforeAdd = mSpanCount;
+
+ if (cs instanceof Spanned) {
+ Spanned sp = (Spanned) cs;
+ Object[] spans = sp.getSpans(csStart, csEnd, Object.class);
for (int i = 0; i < spans.length; i++) {
int st = sp.getSpanStart(spans[i]);
int en = sp.getSpanEnd(spans[i]);
- if (st < tbstart) st = tbstart;
- if (en > tbend) en = tbend;
+ if (st < csStart) st = csStart;
+ if (en > csEnd) en = csEnd;
// Add span only if this object is not yet used as a span in this string
- if (getSpanStart(spans[i]) < 0) {
- setSpan(false, spans[i], st - tbstart + start, en - tbstart + start,
- sp.getSpanFlags(spans[i]));
+ if (getSpanStart(spans[i]) < 0 && !(spans[i] instanceof SpanWatcher)) {
+ setSpan(false, spans[i], st - csStart + start, en - csStart + start,
+ sp.getSpanFlags(spans[i]));
}
}
}
@@ -390,6 +397,8 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
// Documentation from interface
public SpannableStringBuilder replace(final int start, final int end,
CharSequence tb, int tbstart, int tbend) {
+ checkRange("replace", start, end);
+
int filtercount = mFilters.length;
for (int i = 0; i < filtercount; i++) {
CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, this, start, end);
@@ -404,10 +413,6 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
final int origLen = end - start;
final int newLen = tbend - tbstart;
- if (origLen == 0 && newLen == 0) {
- return this;
- }
-
TextWatcher[] textWatchers = getSpans(start, start + origLen, TextWatcher.class);
sendBeforeTextChanged(textWatchers, start, origLen, newLen);
@@ -415,43 +420,101 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
// a text replacement. If replaced or replacement text length is zero, this
// is already taken care of.
boolean adjustSelection = origLen != 0 && newLen != 0;
- int selstart = 0;
- int selend = 0;
+ int selectionStart = 0;
+ int selectionEnd = 0;
if (adjustSelection) {
- selstart = Selection.getSelectionStart(this);
- selend = Selection.getSelectionEnd(this);
+ selectionStart = Selection.getSelectionStart(this);
+ selectionEnd = Selection.getSelectionEnd(this);
}
- checkRange("replace", start, end);
-
change(start, end, tb, tbstart, tbend);
if (adjustSelection) {
- if (selstart > start && selstart < end) {
- long off = selstart - start;
-
- off = off * newLen / origLen;
- selstart = (int) off + start;
+ if (selectionStart > start && selectionStart < end) {
+ final int offset = (selectionStart - start) * newLen / origLen;
+ selectionStart = start + offset;
- setSpan(false, Selection.SELECTION_START, selstart, selstart,
+ setSpan(false, Selection.SELECTION_START, selectionStart, selectionStart,
Spanned.SPAN_POINT_POINT);
}
- if (selend > start && selend < end) {
- long off = selend - start;
+ if (selectionEnd > start && selectionEnd < end) {
+ final int offset = (selectionEnd - start) * newLen / origLen;
+ selectionEnd = start + offset;
- off = off * newLen / origLen;
- selend = (int) off + start;
-
- setSpan(false, Selection.SELECTION_END, selend, selend, Spanned.SPAN_POINT_POINT);
+ setSpan(false, Selection.SELECTION_END, selectionEnd, selectionEnd,
+ Spanned.SPAN_POINT_POINT);
}
}
sendTextChanged(textWatchers, start, origLen, newLen);
sendAfterTextChanged(textWatchers);
+ // Span watchers need to be called after text watchers, which may update the layout
+ sendToSpanWatchers(start, end, newLen - origLen);
+
return this;
}
+ private void sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars) {
+ for (int i = 0; i < mSpanCountBeforeAdd; i++) {
+ int spanStart = mSpanStarts[i];
+ int spanEnd = mSpanEnds[i];
+ if (spanStart > mGapStart) spanStart -= mGapLength;
+ if (spanEnd > mGapStart) spanEnd -= mGapLength;
+ int spanFlags = mSpanFlags[i];
+
+ int newReplaceEnd = replaceEnd + nbNewChars;
+ boolean spanChanged = false;
+ int previousSpanStart = spanStart;
+ if (spanStart > newReplaceEnd) {
+ if (nbNewChars != 0) {
+ previousSpanStart -= nbNewChars;
+ spanChanged = true;
+ }
+ } else if (spanStart >= replaceStart) {
+ // No change if span start was already at replace interval boundaries before replace
+ if ((spanStart != replaceStart ||
+ ((spanFlags & SPAN_START_AT_START) != SPAN_START_AT_START)) &&
+ (spanStart != newReplaceEnd ||
+ ((spanFlags & SPAN_START_AT_END) != SPAN_START_AT_END))) {
+ // TODO previousSpanStart is incorrect, but we would need to save all the
+ // previous spans' positions before replace to provide it
+ spanChanged = true;
+ }
+ }
+ int previousSpanEnd = spanEnd;
+ if (spanEnd > newReplaceEnd) {
+ if (nbNewChars != 0) {
+ previousSpanEnd -= nbNewChars;
+ spanChanged = true;
+ }
+ } else if (spanEnd >= replaceStart) {
+ // No change if span start was already at replace interval boundaries before replace
+ if ((spanEnd != replaceStart ||
+ ((spanFlags & SPAN_END_AT_START) != SPAN_END_AT_START)) &&
+ (spanEnd != newReplaceEnd ||
+ ((spanFlags & SPAN_END_AT_END) != SPAN_END_AT_END))) {
+ // TODO same as above for previousSpanEnd
+ spanChanged = true;
+ }
+ }
+
+ if (spanChanged) {
+ sendSpanChanged(mSpans[i], previousSpanStart, previousSpanEnd, spanStart, spanEnd);
+ }
+ mSpanFlags[i] &= ~SPAN_START_END_MASK;
+ }
+
+ // The spans starting at mIntermediateSpanCount were added from the replacement text
+ for (int i = mSpanCountBeforeAdd; i < mSpanCount; i++) {
+ int spanStart = mSpanStarts[i];
+ int spanEnd = mSpanEnds[i];
+ if (spanStart > mGapStart) spanStart -= mGapLength;
+ if (spanEnd > mGapStart) spanEnd -= mGapLength;
+ sendSpanAdded(mSpans[i], spanStart, spanEnd);
+ }
+ }
+
/**
* Mark the specified range of text with the specified object.
* The flags determine how the span will behave when text is
@@ -788,13 +851,12 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
if (end <= mGapStart) {
System.arraycopy(mText, start, dest, destoff, end - start);
} else if (start >= mGapStart) {
- System.arraycopy(mText, start + mGapLength,
- dest, destoff, end - start);
+ System.arraycopy(mText, start + mGapLength, dest, destoff, end - start);
} else {
System.arraycopy(mText, start, dest, destoff, mGapStart - start);
System.arraycopy(mText, mGapStart + mGapLength,
- dest, destoff + (mGapStart - start),
- end - mGapStart);
+ dest, destoff + (mGapStart - start),
+ end - mGapStart);
}
}
@@ -863,12 +925,14 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
}
}
- private void sendSpanChanged(Object what, int s, int e, int st, int en) {
- SpanWatcher[] recip = getSpans(Math.min(s, st), Math.max(e, en), SpanWatcher.class);
- int n = recip.length;
-
+ private void sendSpanChanged(Object what, int oldStart, int oldEnd, int start, int end) {
+ // The bounds of a possible SpanWatcher are guaranteed to be set before this method is
+ // called, so that the order of the span does not affect this broadcast.
+ SpanWatcher[] spanWatchers = getSpans(Math.min(oldStart, start),
+ Math.min(Math.max(oldEnd, end), length()), SpanWatcher.class);
+ int n = spanWatchers.length;
for (int i = 0; i < n; i++) {
- recip[i].onSpanChanged(this, what, s, e, st, en);
+ spanWatchers[i].onSpanChanged(this, what, oldStart, oldEnd, start, end);
}
}
@@ -879,26 +943,23 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
private void checkRange(final String operation, int start, int end) {
if (end < start) {
throw new IndexOutOfBoundsException(operation + " " +
- region(start, end) +
- " has end before start");
+ region(start, end) + " has end before start");
}
int len = length();
if (start > len || end > len) {
throw new IndexOutOfBoundsException(operation + " " +
- region(start, end) +
- " ends beyond length " + len);
+ region(start, end) + " ends beyond length " + len);
}
if (start < 0 || end < 0) {
throw new IndexOutOfBoundsException(operation + " " +
- region(start, end) +
- " starts before 0");
+ region(start, end) + " starts before 0");
}
}
-/*
+ /*
private boolean isprint(char c) { // XXX
if (c >= ' ' && c <= '~')
return true;
@@ -977,7 +1038,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
System.out.print("\n");
}
-*/
+ */
/**
* Don't call this yourself -- exists for Canvas to use internally.
@@ -1023,7 +1084,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
}
}
- /**
+ /**
* Don't call this yourself -- exists for Paint to use internally.
* {@hide}
*/
@@ -1059,8 +1120,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
if (end <= mGapStart) {
ret = p.getTextWidths(mText, start, end - start, widths);
} else if (start >= mGapStart) {
- ret = p.getTextWidths(mText, start + mGapLength, end - start,
- widths);
+ ret = p.getTextWidths(mText, start + mGapLength, end - start, widths);
} else {
char[] buf = TextUtils.obtain(end - start);
@@ -1205,6 +1265,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
private int[] mSpanEnds;
private int[] mSpanFlags;
private int mSpanCount;
+ private int mSpanCountBeforeAdd;
// TODO These value are tightly related to the public SPAN_MARK/POINT values in {@link Spanned}
private static final int MARK = 1;
@@ -1214,4 +1275,11 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
private static final int START_MASK = 0xF0;
private static final int END_MASK = 0x0F;
private static final int START_SHIFT = 4;
+
+ // These bits are not (currently) used by SPANNED flags
+ private static final int SPAN_START_AT_START = 0x1000;
+ private static final int SPAN_START_AT_END = 0x2000;
+ private static final int SPAN_END_AT_START = 0x4000;
+ private static final int SPAN_END_AT_END = 0x8000;
+ private static final int SPAN_START_END_MASK = 0xF000;
}
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
new file mode 100644
index 0000000..ab21b32
--- /dev/null
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -0,0 +1,900 @@
+/*
+ * 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 static android.view.accessibility.AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Pool;
+import android.util.Poolable;
+import android.util.PoolableManager;
+import android.util.Pools;
+import android.util.SparseLongArray;
+import android.view.ViewGroup.ChildListForAccessibility;
+import android.view.accessibility.AccessibilityInteractionClient;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
+import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class for managing accessibility interactions initiated from the system
+ * and targeting the view hierarchy. A *ClientThread method is to be
+ * called from the interaction connection ViewAncestor gives the system to
+ * talk to it and a corresponding *UiThread method that is executed on the
+ * UI thread.
+ */
+final class AccessibilityInteractionController {
+ private static final int POOL_SIZE = 5;
+
+ private ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
+ new ArrayList<AccessibilityNodeInfo>();
+
+ private final Handler mHandler = new PrivateHandler();
+
+ private final ViewRootImpl mViewRootImpl;
+
+ private final AccessibilityNodePrefetcher mPrefetcher;
+
+ public AccessibilityInteractionController(ViewRootImpl viewRootImpl) {
+ mViewRootImpl = viewRootImpl;
+ mPrefetcher = new AccessibilityNodePrefetcher();
+ }
+
+ // Reusable poolable arguments for interacting with the view hierarchy
+ // to fit more arguments than Message and to avoid sharing objects between
+ // two messages since several threads can send messages concurrently.
+ private final Pool<SomeArgs> mPool = Pools.synchronizedPool(Pools.finitePool(
+ new PoolableManager<SomeArgs>() {
+ public SomeArgs newInstance() {
+ return new SomeArgs();
+ }
+
+ public void onAcquired(SomeArgs info) {
+ /* do nothing */
+ }
+
+ public void onReleased(SomeArgs info) {
+ info.clear();
+ }
+ }, POOL_SIZE)
+ );
+
+ private class SomeArgs implements Poolable<SomeArgs> {
+ private SomeArgs mNext;
+ private boolean mIsPooled;
+
+ public Object arg1;
+ public Object arg2;
+ public int argi1;
+ public int argi2;
+ public int argi3;
+
+ public SomeArgs getNextPoolable() {
+ return mNext;
+ }
+
+ public boolean isPooled() {
+ return mIsPooled;
+ }
+
+ public void setNextPoolable(SomeArgs args) {
+ mNext = args;
+ }
+
+ public void setPooled(boolean isPooled) {
+ mIsPooled = isPooled;
+ }
+
+ private void clear() {
+ arg1 = null;
+ arg2 = null;
+ argi1 = 0;
+ argi2 = 0;
+ argi3 = 0;
+ }
+ }
+
+ public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
+ long accessibilityNodeId, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+ long interrogatingTid) {
+ Message message = mHandler.obtainMessage();
+ message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID;
+ message.arg1 = flags;
+ SomeArgs args = mPool.acquire();
+ args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+ args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
+ args.argi3 = interactionId;
+ args.arg1 = callback;
+ message.obj = args;
+ // If the interrogation is performed by the same thread as the main UI
+ // thread in this process, set the message as a static reference so
+ // after this call completes the same thread but in the interrogating
+ // client can handle the message to generate the result.
+ if (interrogatingPid == Process.myPid()
+ && interrogatingTid == Looper.getMainLooper().getThread().getId()) {
+ AccessibilityInteractionClient.getInstanceForThread(
+ interrogatingTid).setSameThreadMessage(message);
+ } else {
+ mHandler.sendMessage(message);
+ }
+ }
+
+ private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
+ final int flags = message.arg1;
+ SomeArgs args = (SomeArgs) message.obj;
+ final int accessibilityViewId = args.argi1;
+ final int virtualDescendantId = args.argi2;
+ final int interactionId = args.argi3;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) args.arg1;
+ mPool.release(args);
+ List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
+ infos.clear();
+ try {
+ if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
+ return;
+ }
+ mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
+ (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ View root = null;
+ if (accessibilityViewId == AccessibilityNodeInfo.UNDEFINED) {
+ root = mViewRootImpl.mView;
+ } else {
+ root = findViewByAccessibilityId(accessibilityViewId);
+ }
+ if (root != null && isDisplayedOnScreen(root)) {
+ mPrefetcher.prefetchAccessibilityNodeInfos(root, virtualDescendantId, flags, infos);
+ }
+ } finally {
+ try {
+ mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
+ infos.clear();
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ }
+ }
+ }
+
+ public void findAccessibilityNodeInfoByViewIdClientThread(long accessibilityNodeId,
+ int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
+ int flags, int interrogatingPid, long interrogatingTid) {
+ Message message = mHandler.obtainMessage();
+ message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID;
+ message.arg1 = flags;
+ message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+ SomeArgs args = mPool.acquire();
+ args.argi1 = viewId;
+ args.argi2 = interactionId;
+ args.arg1 = callback;
+ message.obj = args;
+ // If the interrogation is performed by the same thread as the main UI
+ // thread in this process, set the message as a static reference so
+ // after this call completes the same thread but in the interrogating
+ // client can handle the message to generate the result.
+ if (interrogatingPid == Process.myPid()
+ && interrogatingTid == Looper.getMainLooper().getThread().getId()) {
+ AccessibilityInteractionClient.getInstanceForThread(
+ interrogatingTid).setSameThreadMessage(message);
+ } else {
+ mHandler.sendMessage(message);
+ }
+ }
+
+ private void findAccessibilityNodeInfoByViewIdUiThread(Message message) {
+ final int flags = message.arg1;
+ final int accessibilityViewId = message.arg2;
+ SomeArgs args = (SomeArgs) message.obj;
+ final int viewId = args.argi1;
+ final int interactionId = args.argi2;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) args.arg1;
+ mPool.release(args);
+ AccessibilityNodeInfo info = null;
+ try {
+ if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
+ return;
+ }
+ mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
+ (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ View root = null;
+ if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
+ root = findViewByAccessibilityId(accessibilityViewId);
+ } else {
+ root = mViewRootImpl.mView;
+ }
+ if (root != null) {
+ View target = root.findViewById(viewId);
+ if (target != null && isDisplayedOnScreen(target)) {
+ info = target.createAccessibilityNodeInfo();
+ }
+ }
+ } finally {
+ try {
+ mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ callback.setFindAccessibilityNodeInfoResult(info, interactionId);
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ }
+ }
+ }
+
+ public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId,
+ String text, int interactionId, IAccessibilityInteractionConnectionCallback callback,
+ int flags, int interrogatingPid, long interrogatingTid) {
+ Message message = mHandler.obtainMessage();
+ message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT;
+ message.arg1 = flags;
+ SomeArgs args = mPool.acquire();
+ args.arg1 = text;
+ args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+ args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
+ args.argi3 = interactionId;
+ args.arg2 = callback;
+ message.obj = args;
+ // If the interrogation is performed by the same thread as the main UI
+ // thread in this process, set the message as a static reference so
+ // after this call completes the same thread but in the interrogating
+ // client can handle the message to generate the result.
+ if (interrogatingPid == Process.myPid()
+ && interrogatingTid == Looper.getMainLooper().getThread().getId()) {
+ AccessibilityInteractionClient.getInstanceForThread(
+ interrogatingTid).setSameThreadMessage(message);
+ } else {
+ mHandler.sendMessage(message);
+ }
+ }
+
+ private void findAccessibilityNodeInfosByTextUiThread(Message message) {
+ final int flags = message.arg1;
+ SomeArgs args = (SomeArgs) message.obj;
+ final String text = (String) args.arg1;
+ final int accessibilityViewId = args.argi1;
+ final int virtualDescendantId = args.argi2;
+ final int interactionId = args.argi3;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) args.arg2;
+ mPool.release(args);
+ List<AccessibilityNodeInfo> infos = null;
+ try {
+ if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
+ return;
+ }
+ mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
+ (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ View root = null;
+ if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
+ root = findViewByAccessibilityId(accessibilityViewId);
+ } else {
+ root = mViewRootImpl.mView;
+ }
+ if (root != null && isDisplayedOnScreen(root)) {
+ AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
+ if (provider != null) {
+ infos = provider.findAccessibilityNodeInfosByText(text,
+ virtualDescendantId);
+ } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED) {
+ ArrayList<View> foundViews = mViewRootImpl.mAttachInfo.mTempArrayList;
+ foundViews.clear();
+ root.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT
+ | View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION
+ | View.FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS);
+ if (!foundViews.isEmpty()) {
+ infos = mTempAccessibilityNodeInfoList;
+ infos.clear();
+ final int viewCount = foundViews.size();
+ for (int i = 0; i < viewCount; i++) {
+ View foundView = foundViews.get(i);
+ if (isDisplayedOnScreen(foundView)) {
+ provider = foundView.getAccessibilityNodeProvider();
+ if (provider != null) {
+ List<AccessibilityNodeInfo> infosFromProvider =
+ provider.findAccessibilityNodeInfosByText(text,
+ virtualDescendantId);
+ if (infosFromProvider != null) {
+ infos.addAll(infosFromProvider);
+ }
+ } else {
+ infos.add(foundView.createAccessibilityNodeInfo());
+ }
+ }
+ }
+ }
+ }
+ }
+ } finally {
+ try {
+ mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ }
+ }
+ }
+
+ public void findFocusClientThread(long accessibilityNodeId, int interactionId, int focusType,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
+ long interrogatingTid) {
+ Message message = mHandler.obtainMessage();
+ message.what = PrivateHandler.MSG_FIND_FOCUS;
+ message.arg1 = flags;
+ message.arg2 = focusType;
+ SomeArgs args = mPool.acquire();
+ args.argi1 = interactionId;
+ args.argi2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+ args.argi3 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
+ args.arg1 = callback;
+ message.obj = args;
+ // If the interrogation is performed by the same thread as the main UI
+ // thread in this process, set the message as a static reference so
+ // after this call completes the same thread but in the interrogating
+ // client can handle the message to generate the result.
+ if (interogatingPid == Process.myPid()
+ && interrogatingTid == Looper.getMainLooper().getThread().getId()) {
+ AccessibilityInteractionClient.getInstanceForThread(
+ interrogatingTid).setSameThreadMessage(message);
+ } else {
+ mHandler.sendMessage(message);
+ }
+ }
+
+ private void findFocusUiThread(Message message) {
+ final int flags = message.arg1;
+ final int focusType = message.arg2;
+ SomeArgs args = (SomeArgs) message.obj;
+ final int interactionId = args.argi1;
+ final int accessibilityViewId = args.argi2;
+ final int virtualDescendantId = args.argi3;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) args.arg1;
+ mPool.release(args);
+ AccessibilityNodeInfo focused = null;
+ try {
+ if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
+ return;
+ }
+ mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
+ (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ View root = null;
+ if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
+ root = findViewByAccessibilityId(accessibilityViewId);
+ } else {
+ root = mViewRootImpl.mView;
+ }
+ if (root != null && isDisplayedOnScreen(root)) {
+ switch (focusType) {
+ case AccessibilityNodeInfo.FOCUS_ACCESSIBILITY: {
+ View host = mViewRootImpl.mAccessibilityFocusedHost;
+ // If there is no accessibility focus host or it is not a descendant
+ // of the root from which to start the search, then the search failed.
+ if (host == null || !ViewRootImpl.isViewDescendantOf(host, root)) {
+ break;
+ }
+ // If the host has a provider ask this provider to search for the
+ // focus instead fetching all provider nodes to do the search here.
+ AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
+ if (provider != null) {
+ focused = provider.findAccessibilitiyFocus(virtualDescendantId);
+ } else if (virtualDescendantId == View.NO_ID) {
+ focused = host.createAccessibilityNodeInfo();
+ }
+ } break;
+ case AccessibilityNodeInfo.FOCUS_INPUT: {
+ // Input focus cannot go to virtual views.
+ View target = root.findFocus();
+ if (target != null && isDisplayedOnScreen(target)) {
+ focused = target.createAccessibilityNodeInfo();
+ }
+ } break;
+ default:
+ throw new IllegalArgumentException("Unknown focus type: " + focusType);
+ }
+ }
+ } finally {
+ try {
+ mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ callback.setFindAccessibilityNodeInfoResult(focused, interactionId);
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ }
+ }
+ }
+
+ public void focusSearchClientThread(long accessibilityNodeId, int interactionId, int direction,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
+ long interrogatingTid) {
+ Message message = mHandler.obtainMessage();
+ message.what = PrivateHandler.MSG_FOCUS_SEARCH;
+ message.arg1 = flags;
+ message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+ SomeArgs args = mPool.acquire();
+ args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
+ args.argi2 = direction;
+ args.argi3 = interactionId;
+ args.arg1 = callback;
+ message.obj = args;
+ // If the interrogation is performed by the same thread as the main UI
+ // thread in this process, set the message as a static reference so
+ // after this call completes the same thread but in the interrogating
+ // client can handle the message to generate the result.
+ if (interogatingPid == Process.myPid()
+ && interrogatingTid == Looper.getMainLooper().getThread().getId()) {
+ AccessibilityInteractionClient.getInstanceForThread(
+ interrogatingTid).setSameThreadMessage(message);
+ } else {
+ mHandler.sendMessage(message);
+ }
+ }
+
+ private void focusSearchUiThread(Message message) {
+ final int flags = message.arg1;
+ final int accessibilityViewId = message.arg2;
+ SomeArgs args = (SomeArgs) message.obj;
+ final int virtualDescendantId = args.argi1;
+ final int direction = args.argi2;
+ final int interactionId = args.argi3;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) args.arg1;
+ mPool.release(args);
+ AccessibilityNodeInfo next = null;
+ try {
+ if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
+ return;
+ }
+ mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
+ (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ View root = null;
+ if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
+ root = findViewByAccessibilityId(accessibilityViewId);
+ } else {
+ root = mViewRootImpl.mView;
+ }
+ if (root != null && isDisplayedOnScreen(root)) {
+ if ((direction & View.FOCUS_ACCESSIBILITY) == View.FOCUS_ACCESSIBILITY) {
+ AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
+ if (provider != null) {
+ next = provider.accessibilityFocusSearch(direction,
+ virtualDescendantId);
+ } else if (virtualDescendantId == View.NO_ID) {
+ View nextView = root.focusSearch(direction);
+ if (nextView != null) {
+ // If the focus search reached a node with a provider
+ // we delegate to the provider to find the next one.
+ provider = nextView.getAccessibilityNodeProvider();
+ if (provider != null) {
+ next = provider.accessibilityFocusSearch(direction,
+ virtualDescendantId);
+ } else {
+ next = nextView.createAccessibilityNodeInfo();
+ }
+ }
+ }
+ } else {
+ View nextView = root.focusSearch(direction);
+ if (nextView != null) {
+ next = nextView.createAccessibilityNodeInfo();
+ }
+ }
+ }
+ } finally {
+ try {
+ mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ callback.setFindAccessibilityNodeInfoResult(next, interactionId);
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ }
+ }
+ }
+
+ public void performAccessibilityActionClientThread(long accessibilityNodeId, int action,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interogatingPid, long interrogatingTid) {
+ Message message = mHandler.obtainMessage();
+ message.what = PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION;
+ message.arg1 = flags;
+ message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+ SomeArgs args = mPool.acquire();
+ args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
+ args.argi2 = action;
+ args.argi3 = interactionId;
+ args.arg1 = callback;
+ message.obj = args;
+ // If the interrogation is performed by the same thread as the main UI
+ // thread in this process, set the message as a static reference so
+ // after this call completes the same thread but in the interrogating
+ // client can handle the message to generate the result.
+ if (interogatingPid == Process.myPid()
+ && interrogatingTid == Looper.getMainLooper().getThread().getId()) {
+ AccessibilityInteractionClient.getInstanceForThread(
+ interrogatingTid).setSameThreadMessage(message);
+ } else {
+ mHandler.sendMessage(message);
+ }
+ }
+
+ private void perfromAccessibilityActionUiThread(Message message) {
+ final int flags = message.arg1;
+ final int accessibilityViewId = message.arg2;
+ SomeArgs args = (SomeArgs) message.obj;
+ final int virtualDescendantId = args.argi1;
+ final int action = args.argi2;
+ final int interactionId = args.argi3;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) args.arg1;
+ mPool.release(args);
+ boolean succeeded = false;
+ try {
+ if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
+ return;
+ }
+ mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
+ (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ View target = null;
+ if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
+ target = findViewByAccessibilityId(accessibilityViewId);
+ } else {
+ target = mViewRootImpl.mView;
+ }
+ if (target != null && isDisplayedOnScreen(target)) {
+ AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
+ if (provider != null) {
+ succeeded = provider.performAccessibilityAction(action, virtualDescendantId);
+ } else if (virtualDescendantId == View.NO_ID) {
+ succeeded = target.performAccessibilityAction(action);
+ }
+ }
+ } finally {
+ try {
+ mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ callback.setPerformAccessibilityActionResult(succeeded, interactionId);
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ }
+ }
+ }
+
+ private View findViewByAccessibilityId(int accessibilityId) {
+ View root = mViewRootImpl.mView;
+ if (root == null) {
+ return null;
+ }
+ View foundView = root.findViewByAccessibilityId(accessibilityId);
+ if (foundView != null && !isDisplayedOnScreen(foundView)) {
+ return null;
+ }
+ return foundView;
+ }
+
+ /**
+ * Computes whether a view is visible on the screen.
+ *
+ * @param view The view to check.
+ * @return Whether the view is visible on the screen.
+ */
+ private boolean isDisplayedOnScreen(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()
+ && view.getGlobalVisibleRect(mViewRootImpl.mTempRect));
+ }
+
+ /**
+ * This class encapsulates a prefetching strategy for the accessibility APIs for
+ * querying window content. It is responsible to prefetch a batch of
+ * AccessibilityNodeInfos in addition to the one for a requested node.
+ */
+ private class AccessibilityNodePrefetcher {
+
+ private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50;
+
+ public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int prefetchFlags,
+ List<AccessibilityNodeInfo> outInfos) {
+ AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
+ if (provider == null) {
+ AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
+ if (root != null) {
+ outInfos.add(root);
+ if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+ prefetchPredecessorsOfRealNode(view, outInfos);
+ }
+ if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
+ prefetchSiblingsOfRealNode(view, outInfos);
+ }
+ if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+ prefetchDescendantsOfRealNode(view, outInfos);
+ }
+ }
+ } else {
+ AccessibilityNodeInfo root = provider.createAccessibilityNodeInfo(virtualViewId);
+ if (root != null) {
+ outInfos.add(root);
+ if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+ prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
+ }
+ if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
+ prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
+ }
+ if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+ prefetchDescendantsOfVirtualNode(root, provider, outInfos);
+ }
+ }
+ }
+ }
+
+ private void prefetchPredecessorsOfRealNode(View view,
+ List<AccessibilityNodeInfo> outInfos) {
+ ViewParent parent = view.getParentForAccessibility();
+ while (parent instanceof View
+ && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ View parentView = (View) parent;
+ final long parentNodeId = AccessibilityNodeInfo.makeNodeId(
+ parentView.getAccessibilityViewId(), AccessibilityNodeInfo.UNDEFINED);
+ AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
+ if (info != null) {
+ outInfos.add(info);
+ }
+ parent = parent.getParentForAccessibility();
+ }
+ }
+
+ private void prefetchSiblingsOfRealNode(View current,
+ List<AccessibilityNodeInfo> outInfos) {
+ ViewParent parent = current.getParentForAccessibility();
+ if (parent instanceof ViewGroup) {
+ ViewGroup parentGroup = (ViewGroup) parent;
+ ChildListForAccessibility children = ChildListForAccessibility.obtain(parentGroup,
+ false);
+ final int childCount = children.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ children.recycle();
+ return;
+ }
+ View child = children.getChildAt(i);
+ if (child.getAccessibilityViewId() != current.getAccessibilityViewId()
+ && isDisplayedOnScreen(child)) {
+ AccessibilityNodeInfo info = null;
+ AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
+ if (provider == null) {
+ info = child.createAccessibilityNodeInfo();
+ } else {
+ info = provider.createAccessibilityNodeInfo(
+ AccessibilityNodeInfo.UNDEFINED);
+ }
+ if (info != null) {
+ outInfos.add(info);
+ }
+ }
+ }
+ children.recycle();
+ }
+ }
+
+ private void prefetchDescendantsOfRealNode(View root,
+ List<AccessibilityNodeInfo> outInfos) {
+ if (!(root instanceof ViewGroup)) {
+ return;
+ }
+ ViewGroup rootGroup = (ViewGroup) root;
+ HashMap<View, AccessibilityNodeInfo> addedChildren =
+ new HashMap<View, AccessibilityNodeInfo>();
+ ChildListForAccessibility children = ChildListForAccessibility.obtain(rootGroup, false);
+ final int childCount = children.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ children.recycle();
+ return;
+ }
+ View child = children.getChildAt(i);
+ if ( isDisplayedOnScreen(child)) {
+ AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
+ if (provider == null) {
+ AccessibilityNodeInfo info = child.createAccessibilityNodeInfo();
+ if (info != null) {
+ outInfos.add(info);
+ addedChildren.put(child, null);
+ }
+ } else {
+ AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo(
+ AccessibilityNodeInfo.UNDEFINED);
+ if (info != null) {
+ outInfos.add(info);
+ addedChildren.put(child, info);
+ }
+ }
+ }
+ }
+ children.recycle();
+ if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
+ View addedChild = entry.getKey();
+ AccessibilityNodeInfo virtualRoot = entry.getValue();
+ if (virtualRoot == null) {
+ prefetchDescendantsOfRealNode(addedChild, outInfos);
+ } else {
+ AccessibilityNodeProvider provider =
+ addedChild.getAccessibilityNodeProvider();
+ prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos);
+ }
+ }
+ }
+ }
+
+ private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root,
+ View providerHost, AccessibilityNodeProvider provider,
+ List<AccessibilityNodeInfo> outInfos) {
+ long parentNodeId = root.getParentNodeId();
+ int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
+ while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
+ if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ return;
+ }
+ final int virtualDescendantId =
+ AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
+ if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED
+ || accessibilityViewId == providerHost.getAccessibilityViewId()) {
+ AccessibilityNodeInfo parent = provider.createAccessibilityNodeInfo(
+ virtualDescendantId);
+ if (parent != null) {
+ outInfos.add(parent);
+ }
+ parentNodeId = parent.getParentNodeId();
+ accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
+ parentNodeId);
+ } else {
+ prefetchPredecessorsOfRealNode(providerHost, outInfos);
+ return;
+ }
+ }
+ }
+
+ private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost,
+ AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
+ final long parentNodeId = current.getParentNodeId();
+ final int parentAccessibilityViewId =
+ AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
+ final int parentVirtualDescendantId =
+ AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
+ if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED
+ || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) {
+ AccessibilityNodeInfo parent =
+ provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
+ if (parent != null) {
+ SparseLongArray childNodeIds = parent.getChildNodeIds();
+ final int childCount = childNodeIds.size();
+ for (int i = 0; i < childCount; i++) {
+ if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ return;
+ }
+ final long childNodeId = childNodeIds.get(i);
+ if (childNodeId != current.getSourceNodeId()) {
+ final int childVirtualDescendantId =
+ AccessibilityNodeInfo.getVirtualDescendantId(childNodeId);
+ AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
+ childVirtualDescendantId);
+ if (child != null) {
+ outInfos.add(child);
+ }
+ }
+ }
+ }
+ } else {
+ prefetchSiblingsOfRealNode(providerHost, outInfos);
+ }
+ }
+
+ private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root,
+ AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
+ SparseLongArray childNodeIds = root.getChildNodeIds();
+ final int initialOutInfosSize = outInfos.size();
+ final int childCount = childNodeIds.size();
+ for (int i = 0; i < childCount; i++) {
+ if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ return;
+ }
+ final long childNodeId = childNodeIds.get(i);
+ AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
+ AccessibilityNodeInfo.getVirtualDescendantId(childNodeId));
+ if (child != null) {
+ outInfos.add(child);
+ }
+ }
+ if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ final int addedChildCount = outInfos.size() - initialOutInfosSize;
+ for (int i = 0; i < addedChildCount; i++) {
+ AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
+ prefetchDescendantsOfVirtualNode(child, provider, outInfos);
+ }
+ }
+ }
+ }
+
+ private class PrivateHandler extends Handler {
+ private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
+ private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
+ private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 3;
+ private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 4;
+ private final static int MSG_FIND_FOCUS = 5;
+ private final static int MSG_FOCUS_SEARCH = 6;
+
+ public PrivateHandler() {
+ super(Looper.getMainLooper());
+ }
+
+ @Override
+ public String getMessageName(Message message) {
+ final int type = message.what;
+ switch (type) {
+ case MSG_PERFORM_ACCESSIBILITY_ACTION:
+ return "MSG_PERFORM_ACCESSIBILITY_ACTION";
+ case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID:
+ return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID";
+ case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID:
+ return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID";
+ case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT:
+ return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT";
+ case MSG_FIND_FOCUS:
+ return "MSG_FIND_FOCUS";
+ case MSG_FOCUS_SEARCH:
+ return "MSG_FOCUS_SEARCH";
+ default:
+ throw new IllegalArgumentException("Unknown message type: " + type);
+ }
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ final int type = message.what;
+ switch (type) {
+ case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
+ findAccessibilityNodeInfoByAccessibilityIdUiThread(message);
+ } break;
+ case MSG_PERFORM_ACCESSIBILITY_ACTION: {
+ perfromAccessibilityActionUiThread(message);
+ } break;
+ case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID: {
+ findAccessibilityNodeInfoByViewIdUiThread(message);
+ } break;
+ case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: {
+ findAccessibilityNodeInfosByTextUiThread(message);
+ } break;
+ case MSG_FIND_FOCUS: {
+ findFocusUiThread(message);
+ } break;
+ case MSG_FOCUS_SEARCH: {
+ focusSearchUiThread(message);
+ } break;
+ default:
+ throw new IllegalArgumentException("Unknown message type: " + type);
+ }
+ }
+ }
+}
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
index 3529b8e..8a01c15 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -17,10 +17,12 @@
package android.view;
import android.graphics.Rect;
+import android.view.ViewGroup.ChildListForAccessibility;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
+import java.util.Stack;
/**
* The algorithm used for finding the next focusable view in a given direction
@@ -30,7 +32,7 @@ public class FocusFinder {
private static ThreadLocal<FocusFinder> tlFocusFinder =
new ThreadLocal<FocusFinder>() {
-
+ @Override
protected FocusFinder initialValue() {
return new FocusFinder();
}
@@ -48,6 +50,10 @@ public class FocusFinder {
Rect mBestCandidateRect = new Rect();
SequentialFocusComparator mSequentialFocusComparator = new SequentialFocusComparator();
+ private final ArrayList<View> mTempList = new ArrayList<View>();
+
+ private Stack<View> mTempStack;
+
// enforce thread local access
private FocusFinder() {}
@@ -60,7 +66,30 @@ public class FocusFinder {
* @return The next focusable view, or null if none exists.
*/
public final View findNextFocus(ViewGroup root, View focused, int direction) {
+ return findNextFocus(root, focused, mFocusedRect, direction);
+ }
+
+ /**
+ * Find the next view to take focus in root's descendants, searching from
+ * a particular rectangle in root's coordinates.
+ * @param root Contains focusedRect. Cannot be null.
+ * @param focusedRect The starting point of the search.
+ * @param direction Direction to look.
+ * @return The next focusable view, or null if none exists.
+ */
+ public View findNextFocusFromRect(ViewGroup root, Rect focusedRect, int direction) {
+ return findNextFocus(root, null, focusedRect, direction);
+ }
+
+ private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
+ if ((direction & View.FOCUS_ACCESSIBILITY) != View.FOCUS_ACCESSIBILITY) {
+ return findNextInputFocus(root, focused, focusedRect, direction);
+ } else {
+ return findNextAccessibilityFocus(root, focused, direction);
+ }
+ }
+ private View findNextInputFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
if (focused != null) {
// check for user specified next focus
View userSetNextFocus = focused.findUserSetNextFocus(root, direction);
@@ -79,90 +108,154 @@ public class FocusFinder {
switch (direction) {
case View.FOCUS_RIGHT:
case View.FOCUS_DOWN:
- setFocusBottomRight(root);
+ setFocusTopLeft(root);
break;
case View.FOCUS_FORWARD:
if (root.isLayoutRtl()) {
- setFocusTopLeft(root);
- } else {
setFocusBottomRight(root);
+ } else {
+ setFocusTopLeft(root);
}
break;
case View.FOCUS_LEFT:
case View.FOCUS_UP:
- setFocusTopLeft(root);
+ setFocusBottomRight(root);
break;
case View.FOCUS_BACKWARD:
if (root.isLayoutRtl()) {
- setFocusBottomRight(root);
- } else {
setFocusTopLeft(root);
+ } else {
+ setFocusBottomRight(root);
break;
}
}
}
- return findNextFocus(root, focused, mFocusedRect, direction);
- }
- private void setFocusTopLeft(ViewGroup root) {
- final int rootBottom = root.getScrollY() + root.getHeight();
- final int rootRight = root.getScrollX() + root.getWidth();
- mFocusedRect.set(rootRight, rootBottom,
- rootRight, rootBottom);
- }
+ ArrayList<View> focusables = mTempList;
+ focusables.clear();
+ root.addFocusables(focusables, direction);
+ if (focusables.isEmpty()) {
+ // The focus cannot change.
+ return null;
+ }
- private void setFocusBottomRight(ViewGroup root) {
- final int rootTop = root.getScrollY();
- final int rootLeft = root.getScrollX();
- mFocusedRect.set(rootLeft, rootTop, rootLeft, rootTop);
+ try {
+ switch (direction) {
+ case View.FOCUS_FORWARD:
+ case View.FOCUS_BACKWARD:
+ return findNextInputFocusInRelativeDirection(focusables, root, focused,
+ focusedRect, direction);
+ case View.FOCUS_UP:
+ case View.FOCUS_DOWN:
+ case View.FOCUS_LEFT:
+ case View.FOCUS_RIGHT:
+ return findNextInputFocusInAbsoluteDirection(focusables, root, focused,
+ focusedRect, direction);
+ default:
+ throw new IllegalArgumentException("Unknown direction: " + direction);
+ }
+ } finally {
+ focusables.clear();
+ }
}
/**
- * Find the next view to take focus in root's descendants, searching from
- * a particular rectangle in root's coordinates.
- * @param root Contains focusedRect. Cannot be null.
- * @param focusedRect The starting point of the search.
+ * Find the next view to take accessibility focus in root's descendants,
+ * starting from the view that currently is accessibility focused.
+ *
+ * @param root The root which also contains the focused view.
+ * @param focused The current accessibility focused view.
* @param direction Direction to look.
* @return The next focusable view, or null if none exists.
*/
- public View findNextFocusFromRect(ViewGroup root, Rect focusedRect, int direction) {
- return findNextFocus(root, null, focusedRect, direction);
- }
-
- private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
- ArrayList<View> focusables = root.getFocusables(direction);
- if (focusables.isEmpty()) {
- // The focus cannot change.
- return null;
+ private View findNextAccessibilityFocus(ViewGroup root, View focused, int direction) {
+ switch (direction) {
+ case View.ACCESSIBILITY_FOCUS_IN:
+ case View.ACCESSIBILITY_FOCUS_OUT:
+ case View.ACCESSIBILITY_FOCUS_FORWARD:
+ case View.ACCESSIBILITY_FOCUS_BACKWARD: {
+ return findNextHierarchicalAcessibilityFocus(root, focused, direction);
+ }
+ case View.ACCESSIBILITY_FOCUS_LEFT:
+ case View.ACCESSIBILITY_FOCUS_RIGHT:
+ case View.ACCESSIBILITY_FOCUS_UP:
+ case View.ACCESSIBILITY_FOCUS_DOWN: {
+ return findNextDirectionalAccessibilityFocus(root, focused, direction);
+ }
+ default:
+ throw new IllegalArgumentException("Unknown direction: " + direction);
}
+ }
- if (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD) {
- if (focused != null && !focusables.contains(focused)) {
- // Add the currently focused view to the list to have it sorted
- // along with the other views.
- focusables.add(focused);
+ private View findNextHierarchicalAcessibilityFocus(ViewGroup root, View focused,
+ int direction) {
+ View current = (focused != null) ? focused : root;
+ switch (direction) {
+ case View.ACCESSIBILITY_FOCUS_IN: {
+ return findNextAccessibilityFocusIn(current);
}
-
- try {
- // Note: This sort is stable.
- mSequentialFocusComparator.setRoot(root);
- Collections.sort(focusables, mSequentialFocusComparator);
- } finally {
- mSequentialFocusComparator.recycle();
+ case View.ACCESSIBILITY_FOCUS_OUT: {
+ return findNextAccessibilityFocusOut(current);
+ }
+ case View.ACCESSIBILITY_FOCUS_FORWARD: {
+ return findNextAccessibilityFocusForward(current);
+ }
+ case View.ACCESSIBILITY_FOCUS_BACKWARD: {
+ return findNextAccessibilityFocusBackward(current);
}
+ }
+ return null;
+ }
- final int count = focusables.size();
- switch (direction) {
- case View.FOCUS_FORWARD:
- return getForwardFocusable(root, focused, focusables, count);
+ private View findNextDirectionalAccessibilityFocus(ViewGroup root, View focused,
+ int direction) {
+ ArrayList<View> focusables = mTempList;
+ focusables.clear();
+ root.addFocusables(focusables, direction, View.FOCUSABLES_ACCESSIBILITY);
+ Rect focusedRect = getFocusedRect(root, focused, direction);
+ final int inputFocusDirection = getCorrespondingInputFocusDirection(direction);
+ View next = findNextInputFocusInAbsoluteDirection(focusables, root,
+ focused, focusedRect, inputFocusDirection);
+ focusables.clear();
+ return next;
+ }
- case View.FOCUS_BACKWARD:
- return getBackwardFocusable(root, focused, focusables, count);
- }
- return null;
+ private View findNextInputFocusInRelativeDirection(ArrayList<View> focusables, ViewGroup root,
+ View focused, Rect focusedRect, int direction) {
+ try {
+ // Note: This sort is stable.
+ mSequentialFocusComparator.setRoot(root);
+ Collections.sort(focusables, mSequentialFocusComparator);
+ } finally {
+ mSequentialFocusComparator.recycle();
}
+ final int count = focusables.size();
+ switch (direction) {
+ case View.FOCUS_FORWARD:
+ return getForwardFocusable(root, focused, focusables, count);
+ case View.FOCUS_BACKWARD:
+ return getBackwardFocusable(root, focused, focusables, count);
+ }
+ return focusables.get(count - 1);
+ }
+
+ private void setFocusBottomRight(ViewGroup root) {
+ final int rootBottom = root.getScrollY() + root.getHeight();
+ final int rootRight = root.getScrollX() + root.getWidth();
+ mFocusedRect.set(rootRight, rootBottom,
+ rootRight, rootBottom);
+ }
+
+ private void setFocusTopLeft(ViewGroup root) {
+ final int rootTop = root.getScrollY();
+ final int rootLeft = root.getScrollX();
+ mFocusedRect.set(rootLeft, rootTop, rootLeft, rootTop);
+ }
+
+ View findNextInputFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused,
+ Rect focusedRect, int direction) {
// initialize the best candidate to something impossible
// (so the first plausible view will become the best choice)
mBestCandidateRect.set(focusedRect);
@@ -201,6 +294,140 @@ public class FocusFinder {
return closest;
}
+ private View findNextAccessibilityFocusIn(View view) {
+ // We have to traverse the full view tree to make sure
+ // we consider views in the order specified by their
+ // parent layout managers since some managers could be
+ // LTR while some could be RTL.
+ if (mTempStack == null) {
+ mTempStack = new Stack<View>();
+ }
+ Stack<View> fringe = mTempStack;
+ fringe.clear();
+ fringe.add(view);
+ while (!fringe.isEmpty()) {
+ View current = fringe.pop();
+ if (current.getAccessibilityNodeProvider() != null) {
+ fringe.clear();
+ return current;
+ }
+ if (current != view && current.includeForAccessibility()) {
+ fringe.clear();
+ return current;
+ }
+ if (current instanceof ViewGroup) {
+ ViewGroup currentGroup = (ViewGroup) current;
+ ChildListForAccessibility children = ChildListForAccessibility.obtain(
+ currentGroup, true);
+ final int childCount = children.getChildCount();
+ for (int i = childCount - 1; i >= 0; i--) {
+ fringe.push(children.getChildAt(i));
+ }
+ children.recycle();
+ }
+ }
+ return null;
+ }
+
+ private View findNextAccessibilityFocusOut(View view) {
+ ViewParent parent = view.getParentForAccessibility();
+ if (parent instanceof View) {
+ return (View) parent;
+ }
+ return null;
+ }
+
+ private View findNextAccessibilityFocusForward(View view) {
+ // We have to traverse the full view tree to make sure
+ // we consider views in the order specified by their
+ // parent layout managers since some managers could be
+ // LTR while some could be RTL.
+ View current = view;
+ while (current != null) {
+ ViewParent parent = current.getParent();
+ if (!(parent instanceof ViewGroup)) {
+ return null;
+ }
+ ViewGroup parentGroup = (ViewGroup) parent;
+ // Ask the parent to find a sibling after the current view
+ // that can take accessibility focus.
+ ChildListForAccessibility children = ChildListForAccessibility.obtain(
+ parentGroup, true);
+ final int fromIndex = children.getChildIndex(current) + 1;
+ final int childCount = children.getChildCount();
+ for (int i = fromIndex; i < childCount; i++) {
+ View child = children.getChildAt(i);
+ View next = null;
+ if (child.getAccessibilityNodeProvider() != null) {
+ next = child;
+ } else if (child.includeForAccessibility()) {
+ next = child;
+ } else {
+ next = findNextAccessibilityFocusIn(child);
+ }
+ if (next != null) {
+ children.recycle();
+ return next;
+ }
+ }
+ children.recycle();
+ // Reaching a regarded for accessibility predecessor without
+ // finding a next view to take focus means that at this level
+ // there is no next accessibility focusable sibling.
+ if (parentGroup.includeForAccessibility()) {
+ return null;
+ }
+ // Try asking a predecessor to find a focusable.
+ current = parentGroup;
+ }
+ return null;
+ }
+
+ private View findNextAccessibilityFocusBackward(View view) {
+ // We have to traverse the full view tree to make sure
+ // we consider views in the order specified by their
+ // parent layout managers since some managers could be
+ // LTR while some could be RTL.
+ View current = view;
+ while (current != null) {
+ ViewParent parent = current.getParent();
+ if (!(parent instanceof ViewGroup)) {
+ return null;
+ }
+ ViewGroup parentGroup = (ViewGroup) parent;
+ // Ask the parent to find a sibling after the current view
+ // to take accessibility focus
+ ChildListForAccessibility children = ChildListForAccessibility.obtain(
+ parentGroup, true);
+ final int fromIndex = children.getChildIndex(current) - 1;
+ for (int i = fromIndex; i >= 0; i--) {
+ View child = children.getChildAt(i);
+ View next = null;
+ if (child.getAccessibilityNodeProvider() != null) {
+ next = child;
+ } else if (child.includeForAccessibility()) {
+ next = child;
+ } else {
+ next = findNextAccessibilityFocusIn(child);
+ }
+ if (next != null) {
+ children.recycle();
+ return next;
+ }
+ }
+ children.recycle();
+ // Reaching a regarded for accessibility predecessor without
+ // finding a previous view to take focus means that at this level
+ // there is no previous accessibility focusable sibling.
+ if (parentGroup.includeForAccessibility()) {
+ return null;
+ }
+ // Try asking a predecessor to find a focusable.
+ current = parentGroup;
+ }
+ return null;
+ }
+
private static View getForwardFocusable(ViewGroup root, View focused,
ArrayList<View> focusables, int count) {
return (root.isLayoutRtl()) ?
@@ -235,6 +462,47 @@ public class FocusFinder {
return focusables.get(count - 1);
}
+ private Rect getFocusedRect(ViewGroup root, View focused, int direction) {
+ Rect focusedRect = mFocusedRect;
+ if (focused != null) {
+ focused.getFocusedRect(focusedRect);
+ root.offsetDescendantRectToMyCoords(focused, focusedRect);
+ } else {
+ switch (direction) {
+ case View.FOCUS_RIGHT:
+ case View.FOCUS_DOWN:
+ final int rootTop = root.getScrollY();
+ final int rootLeft = root.getScrollX();
+ focusedRect.set(rootLeft, rootTop, rootLeft, rootTop);
+ break;
+
+ case View.FOCUS_LEFT:
+ case View.FOCUS_UP:
+ final int rootBottom = root.getScrollY() + root.getHeight();
+ final int rootRight = root.getScrollX() + root.getWidth();
+ focusedRect.set(rootRight, rootBottom, rootRight, rootBottom);
+ break;
+ }
+ }
+ return focusedRect;
+ }
+
+ private int getCorrespondingInputFocusDirection(int accessFocusDirection) {
+ switch (accessFocusDirection) {
+ case View.ACCESSIBILITY_FOCUS_LEFT:
+ return View.FOCUS_LEFT;
+ case View.ACCESSIBILITY_FOCUS_RIGHT:
+ return View.FOCUS_RIGHT;
+ case View.ACCESSIBILITY_FOCUS_UP:
+ return View.FOCUS_UP;
+ case View.ACCESSIBILITY_FOCUS_DOWN:
+ return View.FOCUS_DOWN;
+ default:
+ throw new IllegalArgumentException("Cannot map accessiblity focus"
+ + " direction: " + accessFocusDirection);
+ }
+ }
+
/**
* Is rect1 a better candidate than rect2 for a focus search in a particular
* direction from a source rect? This is the core routine that determines
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 75b2c746..4848a7a 100755
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -16,9 +16,12 @@
package android.view;
+import android.content.Context;
import android.hardware.input.InputManager;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.Vibrator;
+import android.os.NullVibrator;
import java.util.ArrayList;
import java.util.List;
@@ -40,13 +43,17 @@ import java.util.List;
*/
public final class InputDevice implements Parcelable {
private final int mId;
+ private final int mGeneration;
private final String mName;
private final String mDescriptor;
private final int mSources;
private final int mKeyboardType;
private final KeyCharacterMap mKeyCharacterMap;
+ private final boolean mHasVibrator;
private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>();
+ private Vibrator mVibrator; // guarded by mMotionRanges during initialization
+
/**
* A mask for input source classes.
*
@@ -302,23 +309,27 @@ public final class InputDevice implements Parcelable {
};
// Called by native code.
- private InputDevice(int id, String name, String descriptor, int sources,
- int keyboardType, KeyCharacterMap keyCharacterMap) {
+ private InputDevice(int id, int generation, String name, String descriptor, int sources,
+ int keyboardType, KeyCharacterMap keyCharacterMap, boolean hasVibrator) {
mId = id;
+ mGeneration = generation;
mName = name;
mDescriptor = descriptor;
mSources = sources;
mKeyboardType = keyboardType;
mKeyCharacterMap = keyCharacterMap;
+ mHasVibrator = hasVibrator;
}
private InputDevice(Parcel in) {
mId = in.readInt();
+ mGeneration = in.readInt();
mName = in.readString();
mDescriptor = in.readString();
mSources = in.readInt();
mKeyboardType = in.readInt();
mKeyCharacterMap = KeyCharacterMap.CREATOR.createFromParcel(in);
+ mHasVibrator = in.readInt() != 0;
for (;;) {
int axis = in.readInt();
@@ -364,6 +375,19 @@ public final class InputDevice implements Parcelable {
}
/**
+ * Gets a generation number for this input device.
+ * The generation number is incremented whenever the device is reconfigured and its
+ * properties may have changed.
+ *
+ * @return The generation number.
+ *
+ * @hide
+ */
+ public int getGeneration() {
+ return mGeneration;
+ }
+
+ /**
* Gets the input device descriptor, which is a stable identifier for an input device.
* <p>
* An input device descriptor uniquely identifies an input device. Its value
@@ -506,6 +530,31 @@ public final class InputDevice implements Parcelable {
}
/**
+ * Gets the vibrator service associated with the device, if there is one.
+ * Even if the device does not have a vibrator, the result is never null.
+ * Use {@link Vibrator#hasVibrator} to determine whether a vibrator is
+ * present.
+ *
+ * Note that the vibrator associated with the device may be different from
+ * the system vibrator. To obtain an instance of the system vibrator instead, call
+ * {@link Context#getSystemService} with {@link Context#VIBRATOR_SERVICE} as argument.
+ *
+ * @return The vibrator service associated with the device, never null.
+ */
+ public Vibrator getVibrator() {
+ synchronized (mMotionRanges) {
+ if (mVibrator == null) {
+ if (mHasVibrator) {
+ mVibrator = InputManager.getInstance().getInputDeviceVibrator(mId);
+ } else {
+ mVibrator = NullVibrator.getInstance();
+ }
+ }
+ return mVibrator;
+ }
+ }
+
+ /**
* Provides information about the range of values for a particular {@link MotionEvent} axis.
*
* @see InputDevice#getMotionRange(int)
@@ -595,11 +644,13 @@ public final class InputDevice implements Parcelable {
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mId);
+ out.writeInt(mGeneration);
out.writeString(mName);
out.writeString(mDescriptor);
out.writeInt(mSources);
out.writeInt(mKeyboardType);
mKeyCharacterMap.writeToParcel(out, flags);
+ out.writeInt(mHasVibrator ? 1 : 0);
final int numRanges = mMotionRanges.size();
for (int i = 0; i < numRanges; i++) {
@@ -624,6 +675,7 @@ public final class InputDevice implements Parcelable {
StringBuilder description = new StringBuilder();
description.append("Input Device ").append(mId).append(": ").append(mName).append("\n");
description.append(" Descriptor: ").append(mDescriptor).append("\n");
+ description.append(" Generation: ").append(mGeneration).append("\n");
description.append(" Keyboard Type: ");
switch (mKeyboardType) {
@@ -639,6 +691,8 @@ public final class InputDevice implements Parcelable {
}
description.append("\n");
+ description.append(" Has Vibrator: ").append(mHasVibrator).append("\n");
+
description.append(" Sources: 0x").append(Integer.toHexString(mSources)).append(" (");
appendSourceDescriptionIfApplicable(description, SOURCE_KEYBOARD, "keyboard");
appendSourceDescriptionIfApplicable(description, SOURCE_DPAD, "dpad");
diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java
index 3d165ea..12d7b12 100644
--- a/core/java/android/view/KeyCharacterMap.java
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -22,8 +22,6 @@ import android.text.method.MetaKeyKeyListener;
import android.util.AndroidRuntimeException;
import android.util.SparseIntArray;
import android.hardware.input.InputManager;
-import android.util.SparseArray;
-import android.view.InputDevice.MotionRange;
import java.lang.Character;
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index ba62e65..32029ba 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -187,7 +187,9 @@ public class TextureView extends View {
public void setOpaque(boolean opaque) {
if (opaque != mOpaque) {
mOpaque = opaque;
- updateLayer();
+ if (mLayer != null) {
+ updateLayer();
+ }
}
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 1fa19d1..d569ba1 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -24,6 +24,7 @@ import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Camera;
import android.graphics.Canvas;
+import android.graphics.Insets;
import android.graphics.Interpolator;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
@@ -975,6 +976,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
public static final int FOCUSABLES_TOUCH_MODE = 0x00000001;
/**
+ * View flag indicating whether {@link #addFocusables(ArrayList, int, int)}
+ * should add only accessibility focusable Views.
+ *
+ * @hide
+ */
+ public static final int FOCUSABLES_ACCESSIBILITY = 0x00000002;
+
+ /**
* Use with {@link #focusSearch(int)}. Move focus to the previous selectable
* item.
*/
@@ -1006,6 +1015,54 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
public static final int FOCUS_DOWN = 0x00000082;
+ // Accessibility focus directions.
+
+ /**
+ * The accessibility focus which is the current user position when
+ * interacting with the accessibility framework.
+ */
+ public static final int FOCUS_ACCESSIBILITY = 0x00001000;
+
+ /**
+ * Use with {@link #focusSearch(int)}. Move acessibility focus left.
+ */
+ public static final int ACCESSIBILITY_FOCUS_LEFT = FOCUS_LEFT | FOCUS_ACCESSIBILITY;
+
+ /**
+ * Use with {@link #focusSearch(int)}. Move acessibility focus up.
+ */
+ public static final int ACCESSIBILITY_FOCUS_UP = FOCUS_UP | FOCUS_ACCESSIBILITY;
+
+ /**
+ * Use with {@link #focusSearch(int)}. Move acessibility focus right.
+ */
+ public static final int ACCESSIBILITY_FOCUS_RIGHT = FOCUS_RIGHT | FOCUS_ACCESSIBILITY;
+
+ /**
+ * Use with {@link #focusSearch(int)}. Move acessibility focus down.
+ */
+ public static final int ACCESSIBILITY_FOCUS_DOWN = FOCUS_DOWN | FOCUS_ACCESSIBILITY;
+
+ /**
+ * Use with {@link #focusSearch(int)}. Move acessibility focus to the next view.
+ */
+ public static final int ACCESSIBILITY_FOCUS_FORWARD = FOCUS_FORWARD | FOCUS_ACCESSIBILITY;
+
+ /**
+ * Use with {@link #focusSearch(int)}. Move acessibility focus to the previous view.
+ */
+ public static final int ACCESSIBILITY_FOCUS_BACKWARD = FOCUS_BACKWARD | FOCUS_ACCESSIBILITY;
+
+ /**
+ * Use with {@link #focusSearch(int)}. Move acessibility focus in a view.
+ */
+ public static final int ACCESSIBILITY_FOCUS_IN = 0x00000004 | FOCUS_ACCESSIBILITY;
+
+ /**
+ * Use with {@link #focusSearch(int)}. Move acessibility focus out of a view.
+ */
+ public static final int ACCESSIBILITY_FOCUS_OUT = 0x00000008 | FOCUS_ACCESSIBILITY;
+
/**
* Bits of {@link #getMeasuredWidthAndState()} and
* {@link #getMeasuredWidthAndState()} that provide the actual measured size.
@@ -1330,7 +1387,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
R.attr.state_accelerated, VIEW_STATE_ACCELERATED,
R.attr.state_hovered, VIEW_STATE_HOVERED,
R.attr.state_drag_can_accept, VIEW_STATE_DRAG_CAN_ACCEPT,
- R.attr.state_drag_hovered, VIEW_STATE_DRAG_HOVERED,
+ R.attr.state_drag_hovered, VIEW_STATE_DRAG_HOVERED
};
static {
@@ -1452,7 +1509,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
| AccessibilityEvent.TYPE_VIEW_HOVER_ENTER
| AccessibilityEvent.TYPE_VIEW_HOVER_EXIT
| AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
- | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED;
+ | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED
+ | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
/**
* Temporary Rect currently for use in setBackground(). This will probably
@@ -1784,7 +1842,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
private static final int LAYOUT_DIRECTION_DEFAULT = LAYOUT_DIRECTION_INHERIT;
-
/**
* Indicates that the view is tracking some sort of transient state
* that the app should not need to be aware of, but that the framework
@@ -1992,6 +2049,50 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
public static final int TEXT_ALIGNMENT_RESOLVED_DEFAULT =
TEXT_ALIGNMENT_GRAVITY << TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT;
+ // Accessiblity constants for mPrivateFlags2
+
+ /**
+ * Shift for accessibility related bits in {@link #mPrivateFlags2}.
+ */
+ static final int IMPORTANT_FOR_ACCESSIBILITY_SHIFT = 20;
+
+ /**
+ * Automatically determine whether a view is important for accessibility.
+ */
+ public static final int IMPORTANT_FOR_ACCESSIBILITY_AUTO = 0x00000000;
+
+ /**
+ * The view is important for accessibility.
+ */
+ public static final int IMPORTANT_FOR_ACCESSIBILITY_YES = 0x00000001;
+
+ /**
+ * The view is not important for accessibility.
+ */
+ public static final int IMPORTANT_FOR_ACCESSIBILITY_NO = 0x00000002;
+
+ /**
+ * The default whether the view is important for accessiblity.
+ */
+ static final int IMPORTANT_FOR_ACCESSIBILITY_DEFAULT = IMPORTANT_FOR_ACCESSIBILITY_AUTO;
+
+ /**
+ * Mask for obtainig the bits which specify how to determine
+ * whether a view is important for accessibility.
+ */
+ static final int IMPORTANT_FOR_ACCESSIBILITY_MASK = (IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ | IMPORTANT_FOR_ACCESSIBILITY_YES | IMPORTANT_FOR_ACCESSIBILITY_NO)
+ << IMPORTANT_FOR_ACCESSIBILITY_SHIFT;
+
+ /**
+ * Flag indicating whether a view has accessibility focus.
+ */
+ static final int ACCESSIBILITY_FOCUSED = 0x00000040 << IMPORTANT_FOR_ACCESSIBILITY_SHIFT;
+
+ /**
+ * Flag indicating whether a view state for accessibility has changed.
+ */
+ static final int ACCESSIBILITY_STATE_CHANGED = 0x00000080 << IMPORTANT_FOR_ACCESSIBILITY_SHIFT;
/* End of masks for mPrivateFlags2 */
@@ -2598,6 +2699,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
protected int mPaddingBottom;
/**
+ * The layout insets in pixels, that is the distance in pixels between the
+ * visible edges of this view its bounds.
+ */
+ private Insets mLayoutInsets;
+
+ /**
* Briefly describes the view and is primarily used for accessibility support.
*/
private CharSequence mContentDescription;
@@ -2952,7 +3059,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
// Set layout and text direction defaults
mPrivateFlags2 = (LAYOUT_DIRECTION_DEFAULT << LAYOUT_DIRECTION_MASK_SHIFT) |
(TEXT_DIRECTION_DEFAULT << TEXT_DIRECTION_MASK_SHIFT) |
- (TEXT_ALIGNMENT_DEFAULT << TEXT_ALIGNMENT_MASK_SHIFT);
+ (TEXT_ALIGNMENT_DEFAULT << TEXT_ALIGNMENT_MASK_SHIFT) |
+ (IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << IMPORTANT_FOR_ACCESSIBILITY_SHIFT);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
mUserPaddingStart = -1;
@@ -3340,6 +3448,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
final int textAlignment = a.getInt(attr, TEXT_ALIGNMENT_DEFAULT);
mPrivateFlags2 |= TEXT_ALIGNMENT_FLAGS[textAlignment];
break;
+ case R.styleable.View_importantForAccessibility:
+ setImportantForAccessibility(a.getInt(attr,
+ IMPORTANT_FOR_ACCESSIBILITY_DEFAULT));
}
}
@@ -3970,6 +4081,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
onFocusChanged(true, direction, previouslyFocusedRect);
refreshDrawableState();
+
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ notifyAccessibilityStateChanged();
+ }
}
}
@@ -4050,16 +4165,21 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
onFocusChanged(false, 0, null);
+
refreshDrawableState();
ensureInputFocusOnFirstFocusable();
+
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ notifyAccessibilityStateChanged();
+ }
}
}
void ensureInputFocusOnFirstFocusable() {
View root = getRootView();
if (root != null) {
- root.requestFocus(FOCUS_FORWARD);
+ root.requestFocus();
}
}
@@ -4077,6 +4197,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
onFocusChanged(false, 0, null);
refreshDrawableState();
+
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ notifyAccessibilityStateChanged();
+ }
}
}
@@ -4127,7 +4251,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
if (gainFocus) {
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ requestAccessibilityFocus();
+ }
}
InputMethodManager imm = InputMethodManager.peekInstance();
@@ -4237,7 +4364,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
if (mAccessibilityDelegate != null) {
- mAccessibilityDelegate.sendAccessibilityEventUnchecked(this, event);
+ mAccessibilityDelegate.sendAccessibilityEventUnchecked(this, event);
} else {
sendAccessibilityEventUncheckedInternal(event);
}
@@ -4257,6 +4384,31 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if ((event.getEventType() & POPULATING_ACCESSIBILITY_EVENT_TYPES) != 0) {
dispatchPopulateAccessibilityEvent(event);
}
+ // Intercept accessibility focus events fired by virtual nodes to keep
+ // track of accessibility focus position in such nodes.
+ final int eventType = event.getEventType();
+ switch (eventType) {
+ case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
+ final long virtualNodeId = AccessibilityNodeInfo.getVirtualDescendantId(
+ event.getSourceNodeId());
+ if (virtualNodeId != AccessibilityNodeInfo.UNDEFINED) {
+ ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl != null) {
+ viewRootImpl.setAccessibilityFocusedHost(this);
+ }
+ }
+ } break;
+ case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
+ final long virtualNodeId = AccessibilityNodeInfo.getVirtualDescendantId(
+ event.getSourceNodeId());
+ if (virtualNodeId != AccessibilityNodeInfo.UNDEFINED) {
+ ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl != null) {
+ viewRootImpl.setAccessibilityFocusedHost(null);
+ }
+ }
+ } break;
+ }
// In the beginning we called #isShown(), so we know that getParent() is not null.
getParent().requestSendAccessibilityEvent(this, event);
}
@@ -4399,7 +4551,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
event.setContentDescription(mContentDescription);
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED && mAttachInfo != null) {
- ArrayList<View> focusablesTempList = mAttachInfo.mFocusablesTempList;
+ ArrayList<View> focusablesTempList = mAttachInfo.mTempArrayList;
getRootView().addFocusables(focusablesTempList, View.FOCUS_FORWARD,
FOCUSABLES_ALL);
event.setItemCount(focusablesTempList.size());
@@ -4488,10 +4640,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
info.setBoundsInScreen(bounds);
if ((mPrivateFlags & IS_ROOT_NAMESPACE) == 0) {
- ViewParent parent = getParent();
+ ViewParent parent = getParentForAccessibility();
if (parent instanceof View) {
- View parentView = (View) parent;
- info.setParent(parentView);
+ info.setParent((View) parent);
}
}
@@ -4503,6 +4654,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
info.setClickable(isClickable());
info.setFocusable(isFocusable());
info.setFocused(isFocused());
+ info.setAccessibilityFocused(isAccessibilityFocused());
info.setSelected(isSelected());
info.setLongClickable(isLongClickable());
@@ -4597,10 +4749,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* true for views that do not have textual representation (For example,
* ImageButton).
*
- * @return The content descriptiopn.
+ * @return The content description.
*
* @attr ref android.R.styleable#View_contentDescription
*/
+ @ViewDebug.ExportedProperty(category = "accessibility")
public CharSequence getContentDescription() {
return mContentDescription;
}
@@ -5650,8 +5803,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* Adds any focusable views that are descendants of this view (possibly
* including this view if it is focusable itself) to views. This method
* adds all focusable views regardless if we are in touch mode or
- * only views focusable in touch mode if we are in touch mode depending on
- * the focusable mode paramater.
+ * only views focusable in touch mode if we are in touch mode or
+ * only views that can take accessibility focus if accessibility is enabeld
+ * depending on the focusable mode paramater.
*
* @param views Focusable views found so far or null if all we are interested is
* the number of focusables.
@@ -5660,19 +5814,32 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*
* @see #FOCUSABLES_ALL
* @see #FOCUSABLES_TOUCH_MODE
+ * @see #FOCUSABLES_ACCESSIBILITY
*/
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
- if (!isFocusable()) {
+ if (views == null) {
return;
}
-
- if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&
- isInTouchMode() && !isFocusableInTouchMode()) {
- return;
+ if ((focusableMode & FOCUSABLE_IN_TOUCH_MODE) == FOCUSABLE_IN_TOUCH_MODE) {
+ if (isFocusable() && (!isInTouchMode() || isFocusableInTouchMode())) {
+ views.add(this);
+ return;
+ }
}
-
- if (views != null) {
- views.add(this);
+ if ((focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) {
+ if (AccessibilityManager.getInstance(mContext).isEnabled()
+ && includeForAccessibility()) {
+ views.add(this);
+ return;
+ }
+ }
+ if ((focusableMode & FOCUSABLES_ALL) == FOCUSABLES_ALL) {
+ if (isFocusable()) {
+ views.add(this);
+ return;
+ }
+ } else {
+ throw new IllegalArgumentException("Unknow focusable mode: " + focusableMode);
}
}
@@ -5734,6 +5901,149 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
+ * Returns whether this View is accessibility focused.
+ *
+ * @return True if this View is accessibility focused.
+ */
+ boolean isAccessibilityFocused() {
+ return (mPrivateFlags2 & ACCESSIBILITY_FOCUSED) != 0;
+ }
+
+ /**
+ * Call this to try to give accessibility focus to this view.
+ *
+ * A view will not actually take focus if {@link AccessibilityManager#isEnabled()}
+ * returns false or the view is no visible or the view already has accessibility
+ * focus.
+ *
+ * See also {@link #focusSearch(int)}, which is what you call to say that you
+ * have focus, and you want your parent to look for the next one.
+ *
+ * @return Whether this view actually took accessibility focus.
+ *
+ * @hide
+ */
+ public boolean requestAccessibilityFocus() {
+ if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
+ return false;
+ }
+ if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) {
+ return false;
+ }
+ if ((mPrivateFlags2 & ACCESSIBILITY_FOCUSED) == 0) {
+ mPrivateFlags2 |= ACCESSIBILITY_FOCUSED;
+ ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl != null) {
+ viewRootImpl.setAccessibilityFocusedHost(this);
+ }
+ invalidate();
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
+ notifyAccessibilityStateChanged();
+ // Try to give input focus to this view - not a descendant.
+ requestFocusNoSearch(View.FOCUS_DOWN, null);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Call this to try to clear accessibility focus of this view.
+ *
+ * See also {@link #focusSearch(int)}, which is what you call to say that you
+ * have focus, and you want your parent to look for the next one.
+ *
+ * @hide
+ */
+ public void clearAccessibilityFocus() {
+ if ((mPrivateFlags2 & ACCESSIBILITY_FOCUSED) != 0) {
+ mPrivateFlags2 &= ~ACCESSIBILITY_FOCUSED;
+ ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl != null) {
+ viewRootImpl.setAccessibilityFocusedHost(null);
+ }
+ invalidate();
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
+ notifyAccessibilityStateChanged();
+ // Try to move accessibility focus to the input focus.
+ View rootView = getRootView();
+ if (rootView != null) {
+ View inputFocus = rootView.findFocus();
+ if (inputFocus != null) {
+ inputFocus.requestAccessibilityFocus();
+ }
+ }
+ }
+ }
+
+ /**
+ * Find the best view to take accessibility focus from a hover.
+ * This function finds the deepest actionable view and if that
+ * fails ask the parent to take accessibility focus from hover.
+ *
+ * @param x The X hovered location in this view coorditantes.
+ * @param y The Y hovered location in this view coorditantes.
+ * @return Whether the request was handled.
+ *
+ * @hide
+ */
+ public boolean requestAccessibilityFocusFromHover(float x, float y) {
+ if (onRequestAccessibilityFocusFromHover(x, y)) {
+ return true;
+ }
+ ViewParent parent = mParent;
+ if (parent instanceof View) {
+ View parentView = (View) parent;
+
+ float[] position = mAttachInfo.mTmpTransformLocation;
+ position[0] = x;
+ position[1] = y;
+
+ // Compensate for the transformation of the current matrix.
+ if (!hasIdentityMatrix()) {
+ getMatrix().mapPoints(position);
+ }
+
+ // Compensate for the parent scroll and the offset
+ // of this view stop from the parent top.
+ position[0] += mLeft - parentView.mScrollX;
+ position[1] += mTop - parentView.mScrollY;
+
+ return parentView.requestAccessibilityFocusFromHover(position[0], position[1]);
+ }
+ return false;
+ }
+
+ /**
+ * Requests to give this View focus from hover.
+ *
+ * @param x The X hovered location in this view coorditantes.
+ * @param y The Y hovered location in this view coorditantes.
+ * @return Whether the request was handled.
+ *
+ * @hide
+ */
+ public boolean onRequestAccessibilityFocusFromHover(float x, float y) {
+ if (includeForAccessibility()
+ && (isActionableForAccessibility() || hasListenersForAccessibility())) {
+ return requestAccessibilityFocus();
+ }
+ return false;
+ }
+
+ /**
+ * Clears accessibility focus without calling any callback methods
+ * normally invoked in {@link #clearAccessibilityFocus()}. This method
+ * is used for clearing accessibility focus when giving this focus to
+ * another view.
+ */
+ void clearAccessibilityFocusNoCallbacks() {
+ if ((mPrivateFlags2 & ACCESSIBILITY_FOCUSED) != 0) {
+ mPrivateFlags2 &= ~ACCESSIBILITY_FOCUSED;
+ invalidate();
+ }
+ }
+
+ /**
* Call this to try to give focus to a specific view or to one of its
* descendants.
*
@@ -5753,7 +6063,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
return requestFocus(View.FOCUS_DOWN);
}
-
/**
* Call this to try to give focus to a specific view or to one of its
* descendants and give it a hint about what direction focus is heading.
@@ -5805,6 +6114,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @return Whether this view or one of its descendants actually took focus.
*/
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
+ return requestFocusNoSearch(direction, previouslyFocusedRect);
+ }
+
+ private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
// need to be focusable
if ((mViewFlags & FOCUSABLE_MASK) != FOCUSABLE ||
(mViewFlags & VISIBILITY_MASK) != VISIBLE) {
@@ -5864,6 +6177,248 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
+ * Gets the mode for determining whether this View is important for accessibility
+ * which is if it fires accessibility events and if it is reported to
+ * accessibility services that query the screen.
+ *
+ * @return The mode for determining whether a View is important for accessibility.
+ *
+ * @attr ref android.R.styleable#View_importantForAccessibility
+ *
+ * @see #IMPORTANT_FOR_ACCESSIBILITY_YES
+ * @see #IMPORTANT_FOR_ACCESSIBILITY_NO
+ * @see #IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ */
+ @ViewDebug.ExportedProperty(category = "accessibility", mapping = {
+ @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_AUTO,
+ to = "IMPORTANT_FOR_ACCESSIBILITY_AUTO"),
+ @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_YES,
+ to = "IMPORTANT_FOR_ACCESSIBILITY_YES"),
+ @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_NO,
+ to = "IMPORTANT_FOR_ACCESSIBILITY_NO")
+ })
+ public int getImportantForAccessibility() {
+ return (mPrivateFlags2 & IMPORTANT_FOR_ACCESSIBILITY_MASK)
+ >> IMPORTANT_FOR_ACCESSIBILITY_SHIFT;
+ }
+
+ /**
+ * Sets how to determine whether this view is important for accessibility
+ * which is if it fires accessibility events and if it is reported to
+ * accessibility services that query the screen.
+ *
+ * @param mode How to determine whether this view is important for accessibility.
+ *
+ * @attr ref android.R.styleable#View_importantForAccessibility
+ *
+ * @see #IMPORTANT_FOR_ACCESSIBILITY_YES
+ * @see #IMPORTANT_FOR_ACCESSIBILITY_NO
+ * @see #IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ */
+ public void setImportantForAccessibility(int mode) {
+ if (mode != getImportantForAccessibility()) {
+ mPrivateFlags2 &= ~IMPORTANT_FOR_ACCESSIBILITY_MASK;
+ mPrivateFlags2 |= (mode << IMPORTANT_FOR_ACCESSIBILITY_SHIFT)
+ & IMPORTANT_FOR_ACCESSIBILITY_MASK;
+ notifyAccessibilityStateChanged();
+ }
+ }
+
+ /**
+ * Gets whether this view should be exposed for accessibility.
+ *
+ * @return Whether the view is exposed for accessibility.
+ *
+ * @hide
+ */
+ public boolean isImportantForAccessibility() {
+ final int mode = (mPrivateFlags2 & IMPORTANT_FOR_ACCESSIBILITY_MASK)
+ >> IMPORTANT_FOR_ACCESSIBILITY_SHIFT;
+ switch (mode) {
+ case IMPORTANT_FOR_ACCESSIBILITY_YES:
+ return true;
+ case IMPORTANT_FOR_ACCESSIBILITY_NO:
+ return false;
+ case IMPORTANT_FOR_ACCESSIBILITY_AUTO:
+ return isActionableForAccessibility() || hasListenersForAccessibility();
+ default:
+ throw new IllegalArgumentException("Unknow important for accessibility mode: "
+ + mode);
+ }
+ }
+
+ /**
+ * Gets the parent for accessibility purposes. Note that the parent for
+ * accessibility is not necessary the immediate parent. It is the first
+ * predecessor that is important for accessibility.
+ *
+ * @return The parent for accessibility purposes.
+ */
+ public ViewParent getParentForAccessibility() {
+ if (mParent instanceof View) {
+ View parentView = (View) mParent;
+ if (parentView.includeForAccessibility()) {
+ return mParent;
+ } else {
+ return mParent.getParentForAccessibility();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Adds the children of a given View for accessibility. Since some Views are
+ * not important for accessibility the children for accessibility are not
+ * necessarily direct children of the riew, rather they are the first level of
+ * descendants important for accessibility.
+ *
+ * @param children The list of children for accessibility.
+ */
+ public void addChildrenForAccessibility(ArrayList<View> children) {
+ if (includeForAccessibility()) {
+ children.add(this);
+ }
+ }
+
+ /**
+ * Whether to regard this view for accessibility. A view is regarded for
+ * accessibility if it is important for accessibility or the querying
+ * accessibility service has explicitly requested that view not
+ * important for accessibility are regarded.
+ *
+ * @return Whether to regard the view for accessibility.
+ */
+ boolean includeForAccessibility() {
+ if (mAttachInfo != null) {
+ if (!mAttachInfo.mIncludeNotImportantViews) {
+ return isImportantForAccessibility();
+ } else {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether the View is considered actionable from
+ * accessibility perspective. Such view are important for
+ * accessiiblity.
+ *
+ * @return True if the view is actionable for accessibility.
+ */
+ private boolean isActionableForAccessibility() {
+ return (isClickable() || isLongClickable() || isFocusable());
+ }
+
+ /**
+ * Returns whether the View has registered callbacks wich makes it
+ * important for accessiiblity.
+ *
+ * @return True if the view is actionable for accessibility.
+ */
+ private boolean hasListenersForAccessibility() {
+ ListenerInfo info = getListenerInfo();
+ return mTouchDelegate != null || info.mOnKeyListener != null
+ || info.mOnTouchListener != null || info.mOnGenericMotionListener != null
+ || info.mOnHoverListener != null || info.mOnDragListener != null;
+ }
+
+ /**
+ * Notifies accessibility services that some view's important for
+ * accessibility state has changed. Note that such notifications
+ * are made at most once every
+ * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}
+ * to avoid unnecessary load to the system. Also once a view has
+ * made a notifucation this method is a NOP until the notification has
+ * been sent to clients.
+ *
+ * @hide
+ *
+ * TODO: Makse sure this method is called for any view state change
+ * that is interesting for accessilility purposes.
+ */
+ public void notifyAccessibilityStateChanged() {
+ if ((mPrivateFlags2 & ACCESSIBILITY_STATE_CHANGED) == 0) {
+ mPrivateFlags2 |= ACCESSIBILITY_STATE_CHANGED;
+ if (mParent != null) {
+ mParent.childAccessibilityStateChanged(this);
+ }
+ }
+ }
+
+ /**
+ * Reset the state indicating the this view has requested clients
+ * interested in its accessiblity state to be notified.
+ *
+ * @hide
+ */
+ public void resetAccessibilityStateChanged() {
+ mPrivateFlags2 &= ~ACCESSIBILITY_STATE_CHANGED;
+ }
+
+ /**
+ * Performs the specified accessibility action on the view. For
+ * possible accessibility actions look at {@link AccessibilityNodeInfo}.
+ *
+ * @param action The action to perform.
+ * @return Whether the action was performed.
+ */
+ public boolean performAccessibilityAction(int action) {
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_CLICK: {
+ final long now = SystemClock.uptimeMillis();
+ // Send down.
+ MotionEvent event = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN,
+ getWidth() / 2, getHeight() / 2, 0);
+ onTouchEvent(event);
+ // Send up.
+ event.setAction(MotionEvent.ACTION_UP);
+ onTouchEvent(event);
+ // Clean up.
+ event.recycle();
+ } break;
+ case AccessibilityNodeInfo.ACTION_FOCUS: {
+ if (!hasFocus()) {
+ // Get out of touch mode since accessibility
+ // wants to move focus around.
+ getViewRootImpl().ensureTouchMode(false);
+ return requestFocus();
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_CLEAR_FOCUS: {
+ if (hasFocus()) {
+ clearFocus();
+ return !isFocused();
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_SELECT: {
+ if (!isSelected()) {
+ setSelected(true);
+ return isSelected();
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
+ if (isSelected()) {
+ setSelected(false);
+ return !isSelected();
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
+ if (!isAccessibilityFocused()) {
+ return requestAccessibilityFocus();
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
+ if (isAccessibilityFocused()) {
+ clearAccessibilityFocus();
+ return true;
+ }
+ } break;
+ }
+ return false;
+ }
+
+ /**
* @hide
*/
public void dispatchStartTemporaryDetach() {
@@ -6757,21 +7312,27 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
// The root view may receive hover (or touch) events that are outside the bounds of
// the window. This code ensures that we only send accessibility events for
// hovers that are actually within the bounds of the root view.
- final int action = event.getAction();
+ final int action = event.getActionMasked();
if (!mSendingHoverAccessibilityEvents) {
if ((action == MotionEvent.ACTION_HOVER_ENTER
|| action == MotionEvent.ACTION_HOVER_MOVE)
&& !hasHoveredChild()
&& pointInView(event.getX(), event.getY())) {
- mSendingHoverAccessibilityEvents = true;
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
+ mSendingHoverAccessibilityEvents = true;
+ requestAccessibilityFocusFromHover((int) event.getX(), (int) event.getY());
}
} else {
if (action == MotionEvent.ACTION_HOVER_EXIT
- || (action == MotionEvent.ACTION_HOVER_MOVE
+ || (action == MotionEvent.ACTION_MOVE
&& !pointInView(event.getX(), event.getY()))) {
mSendingHoverAccessibilityEvents = false;
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
+ // If the window does not have input focus we take away accessibility
+ // focus as soon as the user stop hovering over the view.
+ if (!mAttachInfo.mHasWindowFocus) {
+ getViewRootImpl().setAccessibilityFocusedHost(null);
+ }
}
}
@@ -6795,6 +7356,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
dispatchGenericMotionEventInternal(event);
return true;
}
+
return false;
}
@@ -6806,7 +7368,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
private boolean isHoverable() {
final int viewFlags = mViewFlags;
- //noinspection SimplifiableIfStatement
if ((viewFlags & ENABLED_MASK) == DISABLED) {
return false;
}
@@ -7130,6 +7691,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
if (mParent != null) mParent.focusableViewAvailable(this);
}
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ notifyAccessibilityStateChanged();
+ }
}
if ((flags & VISIBILITY_MASK) == VISIBLE) {
@@ -7161,6 +7725,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (((mViewFlags & VISIBILITY_MASK) == GONE)) {
if (hasFocus()) clearFocus();
+ clearAccessibilityFocus();
destroyDrawingCache();
if (mParent instanceof View) {
// GONE views noop invalidation, so invalidate the parent
@@ -7185,9 +7750,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
mPrivateFlags |= DRAWN;
if (((mViewFlags & VISIBILITY_MASK) == INVISIBLE) && hasFocus()) {
- // root view becoming invisible shouldn't clear focus
+ // root view becoming invisible shouldn't clear focus and accessibility focus
if (getRootView() != this) {
clearFocus();
+ clearAccessibilityFocus();
}
}
if (mAttachInfo != null) {
@@ -7241,6 +7807,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
mParent.recomputeViewAttributes(this);
}
}
+
+ if (AccessibilityManager.getInstance(mContext).isEnabled()
+ && ((changed & FOCUSABLE) != 0 || (changed & CLICKABLE) != 0
+ || (changed & LONG_CLICKABLE) != 0 || (changed & ENABLED) != 0)) {
+ notifyAccessibilityStateChanged();
+ }
}
/**
@@ -7319,6 +7891,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @param canvas the canvas on which to draw the view
*/
protected void dispatchDraw(Canvas canvas) {
+
}
/**
@@ -10227,6 +10800,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
resolvePadding();
resolveTextDirection();
resolveTextAlignment();
+ clearAccessibilityFocus();
if (isFocused()) {
InputMethodManager imm = InputMethodManager.peekInstance();
imm.focusIn(this);
@@ -10457,6 +11031,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
resetResolvedLayoutDirection();
resetResolvedTextAlignment();
+ resetAccessibilityStateChanged();
+ clearAccessibilityFocus();
}
/**
@@ -13273,6 +13849,29 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
+ * @hide
+ */
+ public Insets getLayoutInsets() {
+ if (mLayoutInsets == null) {
+ if (mBackground == null) {
+ mLayoutInsets = Insets.NONE;
+ } else {
+ Rect insetRect = new Rect();
+ boolean hasInsets = mBackground.getLayoutInsets(insetRect);
+ mLayoutInsets = hasInsets ? Insets.of(insetRect) : Insets.NONE;
+ }
+ }
+ return mLayoutInsets;
+ }
+
+ /**
+ * @hide
+ */
+ public void setLayoutInsets(Insets layoutInsets) {
+ mLayoutInsets = layoutInsets;
+ }
+
+ /**
* Changes the selection state of this view. A view can be selected or not.
* Note that selection is not the same as focus. Views are typically
* selected in the context of an AdapterView like ListView or GridView;
@@ -13287,6 +13886,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
invalidate(true);
refreshDrawableState();
dispatchSetSelected(selected);
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ notifyAccessibilityStateChanged();
+ }
}
}
@@ -13456,7 +14058,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
position[1] += view.mTop;
viewParent = view.mParent;
- }
+ }
if (viewParent instanceof ViewRootImpl) {
// *cough*
@@ -16291,7 +16893,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
/**
* Temporary list for use in collecting focusable descendents of a view.
*/
- final ArrayList<View> mFocusablesTempList = new ArrayList<View>(24);
+ final ArrayList<View> mTempArrayList = new ArrayList<View>(24);
/**
* The id of the window for accessibility purposes.
@@ -16299,6 +16901,17 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
int mAccessibilityWindowId = View.NO_ID;
/**
+ * Whether to ingore not exposed for accessibility Views when
+ * reporting the view tree to accessibility services.
+ */
+ boolean mIncludeNotImportantViews;
+
+ /**
+ * The drawable for highlighting accessibility focus.
+ */
+ Drawable mAccessibilityFocusDrawable;
+
+ /**
* Creates a new set of attachment information with the specified
* events handler and thread.
*
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 9d06145..9134966 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -197,7 +197,7 @@ public class ViewConfiguration {
* gesture and the touch up event of a subsequent tap for the latter tap to be
* considered as a tap i.e. to perform a click.
*/
- private static final int TOUCH_EXPLORATION_TAP_SLOP = 80;
+ private static final int TOUCH_EXPLORE_TAP_SLOP = 80;
/**
* Delay before dispatching a recurring accessibility event in milliseconds.
@@ -238,7 +238,7 @@ public class ViewConfiguration {
private final int mDoubleTapTouchSlop;
private final int mPagingTouchSlop;
private final int mDoubleTapSlop;
- private final int mScaledTouchExplorationTapSlop;
+ private final int mScaledTouchExploreTapSlop;
private final int mWindowTouchSlop;
private final int mMaximumDrawingCacheSize;
private final int mOverscrollDistance;
@@ -265,7 +265,7 @@ public class ViewConfiguration {
mDoubleTapTouchSlop = DOUBLE_TAP_TOUCH_SLOP;
mPagingTouchSlop = PAGING_TOUCH_SLOP;
mDoubleTapSlop = DOUBLE_TAP_SLOP;
- mScaledTouchExplorationTapSlop = TOUCH_EXPLORATION_TAP_SLOP;
+ mScaledTouchExploreTapSlop = TOUCH_EXPLORE_TAP_SLOP;
mWindowTouchSlop = WINDOW_TOUCH_SLOP;
//noinspection deprecation
mMaximumDrawingCacheSize = MAXIMUM_DRAWING_CACHE_SIZE;
@@ -302,7 +302,7 @@ public class ViewConfiguration {
mMaximumFlingVelocity = (int) (density * MAXIMUM_FLING_VELOCITY + 0.5f);
mScrollbarSize = (int) (density * SCROLL_BAR_SIZE + 0.5f);
mDoubleTapSlop = (int) (sizeAndDensity * DOUBLE_TAP_SLOP + 0.5f);
- mScaledTouchExplorationTapSlop = (int) (density * TOUCH_EXPLORATION_TAP_SLOP + 0.5f);
+ mScaledTouchExploreTapSlop = (int) (density * TOUCH_EXPLORE_TAP_SLOP + 0.5f);
mWindowTouchSlop = (int) (sizeAndDensity * WINDOW_TOUCH_SLOP + 0.5f);
final Display display = WindowManagerImpl.getDefault().getDefaultDisplay();
@@ -559,8 +559,8 @@ public class ViewConfiguration {
*
* @hide
*/
- public int getScaledTouchExplorationTapSlop() {
- return mScaledTouchExplorationTapSlop;
+ public int getScaledTouchExploreTapSlop() {
+ return mScaledTouchExploreTapSlop;
}
/**
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 8f6badf..cb37a1c 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -141,6 +141,18 @@ public class ViewDebug {
public static final String DEBUG_LATENCY_TAG = "ViewLatency";
/**
+ * Enables detailed logging of accessibility focus operations.
+ * @hide
+ */
+ public static final boolean DEBUG_ACCESSIBILITY_FOCUS = false;
+
+ /**
+ * Tag for logging of accessibility focus operations
+ * @hide
+ */
+ public static final String DEBUG_ACCESSIBILITY_FOCUS_TAG = "AccessibilityFocus";
+
+ /**
* <p>Enables or disables views consistency check. Even when this property is enabled,
* view consistency checks happen only if {@link false} is set
* to true. The value of this property can be configured externally in one of the
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 121b544..5e02b49 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -45,6 +45,7 @@ import com.android.internal.R;
import com.android.internal.util.Predicate;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashSet;
/**
@@ -169,6 +170,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
protected int mGroupFlags;
+ /*
+ * THe layout mode: either {@link #UNDEFINED_LAYOUT_MODE}, {@link #COMPONENT_BOUNDS} or
+ * {@link #LAYOUT_BOUNDS}
+ */
+ private int mLayoutMode = UNDEFINED_LAYOUT_MODE;
+
/**
* NOTE: If you change the flags below make sure to reflect the changes
* the DisplayList class
@@ -334,6 +341,22 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
public static final int PERSISTENT_ALL_CACHES = 0x3;
+ // Layout Modes
+
+ private static final int UNDEFINED_LAYOUT_MODE = -1;
+
+ /**
+ * This constant is a {@link #setLayoutMode(int) layoutMode}.
+ * Component bounds are the raw values of {@link #getLeft() left}, {@link #getTop() top},
+ * {@link #getRight() right} and {@link #getBottom() bottom}.
+ */
+ public static final int COMPONENT_BOUNDS = 0;
+
+ /**
+ * This constant is a {@link #setLayoutMode(int) layoutMode}.
+ */
+ public static final int LAYOUT_BOUNDS = 1;
+
/**
* We clip to padding when FLAG_CLIP_TO_PADDING and FLAG_PADDING_NOT_NULL
* are set at the same time.
@@ -611,13 +634,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/**
* {@inheritDoc}
*/
+ @Override
public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
- ViewParent parent = getParent();
+ ViewParent parent = mParent;
if (parent == null) {
return false;
}
final boolean propagate = onRequestSendAccessibilityEvent(child, event);
- //noinspection SimplifiableIfStatement
if (!propagate) {
return false;
}
@@ -1552,6 +1575,33 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
return mFirstHoverTarget != null;
}
+ @Override
+ public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) {
+ View[] children = mChildren;
+ final int childrenCount = mChildrenCount;
+ for (int i = 0; i < childrenCount; i++) {
+ View child = children[i];
+ if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
+ && (child.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) {
+ if (child.includeForAccessibility()) {
+ childrenForAccessibility.add(child);
+ } else {
+ child.addChildrenForAccessibility(childrenForAccessibility);
+ }
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void childAccessibilityStateChanged(View child) {
+ if (mParent != null) {
+ mParent.childAccessibilityStateChanged(child);
+ }
+ }
+
/**
* Implement this method to intercept hover events before they are handled
* by child views.
@@ -2294,33 +2344,43 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
@Override
boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
- boolean handled = super.dispatchPopulateAccessibilityEventInternal(event);
- if (handled) {
- return handled;
+ boolean handled = false;
+ if (includeForAccessibility()) {
+ handled = super.dispatchPopulateAccessibilityEventInternal(event);
+ if (handled) {
+ return handled;
+ }
}
// Let our children have a shot in populating the event.
- for (int i = 0, count = getChildCount(); i < count; i++) {
- View child = getChildAt(i);
+ ChildListForAccessibility children = ChildListForAccessibility.obtain(this, true);
+ final int childCount = children.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = children.getChildAt(i);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
- handled = getChildAt(i).dispatchPopulateAccessibilityEvent(event);
+ handled = child.dispatchPopulateAccessibilityEvent(event);
if (handled) {
+ children.recycle();
return handled;
}
}
}
+ children.recycle();
return false;
}
@Override
void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfoInternal(info);
- info.setClassName(ViewGroup.class.getName());
- for (int i = 0, count = mChildrenCount; i < count; i++) {
- View child = mChildren[i];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
- && (child.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) {
+ if (mAttachInfo != null) {
+ ArrayList<View> childrenForAccessibility = mAttachInfo.mTempArrayList;
+ childrenForAccessibility.clear();
+ addChildrenForAccessibility(childrenForAccessibility);
+ final int childrenForAccessibilityCount = childrenForAccessibility.size();
+ for (int i = 0; i < childrenForAccessibilityCount; i++) {
+ View child = childrenForAccessibility.get(i);
info.addChild(child);
}
+ childrenForAccessibility.clear();
}
}
@@ -2331,6 +2391,20 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
+ * @hide
+ */
+ @Override
+ public void resetAccessibilityStateChanged() {
+ super.resetAccessibilityStateChanged();
+ View[] children = mChildren;
+ final int childCount = mChildrenCount;
+ for (int i = 0; i < childCount; i++) {
+ View child = children[i];
+ child.resetAccessibilityStateChanged();
+ }
+ }
+
+ /**
* {@inheritDoc}
*/
@Override
@@ -3400,6 +3474,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
clearChildFocus(view);
ensureInputFocusOnFirstFocusable();
}
+
+ if (view.isAccessibilityFocused()) {
+ view.clearAccessibilityFocus();
+ }
}
/**
@@ -4368,6 +4446,52 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
+ * Returns the basis of alignment during the layout of this view group:
+ * either {@link #COMPONENT_BOUNDS} or {@link #LAYOUT_BOUNDS}.
+ *
+ * @return whether or not this view group should use the component or layout bounds during
+ * layout operations
+ *
+ * @see #setLayoutMode(int)
+ */
+ public int getLayoutMode() {
+ if (mLayoutMode == UNDEFINED_LAYOUT_MODE) {
+ ViewParent parent = getParent();
+ if (parent instanceof ViewGroup) {
+ ViewGroup viewGroup = (ViewGroup) parent;
+ return viewGroup.getLayoutMode();
+ } else {
+ int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
+ boolean preJellyBean = targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN;
+ return preJellyBean ? COMPONENT_BOUNDS : LAYOUT_BOUNDS;
+ }
+
+ }
+ return mLayoutMode;
+ }
+
+ /**
+ * Sets the basis of alignment during alignment of this view group.
+ * Valid values are either {@link #COMPONENT_BOUNDS} or {@link #LAYOUT_BOUNDS}.
+ * <p>
+ * The default is to query the property of the parent if this view group has a parent.
+ * If this ViewGroup is the root of the view hierarchy the default
+ * value is {@link #LAYOUT_BOUNDS} for target SDK's greater than JellyBean,
+ * {@link #LAYOUT_BOUNDS} otherwise.
+ *
+ * @return whether or not this view group should use the component or layout bounds during
+ * layout operations
+ *
+ * @see #getLayoutMode()
+ */
+ public void setLayoutMode(int layoutMode) {
+ if (mLayoutMode != layoutMode) {
+ mLayoutMode = layoutMode;
+ requestLayout();
+ }
+ }
+
+ /**
* Returns a new set of layout parameters based on the supplied attributes set.
*
* @param attrs the attributes to build the layout parameters from
@@ -5622,4 +5746,218 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
}
+
+ /**
+ * Pooled class that orderes the children of a ViewGroup from start
+ * to end based on how they are laid out and the layout direction.
+ */
+ static class ChildListForAccessibility {
+
+ private static final int MAX_POOL_SIZE = 32;
+
+ private static final Object sPoolLock = new Object();
+
+ private static ChildListForAccessibility sPool;
+
+ private static int sPoolSize;
+
+ private boolean mIsPooled;
+
+ private ChildListForAccessibility mNext;
+
+ private final ArrayList<View> mChildren = new ArrayList<View>();
+
+ private final ArrayList<ViewLocationHolder> mHolders = new ArrayList<ViewLocationHolder>();
+
+ public static ChildListForAccessibility obtain(ViewGroup parent, boolean sort) {
+ ChildListForAccessibility list = null;
+ synchronized (sPoolLock) {
+ if (sPool != null) {
+ list = sPool;
+ sPool = list.mNext;
+ list.mNext = null;
+ list.mIsPooled = false;
+ sPoolSize--;
+ } else {
+ list = new ChildListForAccessibility();
+ }
+ list.init(parent, sort);
+ return list;
+ }
+ }
+
+ public void recycle() {
+ if (mIsPooled) {
+ throw new IllegalStateException("Instance already recycled.");
+ }
+ clear();
+ if (sPoolSize < MAX_POOL_SIZE) {
+ mNext = sPool;
+ mIsPooled = true;
+ sPool = this;
+ sPoolSize++;
+ }
+ }
+
+ public int getChildCount() {
+ return mChildren.size();
+ }
+
+ public View getChildAt(int index) {
+ return mChildren.get(index);
+ }
+
+ public int getChildIndex(View child) {
+ return mChildren.indexOf(child);
+ }
+
+ private void init(ViewGroup parent, boolean sort) {
+ ArrayList<View> children = mChildren;
+ final int childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = parent.getChildAt(i);
+ children.add(child);
+ }
+ if (sort) {
+ ArrayList<ViewLocationHolder> holders = mHolders;
+ for (int i = 0; i < childCount; i++) {
+ View child = children.get(i);
+ ViewLocationHolder holder = ViewLocationHolder.obtain(parent, child);
+ holders.add(holder);
+ }
+ Collections.sort(holders);
+ for (int i = 0; i < childCount; i++) {
+ ViewLocationHolder holder = holders.get(i);
+ children.set(i, holder.mView);
+ holder.recycle();
+ }
+ holders.clear();
+ }
+ }
+
+ private void clear() {
+ mChildren.clear();
+ }
+ }
+
+ /**
+ * Pooled class that holds a View and its location with respect to
+ * a specified root. This enables sorting of views based on their
+ * coordinates without recomputing the position relative to the root
+ * on every comparison.
+ */
+ static class ViewLocationHolder implements Comparable<ViewLocationHolder> {
+
+ private static final int MAX_POOL_SIZE = 32;
+
+ private static final Object sPoolLock = new Object();
+
+ private static ViewLocationHolder sPool;
+
+ private static int sPoolSize;
+
+ private boolean mIsPooled;
+
+ private ViewLocationHolder mNext;
+
+ private final Rect mLocation = new Rect();
+
+ public View mView;
+
+ private int mLayoutDirection;
+
+ public static ViewLocationHolder obtain(ViewGroup root, View view) {
+ ViewLocationHolder holder = null;
+ synchronized (sPoolLock) {
+ if (sPool != null) {
+ holder = sPool;
+ sPool = holder.mNext;
+ holder.mNext = null;
+ holder.mIsPooled = false;
+ sPoolSize--;
+ } else {
+ holder = new ViewLocationHolder();
+ }
+ holder.init(root, view);
+ return holder;
+ }
+ }
+
+ public void recycle() {
+ if (mIsPooled) {
+ throw new IllegalStateException("Instance already recycled.");
+ }
+ clear();
+ if (sPoolSize < MAX_POOL_SIZE) {
+ mNext = sPool;
+ mIsPooled = true;
+ sPool = this;
+ sPoolSize++;
+ }
+ }
+
+ @Override
+ public int compareTo(ViewLocationHolder another) {
+ // This instance is greater than an invalid argument.
+ if (another == null) {
+ return 1;
+ }
+ if (getClass() != another.getClass()) {
+ return 1;
+ }
+ // First is above second.
+ if (mLocation.bottom - another.mLocation.top <= 0) {
+ return -1;
+ }
+ // First is below second.
+ if (mLocation.top - another.mLocation.bottom >= 0) {
+ return 1;
+ }
+ // LTR
+ if (mLayoutDirection == LAYOUT_DIRECTION_LTR) {
+ final int leftDifference = mLocation.left - another.mLocation.left;
+ // First more to the left than second.
+ if (leftDifference != 0) {
+ return leftDifference;
+ }
+ } else { // RTL
+ final int rightDifference = mLocation.right - another.mLocation.right;
+ // First more to the right than second.
+ if (rightDifference != 0) {
+ return -rightDifference;
+ }
+ }
+ // Break tie by top.
+ final int topDiference = mLocation.top - another.mLocation.top;
+ if (topDiference != 0) {
+ return topDiference;
+ }
+ // Break tie by height.
+ final int heightDiference = mLocation.height() - another.mLocation.height();
+ if (heightDiference != 0) {
+ return -heightDiference;
+ }
+ // Break tie by width.
+ final int widthDiference = mLocation.width() - another.mLocation.width();
+ if (widthDiference != 0) {
+ return -widthDiference;
+ }
+ // Return nondeterministically one of them since we do
+ // not want to ignore any views.
+ return 1;
+ }
+
+ private void init(ViewGroup root, View view) {
+ Rect viewLocation = mLocation;
+ view.getDrawingRect(viewLocation);
+ root.offsetDescendantRectToMyCoords(view, viewLocation);
+ mView = view;
+ mLayoutDirection = root.getResolvedLayoutDirection();
+ }
+
+ private void clear() {
+ mView = null;
+ mLocation.set(0, 0, 0, 0);
+ }
+ }
}
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index 75e9151..ddff91d 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -277,4 +277,22 @@ public interface ViewParent {
* View.fitSystemWindows(Rect)} be performed.
*/
public void requestFitSystemWindows();
+
+ /**
+ * Gets the parent of a given View for accessibility. Since some Views are not
+ * exposed to the accessibility layer the parent for accessibility is not
+ * necessarily the direct parent of the View, rather it is a predecessor.
+ *
+ * @return The parent or <code>null</code> if no such is found.
+ */
+ public ViewParent getParentForAccessibility();
+
+ /**
+ * A child notifies its parent that its state for accessibility has changed.
+ * That is some of the child properties reported to accessibility services has
+ * changed, hence the interested services have to be notified for the new state.
+ *
+ * @hide
+ */
+ public void childAccessibilityStateChanged(View child);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2e3ff38..b4554d5 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -37,6 +37,7 @@ import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Region;
+import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.os.Binder;
import android.os.Bundle;
@@ -56,17 +57,11 @@ import android.util.AndroidRuntimeException;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
-import android.util.Pool;
-import android.util.Poolable;
-import android.util.PoolableManager;
-import android.util.Pools;
import android.util.Slog;
-import android.util.SparseLongArray;
import android.util.TypedValue;
import android.view.View.AttachInfo;
import android.view.View.MeasureSpec;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -79,6 +74,7 @@ import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.Scroller;
+import com.android.internal.R;
import com.android.internal.policy.PolicyManager;
import com.android.internal.view.BaseSurfaceHolder;
import com.android.internal.view.IInputMethodCallback;
@@ -89,9 +85,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.HashSet;
/**
* The top of a view hierarchy, implementing the needed protocol between View
@@ -181,6 +175,10 @@ public final class ViewRootImpl implements ViewParent,
View mFocusedView;
View mRealFocusedView; // this is not set to null in touch mode
View mOldFocusedView;
+
+ View mAccessibilityFocusedHost;
+ AccessibilityNodeInfo mAccessibilityFocusedVirtualView;
+
int mViewVisibility;
boolean mAppVisible = true;
int mOrigWindowType = -1;
@@ -321,7 +319,7 @@ public final class ViewRootImpl implements ViewParent,
SendWindowContentChangedAccessibilityEvent mSendWindowContentChangedAccessibilityEvent;
- AccessibilityNodePrefetcher mAccessibilityNodePrefetcher;
+ HashSet<View> mTempHashSet;
private final int mDensity;
@@ -630,6 +628,10 @@ public final class ViewRootImpl implements ViewParent,
if (mAccessibilityManager.isEnabled()) {
mAccessibilityInteractionConnectionManager.ensureConnection();
}
+
+ if (view.getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
}
}
}
@@ -1418,6 +1420,8 @@ public final class ViewRootImpl implements ViewParent,
mView.draw(layerCanvas);
+ drawAccessibilityFocusedDrawableIfNeeded(layerCanvas);
+
mResizeBufferStartTime = SystemClock.uptimeMillis();
mResizeBufferDuration = mView.getResources().getInteger(
com.android.internal.R.integer.config_mediumAnimTime);
@@ -1712,7 +1716,7 @@ public final class ViewRootImpl implements ViewParent,
attachInfo.mTreeObserver.dispatchOnGlobalLayout();
if (AccessibilityManager.getInstance(host.mContext).isEnabled()) {
- postSendWindowContentChangedCallback();
+ postSendWindowContentChangedCallback(mView);
}
}
@@ -1880,6 +1884,7 @@ public final class ViewRootImpl implements ViewParent,
mResizePaint.setAlpha(mResizeAlpha);
canvas.drawHardwareLayer(mResizeBuffer, 0.0f, mHardwareYOffset, mResizePaint);
}
+ drawAccessibilityFocusedDrawableIfNeeded(canvas);
}
/**
@@ -2234,6 +2239,8 @@ public final class ViewRootImpl implements ViewParent,
mView.draw(canvas);
+ drawAccessibilityFocusedDrawableIfNeeded(canvas);
+
if (ViewDebug.DEBUG_LATENCY) {
long now = System.nanoTime();
Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- draw() took "
@@ -2274,6 +2281,64 @@ public final class ViewRootImpl implements ViewParent,
return true;
}
+ /**
+ * We want to draw a highlight around the current accessibility focused.
+ * Since adding a style for all possible view is not a viable option we
+ * have this specialized drawing method.
+ *
+ * Note: We are doing this here to be able to draw the highlight for
+ * virtual views in addition to real ones.
+ *
+ * @param canvas The canvas on which to draw.
+ */
+ private void drawAccessibilityFocusedDrawableIfNeeded(Canvas canvas) {
+ if (!AccessibilityManager.getInstance(mView.mContext).isEnabled()) {
+ return;
+ }
+ if (mAccessibilityFocusedHost == null || mAccessibilityFocusedHost.mAttachInfo == null) {
+ return;
+ }
+ Drawable drawable = getAccessibilityFocusedDrawable();
+ if (drawable == null) {
+ return;
+ }
+ AccessibilityNodeProvider provider =
+ mAccessibilityFocusedHost.getAccessibilityNodeProvider();
+ Rect bounds = mView.mAttachInfo.mTmpInvalRect;
+ if (provider == null) {
+ mAccessibilityFocusedHost.getDrawingRect(bounds);
+ if (mView instanceof ViewGroup) {
+ ViewGroup viewGroup = (ViewGroup) mView;
+ viewGroup.offsetDescendantRectToMyCoords(mAccessibilityFocusedHost, bounds);
+ }
+ } else {
+ if (mAccessibilityFocusedVirtualView == null) {
+ mAccessibilityFocusedVirtualView = provider.findAccessibilitiyFocus(View.NO_ID);
+ }
+ mAccessibilityFocusedVirtualView.getBoundsInScreen(bounds);
+ bounds.offset(-mAttachInfo.mWindowLeft, -mAttachInfo.mWindowTop);
+ }
+ drawable.setBounds(bounds);
+ drawable.draw(canvas);
+ }
+
+ private Drawable getAccessibilityFocusedDrawable() {
+ if (mAttachInfo != null) {
+ // Lazily load the accessibility focus drawable.
+ if (mAttachInfo.mAccessibilityFocusDrawable == null) {
+ TypedValue value = new TypedValue();
+ final boolean resolved = mView.mContext.getTheme().resolveAttribute(
+ R.attr.accessibilityFocusedDrawable, value, true);
+ if (resolved) {
+ mAttachInfo.mAccessibilityFocusDrawable =
+ mView.mContext.getResources().getDrawable(value.resourceId);
+ }
+ }
+ return mAttachInfo.mAccessibilityFocusDrawable;
+ }
+ return null;
+ }
+
void invalidateDisplayLists() {
final ArrayList<DisplayList> displayLists = mDisplayLists;
final int count = displayLists.size();
@@ -2407,6 +2472,14 @@ public final class ViewRootImpl implements ViewParent,
return handled;
}
+ void setAccessibilityFocusedHost(View host) {
+ if (mAccessibilityFocusedHost != null && mAccessibilityFocusedVirtualView == null) {
+ mAccessibilityFocusedHost.clearAccessibilityFocusNoCallbacks();
+ }
+ mAccessibilityFocusedHost = host;
+ mAccessibilityFocusedVirtualView = null;
+ }
+
public void requestChildFocus(View child, View focused) {
checkThread();
@@ -2437,9 +2510,13 @@ public final class ViewRootImpl implements ViewParent,
mFocusedView = mRealFocusedView = null;
}
+ @Override
+ public ViewParent getParentForAccessibility() {
+ return null;
+ }
+
public void focusableViewAvailable(View v) {
checkThread();
-
if (mView != null) {
if (!mView.hasFocus()) {
v.requestFocus();
@@ -2547,7 +2624,7 @@ public final class ViewRootImpl implements ViewParent,
/**
* Return true if child is an ancestor of parent, (or equal to the parent).
*/
- private static boolean isViewDescendantOf(View child, View parent) {
+ static boolean isViewDescendantOf(View child, View parent) {
if (child == parent) {
return true;
}
@@ -2585,13 +2662,9 @@ public final class ViewRootImpl implements ViewParent,
private final static int MSG_DISPATCH_DRAG_LOCATION_EVENT = 16;
private final static int MSG_DISPATCH_SYSTEM_UI_VISIBILITY = 17;
private final static int MSG_UPDATE_CONFIGURATION = 18;
- private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 19;
- private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 20;
- private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 21;
- private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 22;
- private final static int MSG_PROCESS_INPUT_EVENTS = 23;
- private final static int MSG_DISPATCH_SCREEN_STATE = 24;
- private final static int MSG_INVALIDATE_DISPLAY_LIST = 25;
+ private final static int MSG_PROCESS_INPUT_EVENTS = 19;
+ private final static int MSG_DISPATCH_SCREEN_STATE = 20;
+ private final static int MSG_INVALIDATE_DISPLAY_LIST = 21;
final class ViewRootHandler extends Handler {
@Override
@@ -2633,14 +2706,6 @@ public final class ViewRootImpl implements ViewParent,
return "MSG_DISPATCH_SYSTEM_UI_VISIBILITY";
case MSG_UPDATE_CONFIGURATION:
return "MSG_UPDATE_CONFIGURATION";
- case MSG_PERFORM_ACCESSIBILITY_ACTION:
- return "MSG_PERFORM_ACCESSIBILITY_ACTION";
- case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID:
- return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID";
- case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID:
- return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID";
- case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT:
- return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT";
case MSG_PROCESS_INPUT_EVENTS:
return "MSG_PROCESS_INPUT_EVENTS";
case MSG_DISPATCH_SCREEN_STATE:
@@ -2770,8 +2835,28 @@ public final class ViewRootImpl implements ViewParent,
mHasHadWindowFocus = true;
}
- if (hasWindowFocus && mView != null && mAccessibilityManager.isEnabled()) {
- mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ if (mView != null && mAccessibilityManager.isEnabled()) {
+ if (hasWindowFocus) {
+ mView.sendAccessibilityEvent(
+ AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ // Give accessibility focus to the view that has input
+ // focus if such, otherwise to the first one.
+ if (mView instanceof ViewGroup) {
+ ViewGroup viewGroup = (ViewGroup) mView;
+ View focused = viewGroup.findFocus();
+ if (focused != null) {
+ focused.requestAccessibilityFocus();
+ }
+ }
+ // There is no accessibility focus, despite our effort
+ // above, now just give it to the first view.
+ if (mAccessibilityFocusedHost == null) {
+ mView.requestAccessibilityFocus();
+ }
+ } else {
+ // Clear accessibility focus when the window loses input focus.
+ setAccessibilityFocusedHost(null);
+ }
}
}
} break;
@@ -2828,30 +2913,6 @@ public final class ViewRootImpl implements ViewParent,
}
updateConfiguration(config, false);
} break;
- case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
- if (mView != null) {
- getAccessibilityInteractionController()
- .findAccessibilityNodeInfoByAccessibilityIdUiThread(msg);
- }
- } break;
- case MSG_PERFORM_ACCESSIBILITY_ACTION: {
- if (mView != null) {
- getAccessibilityInteractionController()
- .perfromAccessibilityActionUiThread(msg);
- }
- } break;
- case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID: {
- if (mView != null) {
- getAccessibilityInteractionController()
- .findAccessibilityNodeInfoByViewIdUiThread(msg);
- }
- } break;
- case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: {
- if (mView != null) {
- getAccessibilityInteractionController()
- .findAccessibilityNodeInfosByTextUiThread(msg);
- }
- } break;
case MSG_DISPATCH_SCREEN_STATE: {
if (mView != null) {
handleScreenStateChange(msg.arg1 == 1);
@@ -2917,28 +2978,25 @@ public final class ViewRootImpl implements ViewParent,
// set yet.
final View focused = mView.findFocus();
if (focused != null && !focused.isFocusableInTouchMode()) {
-
final ViewGroup ancestorToTakeFocus =
findAncestorToTakeFocusInTouchMode(focused);
if (ancestorToTakeFocus != null) {
// there is an ancestor that wants focus after its descendants that
// is focusable in touch mode.. give it focus
return ancestorToTakeFocus.requestFocus();
- } else {
- // nothing appropriate to have focus in touch mode, clear it out
- mView.unFocus();
- mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(focused, null);
- mFocusedView = null;
- mOldFocusedView = null;
- return true;
}
}
+ // nothing appropriate to have focus in touch mode, clear it out
+ mView.unFocus();
+ mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(focused, null);
+ mFocusedView = null;
+ mOldFocusedView = null;
+ return true;
}
}
return false;
}
-
/**
* Find an ancestor of focused that wants focus after its descendants and is
* focusable in touch mode.
@@ -2964,25 +3022,45 @@ public final class ViewRootImpl implements ViewParent,
private boolean leaveTouchMode() {
if (mView != null) {
+ boolean inputFocusValid = false;
if (mView.hasFocus()) {
// i learned the hard way to not trust mFocusedView :)
mFocusedView = mView.findFocus();
if (!(mFocusedView instanceof ViewGroup)) {
// some view has focus, let it keep it
- return false;
- } else if (((ViewGroup)mFocusedView).getDescendantFocusability() !=
+ inputFocusValid = true;
+ } else if (((ViewGroup) mFocusedView).getDescendantFocusability() !=
ViewGroup.FOCUS_AFTER_DESCENDANTS) {
// some view group has focus, and doesn't prefer its children
// over itself for focus, so let them keep it.
- return false;
+ inputFocusValid = true;
}
}
-
- // find the best view to give focus to in this brave new non-touch-mode
- // world
- final View focused = focusSearch(null, View.FOCUS_DOWN);
- if (focused != null) {
- return focused.requestFocus(View.FOCUS_DOWN);
+ // In accessibility mode we always have a view that has the
+ // accessibility focus and input focus follows it, i.e. we
+ // try to give input focus to the accessibility focused view.
+ if (!AccessibilityManager.getInstance(mView.mContext).isEnabled()) {
+ // If the current input focus is not valid, find the best view to give
+ // focus to in this brave new non-touch-mode world.
+ if (!inputFocusValid) {
+ final View focused = focusSearch(null, View.FOCUS_DOWN);
+ if (focused != null) {
+ return focused.requestFocus(View.FOCUS_DOWN);
+ }
+ }
+ } else {
+ // If the current input focus is not valid clear it but do not
+ // give it to another view since the accessibility focus is
+ // leading now and the input one follows.
+ if (!inputFocusValid) {
+ if (mFocusedView != null) {
+ mView.unFocus();
+ mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mFocusedView, null);
+ mFocusedView = null;
+ mOldFocusedView = null;
+ return true;
+ }
+ }
}
}
return false;
@@ -3487,37 +3565,36 @@ public final class ViewRootImpl implements ViewParent,
if (event.getAction() == KeyEvent.ACTION_DOWN) {
int direction = 0;
switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_DPAD_LEFT:
- if (event.hasNoModifiers()) {
- direction = View.FOCUS_LEFT;
- }
- break;
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (event.hasNoModifiers()) {
- direction = View.FOCUS_RIGHT;
- }
- break;
- case KeyEvent.KEYCODE_DPAD_UP:
- if (event.hasNoModifiers()) {
- direction = View.FOCUS_UP;
- }
- break;
- case KeyEvent.KEYCODE_DPAD_DOWN:
- if (event.hasNoModifiers()) {
- direction = View.FOCUS_DOWN;
- }
- break;
- case KeyEvent.KEYCODE_TAB:
- if (event.hasNoModifiers()) {
- direction = View.FOCUS_FORWARD;
- } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
- direction = View.FOCUS_BACKWARD;
- }
- break;
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (event.hasNoModifiers()) {
+ direction = View.FOCUS_LEFT;
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (event.hasNoModifiers()) {
+ direction = View.FOCUS_RIGHT;
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ if (event.hasNoModifiers()) {
+ direction = View.FOCUS_UP;
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (event.hasNoModifiers()) {
+ direction = View.FOCUS_DOWN;
+ }
+ break;
+ case KeyEvent.KEYCODE_TAB:
+ if (event.hasNoModifiers()) {
+ direction = View.FOCUS_FORWARD;
+ } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
+ direction = View.FOCUS_BACKWARD;
+ }
+ break;
}
-
if (direction != 0) {
- View focused = mView != null ? mView.findFocus() : null;
+ View focused = mView.findFocus();
if (focused != null) {
View v = focused.focusSearch(direction);
if (v != null && v != focused) {
@@ -3532,8 +3609,8 @@ public final class ViewRootImpl implements ViewParent,
v, mTempRect);
}
if (v.requestFocus(direction, mTempRect)) {
- playSoundEffect(
- SoundEffectConstants.getContantForFocusDirection(direction));
+ playSoundEffect(SoundEffectConstants
+ .getContantForFocusDirection(direction));
finishInputEvent(q, true);
return;
}
@@ -3683,22 +3760,11 @@ public final class ViewRootImpl implements ViewParent,
+ " called when there is no mView");
}
if (mAccessibilityInteractionController == null) {
- mAccessibilityInteractionController = new AccessibilityInteractionController();
+ mAccessibilityInteractionController = new AccessibilityInteractionController(this);
}
return mAccessibilityInteractionController;
}
- public AccessibilityNodePrefetcher getAccessibilityNodePrefetcher() {
- if (mView == null) {
- throw new IllegalStateException("getAccessibilityNodePrefetcher"
- + " called when there is no mView");
- }
- if (mAccessibilityNodePrefetcher == null) {
- mAccessibilityNodePrefetcher = new AccessibilityNodePrefetcher();
- }
- return mAccessibilityNodePrefetcher;
- }
-
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
@@ -4375,15 +4441,19 @@ public final class ViewRootImpl implements ViewParent,
* This event is send at most once every
* {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}.
*/
- private void postSendWindowContentChangedCallback() {
+ private void postSendWindowContentChangedCallback(View source) {
if (mSendWindowContentChangedAccessibilityEvent == null) {
mSendWindowContentChangedAccessibilityEvent =
new SendWindowContentChangedAccessibilityEvent();
}
- if (!mSendWindowContentChangedAccessibilityEvent.mIsPending) {
- mSendWindowContentChangedAccessibilityEvent.mIsPending = true;
+ View oldSource = mSendWindowContentChangedAccessibilityEvent.mSource;
+ if (oldSource == null) {
+ mSendWindowContentChangedAccessibilityEvent.mSource = source;
mHandler.postDelayed(mSendWindowContentChangedAccessibilityEvent,
ViewConfiguration.getSendRecurringAccessibilityEventsInterval());
+ } else {
+ View newSource = getCommonPredecessor(oldSource, source);
+ mSendWindowContentChangedAccessibilityEvent.mSource = newSource;
}
}
@@ -4419,6 +4489,46 @@ public final class ViewRootImpl implements ViewParent,
return true;
}
+ @Override
+ public void childAccessibilityStateChanged(View child) {
+ postSendWindowContentChangedCallback(child);
+ }
+
+ private View getCommonPredecessor(View first, View second) {
+ if (mAttachInfo != null) {
+ if (mTempHashSet == null) {
+ mTempHashSet = new HashSet<View>();
+ }
+ HashSet<View> seen = mTempHashSet;
+ seen.clear();
+ View firstCurrent = first;
+ while (firstCurrent != null) {
+ seen.add(firstCurrent);
+ ViewParent firstCurrentParent = firstCurrent.mParent;
+ if (firstCurrentParent instanceof View) {
+ firstCurrent = (View) firstCurrentParent;
+ } else {
+ firstCurrent = null;
+ }
+ }
+ View secondCurrent = second;
+ while (secondCurrent != null) {
+ if (seen.contains(secondCurrent)) {
+ seen.clear();
+ return secondCurrent;
+ }
+ ViewParent secondCurrentParent = secondCurrent.mParent;
+ if (secondCurrentParent instanceof View) {
+ secondCurrent = (View) secondCurrentParent;
+ } else {
+ secondCurrent = null;
+ }
+ }
+ seen.clear();
+ }
+ return null;
+ }
+
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
@@ -4953,6 +5063,7 @@ public final class ViewRootImpl implements ViewParent,
}
} else {
ensureNoConnection();
+ setAccessibilityFocusedHost(null);
}
}
@@ -4991,14 +5102,15 @@ public final class ViewRootImpl implements ViewParent,
mViewRootImpl = new WeakReference<ViewRootImpl>(viewRootImpl);
}
+ @Override
public void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId,
int interactionId, IAccessibilityInteractionConnectionCallback callback,
- int prefetchFlags, int interrogatingPid, long interrogatingTid) {
+ int flags, int interrogatingPid, long interrogatingTid) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.findAccessibilityNodeInfoByAccessibilityIdClientThread(accessibilityNodeId,
- interactionId, callback, prefetchFlags, interrogatingPid, interrogatingTid);
+ interactionId, callback, flags, interrogatingPid, interrogatingTid);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
@@ -5009,16 +5121,17 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ @Override
public void performAccessibilityAction(long accessibilityNodeId, int action,
int interactionId, IAccessibilityInteractionConnectionCallback callback,
- int interogatingPid, long interrogatingTid) {
+ int flags, int interogatingPid, long interrogatingTid) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.performAccessibilityActionClientThread(accessibilityNodeId, action,
- interactionId, callback, interogatingPid, interrogatingTid);
+ interactionId, callback, flags, interogatingPid, interrogatingTid);
} else {
- // We cannot make the call and notify the caller so it does not
+ // We cannot make the call and notify the caller so it does not wait.
try {
callback.setPerformAccessibilityActionResult(false, interactionId);
} catch (RemoteException re) {
@@ -5027,16 +5140,17 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ @Override
public void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int viewId,
int interactionId, IAccessibilityInteractionConnectionCallback callback,
- int interrogatingPid, long interrogatingTid) {
+ int flags, int interrogatingPid, long interrogatingTid) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.findAccessibilityNodeInfoByViewIdClientThread(accessibilityNodeId, viewId,
- interactionId, callback, interrogatingPid, interrogatingTid);
+ interactionId, callback, flags, interrogatingPid, interrogatingTid);
} else {
- // We cannot make the call and notify the caller so it does not
+ // We cannot make the call and notify the caller so it does not wait.
try {
callback.setFindAccessibilityNodeInfoResult(null, interactionId);
} catch (RemoteException re) {
@@ -5045,16 +5159,17 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ @Override
public void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text,
int interactionId, IAccessibilityInteractionConnectionCallback callback,
- int interrogatingPid, long interrogatingTid) {
+ int flags, int interrogatingPid, long interrogatingTid) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.findAccessibilityNodeInfosByTextClientThread(accessibilityNodeId, text,
- interactionId, callback, interrogatingPid, interrogatingTid);
+ interactionId, callback, flags, interrogatingPid, interrogatingTid);
} else {
- // We cannot make the call and notify the caller so it does not
+ // We cannot make the call and notify the caller so it does not wait.
try {
callback.setFindAccessibilityNodeInfosResult(null, interactionId);
} catch (RemoteException re) {
@@ -5062,606 +5177,54 @@ public final class ViewRootImpl implements ViewParent,
}
}
}
- }
-
- /**
- * Computes whether a view is visible on the screen.
- *
- * @param view The view to check.
- * @return Whether the view is visible on the screen.
- */
- private boolean isDisplayedOnScreen(View view) {
- return (view.mAttachInfo != null
- && view.mAttachInfo.mWindowVisibility == View.VISIBLE
- && view.getVisibility() == View.VISIBLE
- && view.getGlobalVisibleRect(mTempRect));
- }
-
- /**
- * Class for managing accessibility interactions initiated from the system
- * and targeting the view hierarchy. A *ClientThread method is to be
- * called from the interaction connection this ViewAncestor gives the
- * system to talk to it and a corresponding *UiThread method that is executed
- * on the UI thread.
- */
- final class AccessibilityInteractionController {
- private static final int POOL_SIZE = 5;
-
- private ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
- new ArrayList<AccessibilityNodeInfo>();
-
- // Reusable poolable arguments for interacting with the view hierarchy
- // to fit more arguments than Message and to avoid sharing objects between
- // two messages since several threads can send messages concurrently.
- private final Pool<SomeArgs> mPool = Pools.synchronizedPool(Pools.finitePool(
- new PoolableManager<SomeArgs>() {
- public SomeArgs newInstance() {
- return new SomeArgs();
- }
-
- public void onAcquired(SomeArgs info) {
- /* do nothing */
- }
-
- public void onReleased(SomeArgs info) {
- info.clear();
- }
- }, POOL_SIZE)
- );
-
- public class SomeArgs implements Poolable<SomeArgs> {
- private SomeArgs mNext;
- private boolean mIsPooled;
-
- public Object arg1;
- public Object arg2;
- public int argi1;
- public int argi2;
- public int argi3;
-
- public SomeArgs getNextPoolable() {
- return mNext;
- }
-
- public boolean isPooled() {
- return mIsPooled;
- }
-
- public void setNextPoolable(SomeArgs args) {
- mNext = args;
- }
-
- public void setPooled(boolean isPooled) {
- mIsPooled = isPooled;
- }
-
- private void clear() {
- arg1 = null;
- arg2 = null;
- argi1 = 0;
- argi2 = 0;
- argi3 = 0;
- }
- }
- public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
- long accessibilityNodeId, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int prefetchFlags,
+ @Override
+ public void findFocus(long accessibilityNodeId, int interactionId, int focusType,
+ IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid) {
- Message message = mHandler.obtainMessage();
- message.what = MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID;
- message.arg1 = prefetchFlags;
- SomeArgs args = mPool.acquire();
- args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
- args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
- args.argi3 = interactionId;
- args.arg1 = callback;
- message.obj = args;
- // If the interrogation is performed by the same thread as the main UI
- // thread in this process, set the message as a static reference so
- // after this call completes the same thread but in the interrogating
- // client can handle the message to generate the result.
- if (interrogatingPid == Process.myPid()
- && interrogatingTid == Looper.getMainLooper().getThread().getId()) {
- AccessibilityInteractionClient.getInstanceForThread(
- interrogatingTid).setSameThreadMessage(message);
+ ViewRootImpl viewRootImpl = mViewRootImpl.get();
+ if (viewRootImpl != null && viewRootImpl.mView != null) {
+ viewRootImpl.getAccessibilityInteractionController()
+ .findFocusClientThread(accessibilityNodeId, interactionId, focusType,
+ callback, flags, interrogatingPid, interrogatingTid);
} else {
- mHandler.sendMessage(message);
- }
- }
-
- public void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
- final int prefetchFlags = message.arg1;
- SomeArgs args = (SomeArgs) message.obj;
- final int accessibilityViewId = args.argi1;
- final int virtualDescendantId = args.argi2;
- final int interactionId = args.argi3;
- final IAccessibilityInteractionConnectionCallback callback =
- (IAccessibilityInteractionConnectionCallback) args.arg1;
- mPool.release(args);
- List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
- infos.clear();
- try {
- View target = null;
- if (accessibilityViewId == AccessibilityNodeInfo.UNDEFINED) {
- target = ViewRootImpl.this.mView;
- } else {
- target = findViewByAccessibilityId(accessibilityViewId);
- }
- if (target != null && isDisplayedOnScreen(target)) {
- getAccessibilityNodePrefetcher().prefetchAccessibilityNodeInfos(target,
- virtualDescendantId, prefetchFlags, infos);
- }
- } finally {
+ // We cannot make the call and notify the caller so it does not wait.
try {
- callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
- infos.clear();
+ callback.setFindAccessibilityNodeInfoResult(null, interactionId);
} catch (RemoteException re) {
- /* ignore - the other side will time out */
+ /* best effort - ignore */
}
}
}
- public void findAccessibilityNodeInfoByViewIdClientThread(long accessibilityNodeId,
- int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
+ @Override
+ public void focusSearch(long accessibilityNodeId, int interactionId, int direction,
+ IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid) {
- Message message = mHandler.obtainMessage();
- message.what = MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID;
- message.arg1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
- SomeArgs args = mPool.acquire();
- args.argi1 = viewId;
- args.argi2 = interactionId;
- args.arg1 = callback;
- message.obj = args;
- // If the interrogation is performed by the same thread as the main UI
- // thread in this process, set the message as a static reference so
- // after this call completes the same thread but in the interrogating
- // client can handle the message to generate the result.
- if (interrogatingPid == Process.myPid()
- && interrogatingTid == Looper.getMainLooper().getThread().getId()) {
- AccessibilityInteractionClient.getInstanceForThread(
- interrogatingTid).setSameThreadMessage(message);
- } else {
- mHandler.sendMessage(message);
- }
- }
-
- public void findAccessibilityNodeInfoByViewIdUiThread(Message message) {
- final int accessibilityViewId = message.arg1;
- SomeArgs args = (SomeArgs) message.obj;
- final int viewId = args.argi1;
- final int interactionId = args.argi2;
- final IAccessibilityInteractionConnectionCallback callback =
- (IAccessibilityInteractionConnectionCallback) args.arg1;
- mPool.release(args);
- AccessibilityNodeInfo info = null;
- try {
- View root = null;
- if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
- root = findViewByAccessibilityId(accessibilityViewId);
- } else {
- root = ViewRootImpl.this.mView;
- }
- if (root != null) {
- View target = root.findViewById(viewId);
- if (target != null && isDisplayedOnScreen(target)) {
- info = target.createAccessibilityNodeInfo();
- }
- }
- } finally {
- try {
- callback.setFindAccessibilityNodeInfoResult(info, interactionId);
- } catch (RemoteException re) {
- /* ignore - the other side will time out */
- }
- }
- }
-
- public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId,
- String text, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int interrogatingPid,
- long interrogatingTid) {
- Message message = mHandler.obtainMessage();
- message.what = MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT;
- SomeArgs args = mPool.acquire();
- args.arg1 = text;
- args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
- args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
- args.argi3 = interactionId;
- args.arg2 = callback;
- message.obj = args;
- // If the interrogation is performed by the same thread as the main UI
- // thread in this process, set the message as a static reference so
- // after this call completes the same thread but in the interrogating
- // client can handle the message to generate the result.
- if (interrogatingPid == Process.myPid()
- && interrogatingTid == Looper.getMainLooper().getThread().getId()) {
- AccessibilityInteractionClient.getInstanceForThread(
- interrogatingTid).setSameThreadMessage(message);
- } else {
- mHandler.sendMessage(message);
- }
- }
-
- public void findAccessibilityNodeInfosByTextUiThread(Message message) {
- SomeArgs args = (SomeArgs) message.obj;
- final String text = (String) args.arg1;
- final int accessibilityViewId = args.argi1;
- final int virtualDescendantId = args.argi2;
- final int interactionId = args.argi3;
- final IAccessibilityInteractionConnectionCallback callback =
- (IAccessibilityInteractionConnectionCallback) args.arg2;
- mPool.release(args);
- List<AccessibilityNodeInfo> infos = null;
- try {
- View target;
- if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
- target = findViewByAccessibilityId(accessibilityViewId);
- } else {
- target = ViewRootImpl.this.mView;
- }
- if (target != null && isDisplayedOnScreen(target)) {
- AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
- if (provider != null) {
- infos = provider.findAccessibilityNodeInfosByText(text,
- virtualDescendantId);
- } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED) {
- ArrayList<View> foundViews = mAttachInfo.mFocusablesTempList;
- foundViews.clear();
- target.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT
- | View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION
- | View.FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS);
- if (!foundViews.isEmpty()) {
- infos = mTempAccessibilityNodeInfoList;
- infos.clear();
- final int viewCount = foundViews.size();
- for (int i = 0; i < viewCount; i++) {
- View foundView = foundViews.get(i);
- if (isDisplayedOnScreen(foundView)) {
- provider = foundView.getAccessibilityNodeProvider();
- if (provider != null) {
- List<AccessibilityNodeInfo> infosFromProvider =
- provider.findAccessibilityNodeInfosByText(text,
- virtualDescendantId);
- if (infosFromProvider != null) {
- infos.addAll(infosFromProvider);
- }
- } else {
- infos.add(foundView.createAccessibilityNodeInfo());
- }
- }
- }
- }
- }
- }
- } finally {
- try {
- callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
- } catch (RemoteException re) {
- /* ignore - the other side will time out */
- }
- }
- }
-
- public void performAccessibilityActionClientThread(long accessibilityNodeId, int action,
- int interactionId, IAccessibilityInteractionConnectionCallback callback,
- int interogatingPid, long interrogatingTid) {
- Message message = mHandler.obtainMessage();
- message.what = MSG_PERFORM_ACCESSIBILITY_ACTION;
- message.arg1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
- message.arg2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
- SomeArgs args = mPool.acquire();
- args.argi1 = action;
- args.argi2 = interactionId;
- args.arg1 = callback;
- message.obj = args;
- // If the interrogation is performed by the same thread as the main UI
- // thread in this process, set the message as a static reference so
- // after this call completes the same thread but in the interrogating
- // client can handle the message to generate the result.
- if (interogatingPid == Process.myPid()
- && interrogatingTid == Looper.getMainLooper().getThread().getId()) {
- AccessibilityInteractionClient.getInstanceForThread(
- interrogatingTid).setSameThreadMessage(message);
+ ViewRootImpl viewRootImpl = mViewRootImpl.get();
+ if (viewRootImpl != null && viewRootImpl.mView != null) {
+ viewRootImpl.getAccessibilityInteractionController()
+ .focusSearchClientThread(accessibilityNodeId, interactionId, direction,
+ callback, flags, interrogatingPid, interrogatingTid);
} else {
- mHandler.sendMessage(message);
- }
- }
-
- public void perfromAccessibilityActionUiThread(Message message) {
- final int accessibilityViewId = message.arg1;
- final int virtualDescendantId = message.arg2;
- SomeArgs args = (SomeArgs) message.obj;
- final int action = args.argi1;
- final int interactionId = args.argi2;
- final IAccessibilityInteractionConnectionCallback callback =
- (IAccessibilityInteractionConnectionCallback) args.arg1;
- mPool.release(args);
- boolean succeeded = false;
- try {
- View target = findViewByAccessibilityId(accessibilityViewId);
- if (target != null && isDisplayedOnScreen(target)) {
- AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
- if (provider != null) {
- succeeded = provider.performAccessibilityAction(action,
- virtualDescendantId);
- } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED) {
- switch (action) {
- case AccessibilityNodeInfo.ACTION_FOCUS: {
- if (!target.hasFocus()) {
- // Get out of touch mode since accessibility
- // wants to move focus around.
- ensureTouchMode(false);
- succeeded = target.requestFocus();
- }
- } break;
- case AccessibilityNodeInfo.ACTION_CLEAR_FOCUS: {
- if (target.hasFocus()) {
- target.clearFocus();
- succeeded = !target.isFocused();
- }
- } break;
- case AccessibilityNodeInfo.ACTION_SELECT: {
- if (!target.isSelected()) {
- target.setSelected(true);
- succeeded = target.isSelected();
- }
- } break;
- case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
- if (target.isSelected()) {
- target.setSelected(false);
- succeeded = !target.isSelected();
- }
- } break;
- }
- }
- }
- } finally {
+ // We cannot make the call and notify the caller so it does not wait.
try {
- callback.setPerformAccessibilityActionResult(succeeded, interactionId);
+ callback.setFindAccessibilityNodeInfoResult(null, interactionId);
} catch (RemoteException re) {
- /* ignore - the other side will time out */
+ /* best effort - ignore */
}
}
}
-
- private View findViewByAccessibilityId(int accessibilityId) {
- View root = ViewRootImpl.this.mView;
- if (root == null) {
- return null;
- }
- View foundView = root.findViewByAccessibilityId(accessibilityId);
- if (foundView != null && foundView.getVisibility() != View.VISIBLE) {
- return null;
- }
- return foundView;
- }
}
private class SendWindowContentChangedAccessibilityEvent implements Runnable {
- public volatile boolean mIsPending;
+ public View mSource;
public void run() {
- if (mView != null) {
- mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- mIsPending = false;
- }
- }
- }
-
- /**
- * This class encapsulates a prefetching strategy for the accessibility APIs for
- * querying window content. It is responsible to prefetch a batch of
- * AccessibilityNodeInfos in addition to the one for a requested node.
- */
- class AccessibilityNodePrefetcher {
-
- private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50;
-
- public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int prefetchFlags,
- List<AccessibilityNodeInfo> outInfos) {
- AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
- if (provider == null) {
- AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
- if (root != null) {
- outInfos.add(root);
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
- prefetchPredecessorsOfRealNode(view, outInfos);
- }
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
- prefetchSiblingsOfRealNode(view, outInfos);
- }
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
- prefetchDescendantsOfRealNode(view, outInfos);
- }
- }
- } else {
- AccessibilityNodeInfo root = provider.createAccessibilityNodeInfo(virtualViewId);
- if (root != null) {
- outInfos.add(root);
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
- prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
- }
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
- prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
- }
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
- prefetchDescendantsOfVirtualNode(root, provider, outInfos);
- }
- }
- }
- }
-
- private void prefetchPredecessorsOfRealNode(View view,
- List<AccessibilityNodeInfo> outInfos) {
- ViewParent parent = view.getParent();
- while (parent instanceof View
- && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
- View parentView = (View) parent;
- final long parentNodeId = AccessibilityNodeInfo.makeNodeId(
- parentView.getAccessibilityViewId(), AccessibilityNodeInfo.UNDEFINED);
- AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
- if (info != null) {
- outInfos.add(info);
- }
- parent = parent.getParent();
- }
- }
-
- private void prefetchSiblingsOfRealNode(View current,
- List<AccessibilityNodeInfo> outInfos) {
- ViewParent parent = current.getParent();
- if (parent instanceof ViewGroup) {
- ViewGroup parentGroup = (ViewGroup) parent;
- final int childCount = parentGroup.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = parentGroup.getChildAt(i);
- if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE
- && child.getAccessibilityViewId() != current.getAccessibilityViewId()
- && isDisplayedOnScreen(child)) {
- final long childNodeId = AccessibilityNodeInfo.makeNodeId(
- child.getAccessibilityViewId(), AccessibilityNodeInfo.UNDEFINED);
- AccessibilityNodeInfo info = null;
- AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
- if (provider == null) {
- info = child.createAccessibilityNodeInfo();
- } else {
- info = provider.createAccessibilityNodeInfo(
- AccessibilityNodeInfo.UNDEFINED);
- }
- if (info != null) {
- outInfos.add(info);
- }
- }
- }
- }
- }
-
- private void prefetchDescendantsOfRealNode(View root,
- List<AccessibilityNodeInfo> outInfos) {
- if (root instanceof ViewGroup) {
- ViewGroup rootGroup = (ViewGroup) root;
- HashMap<View, AccessibilityNodeInfo> addedChildren =
- new HashMap<View, AccessibilityNodeInfo>();
- final int childCount = rootGroup.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = rootGroup.getChildAt(i);
- if (isDisplayedOnScreen(child)
- && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
- final long childNodeId = AccessibilityNodeInfo.makeNodeId(
- child.getAccessibilityViewId(), AccessibilityNodeInfo.UNDEFINED);
- AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
- if (provider == null) {
- AccessibilityNodeInfo info = child.createAccessibilityNodeInfo();
- if (info != null) {
- outInfos.add(info);
- addedChildren.put(child, null);
- }
- } else {
- AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo(
- AccessibilityNodeInfo.UNDEFINED);
- if (info != null) {
- outInfos.add(info);
- addedChildren.put(child, info);
- }
- }
- }
- }
- if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
- for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
- View addedChild = entry.getKey();
- AccessibilityNodeInfo virtualRoot = entry.getValue();
- if (virtualRoot == null) {
- prefetchDescendantsOfRealNode(addedChild, outInfos);
- } else {
- AccessibilityNodeProvider provider =
- addedChild.getAccessibilityNodeProvider();
- prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos);
- }
- }
- }
- }
- }
-
- private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root,
- View providerHost, AccessibilityNodeProvider provider,
- List<AccessibilityNodeInfo> outInfos) {
- long parentNodeId = root.getParentNodeId();
- int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
- while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
- final int virtualDescendantId =
- AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
- if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED
- || accessibilityViewId == providerHost.getAccessibilityViewId()) {
- AccessibilityNodeInfo parent = provider.createAccessibilityNodeInfo(
- virtualDescendantId);
- if (parent != null) {
- outInfos.add(parent);
- }
- parentNodeId = parent.getParentNodeId();
- accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
- parentNodeId);
- } else {
- prefetchPredecessorsOfRealNode(providerHost, outInfos);
- return;
- }
- }
- }
-
- private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost,
- AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
- final long parentNodeId = current.getParentNodeId();
- final int parentAccessibilityViewId =
- AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
- final int parentVirtualDescendantId =
- AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
- if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED
- || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) {
- AccessibilityNodeInfo parent =
- provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
- if (parent != null) {
- SparseLongArray childNodeIds = parent.getChildNodeIds();
- final int childCount = childNodeIds.size();
- for (int i = 0; i < childCount; i++) {
- final long childNodeId = childNodeIds.get(i);
- if (childNodeId != current.getSourceNodeId()
- && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
- final int childVirtualDescendantId =
- AccessibilityNodeInfo.getVirtualDescendantId(childNodeId);
- AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
- childVirtualDescendantId);
- if (child != null) {
- outInfos.add(child);
- }
- }
- }
- }
- } else {
- prefetchSiblingsOfRealNode(providerHost, outInfos);
- }
- }
-
- private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root,
- AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
- SparseLongArray childNodeIds = root.getChildNodeIds();
- final int initialOutInfosSize = outInfos.size();
- final int childCount = childNodeIds.size();
- for (int i = 0; i < childCount; i++) {
- if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
- final long childNodeId = childNodeIds.get(i);
- AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
- AccessibilityNodeInfo.getVirtualDescendantId(childNodeId));
- if (child != null) {
- outInfos.add(child);
- }
- }
- }
- if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
- final int addedChildCount = outInfos.size() - initialOutInfosSize;
- for (int i = 0; i < addedChildCount; i++) {
- AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
- prefetchDescendantsOfVirtualNode(child, provider, outInfos);
- }
+ if (mSource != null) {
+ mSource.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ mSource.resetAccessibilityStateChanged();
+ mSource = null;
}
}
}
diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java
index 9152cc3..110c239 100644
--- a/core/java/android/view/VolumePanel.java
+++ b/core/java/android/view/VolumePanel.java
@@ -263,7 +263,7 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
| LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()];
- mVibrator = new Vibrator();
+ mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
mVoiceCapable = context.getResources().getBoolean(R.bool.config_voice_capable);
mShowCombinedVolumes = !mVoiceCapable && !useMasterVolume;
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 66bdc5d..27baaea 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -667,7 +667,7 @@ public interface WindowManagerPolicy {
/**
* Create and return an animation to re-display a force hidden window.
*/
- public Animation createForceHideEnterAnimation();
+ public Animation createForceHideEnterAnimation(boolean onWallpaper);
/**
* Called from the input reader thread before a key is enqueued.
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 0998c80..6cb1578 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -508,7 +508,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
public static final int TYPE_VIEW_SELECTED = 0x00000004;
/**
- * Represents the event of focusing a {@link android.view.View}.
+ * Represents the event of setting input focus of a {@link android.view.View}.
*/
public static final int TYPE_VIEW_FOCUSED = 0x00000008;
@@ -549,7 +549,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
public static final int TYPE_TOUCH_EXPLORATION_GESTURE_END = 0x00000400;
/**
- * Represents the event of changing the content of a window.
+ * Represents the event of changing the content of a window and more
+ * specifically the sub-tree rooted at the event's source.
*/
public static final int TYPE_WINDOW_CONTENT_CHANGED = 0x00000800;
@@ -569,6 +570,16 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
public static final int TYPE_ANNOUNCEMENT = 0x00004000;
/**
+ * Represents the event of gaining accessibility focus.
+ */
+ public static final int TYPE_VIEW_ACCESSIBILITY_FOCUSED = 0x00008000;
+
+ /**
+ * Represents the event of clearing accessibility focus.
+ */
+ public static final int TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED = 0x00010000;
+
+ /**
* Mask for {@link AccessibilityEvent} all types.
*
* @see #TYPE_VIEW_CLICKED
@@ -1018,6 +1029,10 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
return "TYPE_VIEW_SCROLLED";
case TYPE_ANNOUNCEMENT:
return "TYPE_ANNOUNCEMENT";
+ case TYPE_VIEW_ACCESSIBILITY_FOCUSED:
+ return "TYPE_VIEW_ACCESSIBILITY_FOCUSED";
+ case TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED:
+ return "TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED";
default:
return null;
}
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index be74b31..35f0d9d 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -18,7 +18,9 @@ package android.view.accessibility;
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.graphics.Rect;
+import android.os.Binder;
import android.os.Message;
+import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
@@ -174,7 +176,7 @@ public final class AccessibilityInteractionClient
final int interactionId = mInteractionIdCounter.getAndIncrement();
final float windowScale = connection.findAccessibilityNodeInfoByAccessibilityId(
accessibilityWindowId, accessibilityNodeId, interactionId, this,
- Thread.currentThread().getId(), prefetchFlags);
+ prefetchFlags, Thread.currentThread().getId());
// If the scale is zero the call has failed.
if (windowScale > 0) {
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
@@ -293,6 +295,96 @@ public final class AccessibilityInteractionClient
}
/**
+ * Finds the {@link android.view.accessibility.AccessibilityNodeInfo} that has the
+ * specified focus type. The search is performed in the window whose id is specified
+ * and starts from the node whose accessibility id is specified.
+ *
+ * @param connectionId The id of a connection for interacting with the system.
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+ * to start from the root.
+ * @param focusType The focus type.
+ * @return The accessibility focused {@link AccessibilityNodeInfo}.
+ */
+ public AccessibilityNodeInfo findFocus(int connectionId, int accessibilityWindowId,
+ long accessibilityNodeId, int focusType) {
+ try {
+ IAccessibilityServiceConnection connection = getConnection(connectionId);
+ if (connection != null) {
+ final int interactionId = mInteractionIdCounter.getAndIncrement();
+ final float windowScale = connection.findFocus(accessibilityWindowId,
+ accessibilityNodeId, focusType, interactionId, this,
+ Thread.currentThread().getId());
+ // If the scale is zero the call has failed.
+ if (windowScale > 0) {
+ AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
+ interactionId);
+ finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale);
+ return info;
+ }
+ } else {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+ }
+ }
+ } catch (RemoteException re) {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "Error while calling remote findAccessibilityFocus", re);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Finds the accessibility focused {@link android.view.accessibility.AccessibilityNodeInfo}.
+ * The search is performed in the window whose id is specified and starts from the
+ * node whose accessibility id is specified.
+ *
+ * @param connectionId The id of a connection for interacting with the system.
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+ * to start from the root.
+ * @param direction The direction in which to search for focusable.
+ * @return The accessibility focused {@link AccessibilityNodeInfo}.
+ */
+ public AccessibilityNodeInfo focusSearch(int connectionId, int accessibilityWindowId,
+ long accessibilityNodeId, int direction) {
+ try {
+ IAccessibilityServiceConnection connection = getConnection(connectionId);
+ if (connection != null) {
+ final int interactionId = mInteractionIdCounter.getAndIncrement();
+ final float windowScale = connection.focusSearch(accessibilityWindowId,
+ accessibilityNodeId, direction, interactionId, this,
+ Thread.currentThread().getId());
+ // If the scale is zero the call has failed.
+ if (windowScale > 0) {
+ AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
+ interactionId);
+ finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale);
+ return info;
+ }
+ } else {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+ }
+ }
+ } catch (RemoteException re) {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "Error while calling remote accessibilityFocusSearch", re);
+ }
+ }
+ return null;
+ }
+
+ /**
* Performs an accessibility action on an {@link AccessibilityNodeInfo}.
*
* @param connectionId The id of a connection for interacting with the system.
@@ -382,7 +474,12 @@ public final class AccessibilityInteractionClient
int interactionId) {
synchronized (mInstanceLock) {
final boolean success = waitForResultTimedLocked(interactionId);
- List<AccessibilityNodeInfo> result = success ? mFindAccessibilityNodeInfosResult : null;
+ List<AccessibilityNodeInfo> result = null;
+ if (success) {
+ result = mFindAccessibilityNodeInfosResult;
+ } else {
+ result = Collections.emptyList();
+ }
clearResultLocked();
return result;
}
@@ -395,13 +492,18 @@ public final class AccessibilityInteractionClient
int interactionId) {
synchronized (mInstanceLock) {
if (interactionId > mInteractionId) {
- // If the call is not an IPC, i.e. it is made from the same process, we need to
- // instantiate new result list to avoid passing internal instances to clients.
- final boolean isIpcCall = (queryLocalInterface(getInterfaceDescriptor()) == null);
- if (!isIpcCall) {
- mFindAccessibilityNodeInfosResult = new ArrayList<AccessibilityNodeInfo>(infos);
+ if (infos != null) {
+ // If the call is not an IPC, i.e. it is made from the same process, we need to
+ // instantiate new result list to avoid passing internal instances to clients.
+ final boolean isIpcCall = (Binder.getCallingPid() != Process.myPid());
+ if (!isIpcCall) {
+ mFindAccessibilityNodeInfosResult =
+ new ArrayList<AccessibilityNodeInfo>(infos);
+ } else {
+ mFindAccessibilityNodeInfosResult = infos;
+ }
} else {
- mFindAccessibilityNodeInfosResult = infos;
+ mFindAccessibilityNodeInfosResult = Collections.emptyList();
}
mInteractionId = interactionId;
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index e37de6f..77fd12a 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -204,6 +204,12 @@ public final class AccessibilityManager {
* @param event The event to send.
*
* @throws IllegalStateException if accessibility is not enabled.
+ *
+ * <strong>Note:</strong> The preferred mechanism for sending custom accessibility
+ * events is through calling
+ * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)}
+ * instead of this method to allow predecessors to augment/filter events sent by
+ * their descendants.
*/
public void sendAccessibilityEvent(AccessibilityEvent event) {
if (!mIsEnabled) {
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index f616dca..1071c65 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -74,29 +74,57 @@ public class AccessibilityNodeInfo implements Parcelable {
public static final int FLAG_PREFETCH_SIBLINGS = 0x00000002;
/** @hide */
- public static final int FLAG_PREFETCH_DESCENDANTS = 0x00000003;
+ public static final int FLAG_PREFETCH_DESCENDANTS = 0x00000004;
+
+ /** @hide */
+ public static final int INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000008;
// Actions.
/**
- * Action that focuses the node.
+ * Action that gives input focus to the node.
*/
- public static final int ACTION_FOCUS = 0x00000001;
+ public static final int ACTION_FOCUS = 0x00000001;
/**
- * Action that unfocuses the node.
+ * Action that clears input focus of the node.
*/
- public static final int ACTION_CLEAR_FOCUS = 0x00000002;
+ public static final int ACTION_CLEAR_FOCUS = 0x00000002;
/**
* Action that selects the node.
*/
- public static final int ACTION_SELECT = 0x00000004;
+ public static final int ACTION_SELECT = 0x00000004;
/**
* Action that unselects the node.
*/
- public static final int ACTION_CLEAR_SELECTION = 0x00000008;
+ public static final int ACTION_CLEAR_SELECTION = 0x00000008;
+
+ /**
+ * Action that gives accessibility focus to the node.
+ */
+ public static final int ACTION_ACCESSIBILITY_FOCUS = 0x00000010;
+
+ /**
+ * Action that clears accessibility focus of the node.
+ */
+ public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 0x00000020;
+
+ /**
+ * Action that clicks on the node info./AccessibilityNodeInfoCache.java
+ */
+ public static final int ACTION_CLICK = 0x00000040;
+
+ /**
+ * The input focus.
+ */
+ public static final int FOCUS_INPUT = 1;
+
+ /**
+ * The accessibility focus.
+ */
+ public static final int FOCUS_ACCESSIBILITY = 2;
// Boolean attributes.
@@ -120,6 +148,8 @@ public class AccessibilityNodeInfo implements Parcelable {
private static final int PROPERTY_SCROLLABLE = 0x00000200;
+ private static final int PROPERTY_ACCESSIBILITY_FOCUSED = 0x00000400;
+
/**
* Bits that provide the id of a virtual descendant of a view.
*/
@@ -248,6 +278,57 @@ public class AccessibilityNodeInfo implements Parcelable {
(root != null) ? root.getAccessibilityViewId() : UNDEFINED;
mSourceNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
}
+
+ /**
+ * Find the view that has the input focus. The search starts from
+ * the view represented by this node info.
+ *
+ * @param focus The focus to find. One of {@link #FOCUS_INPUT} or
+ * {@link #FOCUS_ACCESSIBILITY}.
+ * @return The node info of the focused view or null.
+ *
+ * @see #FOCUS_INPUT
+ * @see #FOCUS_ACCESSIBILITY
+ */
+ public AccessibilityNodeInfo findFocus(int focus) {
+ enforceSealed();
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return null;
+ }
+ return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId, mWindowId,
+ mSourceNodeId, focus);
+ }
+
+ /**
+ * Searches for the nearest view in the specified direction that can take
+ * the input focus.
+ *
+ * @param direction The direction. Can be one of:
+ * {@link View#FOCUS_DOWN},
+ * {@link View#FOCUS_UP},
+ * {@link View#FOCUS_LEFT},
+ * {@link View#FOCUS_RIGHT},
+ * {@link View#FOCUS_FORWARD},
+ * {@link View#FOCUS_BACKWARD},
+ * {@link View#ACCESSIBILITY_FOCUS_IN},
+ * {@link View#ACCESSIBILITY_FOCUS_OUT},
+ * {@link View#ACCESSIBILITY_FOCUS_FORWARD},
+ * {@link View#ACCESSIBILITY_FOCUS_BACKWARD},
+ * {@link View#ACCESSIBILITY_FOCUS_UP},
+ * {@link View#ACCESSIBILITY_FOCUS_RIGHT},
+ * {@link View#ACCESSIBILITY_FOCUS_DOWN},
+ * {@link View#ACCESSIBILITY_FOCUS_LEFT}.
+ *
+ * @return The node info for the view that can take accessibility focus.
+ */
+ public AccessibilityNodeInfo focusSearch(int direction) {
+ enforceSealed();
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return null;
+ }
+ return AccessibilityInteractionClient.getInstance().focusSearch(mConnectionId, mWindowId,
+ mSourceNodeId, direction);
+ }
/**
* Gets the id of the window from which the info comes from.
@@ -642,6 +723,31 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * Gets whether this node is accessibility focused.
+ *
+ * @return True if the node is accessibility focused.
+ */
+ public boolean isAccessibilityFocused() {
+ return getBooleanProperty(PROPERTY_ACCESSIBILITY_FOCUSED);
+ }
+
+ /**
+ * Sets whether this node is accessibility focused.
+ * <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 focused True if the node is accessibility focused.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setAccessibilityFocused(boolean focused) {
+ setBooleanProperty(PROPERTY_ACCESSIBILITY_FOCUSED, focused);
+ }
+
+ /**
* Gets whether this node is selected.
*
* @return True if the node is selected.
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java
index dfbfc70..d2609bb 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java
@@ -18,6 +18,7 @@ package android.view.accessibility;
import android.util.Log;
import android.util.LongSparseArray;
+import android.util.SparseLongArray;
/**
* Simple cache for AccessibilityNodeInfos. The cache is mapping an
@@ -54,20 +55,25 @@ public class AccessibilityNodeInfoCache {
* @param event An event.
*/
public void onAccessibilityEvent(AccessibilityEvent event) {
- final int eventType = event.getEventType();
- switch (eventType) {
- case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
- case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
- case AccessibilityEvent.TYPE_VIEW_SCROLLED:
- clear();
- break;
- case AccessibilityEvent.TYPE_VIEW_FOCUSED:
- case AccessibilityEvent.TYPE_VIEW_SELECTED:
- case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
- case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED:
- final long accessibilityNodeId = event.getSourceNodeId();
- remove(accessibilityNodeId);
- break;
+ if (ENABLED) {
+ final int eventType = event.getEventType();
+ switch (eventType) {
+ case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
+ clear();
+ } break;
+ case AccessibilityEvent.TYPE_VIEW_FOCUSED:
+ case AccessibilityEvent.TYPE_VIEW_SELECTED:
+ case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
+ case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: {
+ final long accessibilityNodeId = event.getSourceNodeId();
+ remove(accessibilityNodeId);
+ } break;
+ case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
+ case AccessibilityEvent.TYPE_VIEW_SCROLLED: {
+ final long accessibilityNodeId = event.getSourceNodeId();
+ clearSubTree(accessibilityNodeId);
+ } break;
+ }
}
}
@@ -167,4 +173,23 @@ public class AccessibilityNodeInfoCache {
}
}
}
+
+ /**
+ * Clears a subtree rooted at the node with the given id.
+ *
+ * @param rootNodeId The root id.
+ */
+ private void clearSubTree(long rootNodeId) {
+ AccessibilityNodeInfo current = mCacheImpl.get(rootNodeId);
+ if (current == null) {
+ return;
+ }
+ mCacheImpl.remove(rootNodeId);
+ SparseLongArray childNodeIds = current.getChildNodeIds();
+ final int childCount = childNodeIds.size();
+ for (int i = 0; i < childCount; i++) {
+ final long childNodeId = childNodeIds.valueAt(i);
+ clearSubTree(childNodeId);
+ }
+ }
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeProvider.java b/core/java/android/view/accessibility/AccessibilityNodeProvider.java
index 5890417..19e35dd 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeProvider.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeProvider.java
@@ -87,6 +87,7 @@ public abstract class AccessibilityNodeProvider {
* @return A populated {@link AccessibilityNodeInfo} for a virtual descendant or the
* host View.
*
+ * @see View#createAccessibilityNodeInfo()
* @see AccessibilityNodeInfo
*/
public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
@@ -102,6 +103,7 @@ public abstract class AccessibilityNodeProvider {
* @param virtualViewId A client defined virtual view id.
* @return True if the action was performed.
*
+ * @see View#performAccessibilityAction(int)
* @see #createAccessibilityNodeInfo(int)
* @see AccessibilityNodeInfo
*/
@@ -127,4 +129,58 @@ public abstract class AccessibilityNodeProvider {
int virtualViewId) {
return null;
}
+
+ /**
+ * Finds the accessibility focused {@link AccessibilityNodeInfo}. The search is
+ * relative to the virtual view, i.e. a descendant of the host View, with the
+ * given <code>virtualViewId</code> or the host View itself
+ * <code>virtualViewId</code> equals to {@link View#NO_ID}.
+ *
+ * <strong>Note:</strong> Normally the system is responsible to transparently find
+ * accessibility focused view starting from a given root but for virtual view
+ * hierarchies it is a responsibility of this provider's implementor to find
+ * the accessibility focused virtual view.
+ *
+ * @param virtualViewId A client defined virtual view id which defined
+ * the root of the tree in which to perform the search.
+ * @return A list of node info.
+ *
+ * @see #createAccessibilityNodeInfo(int)
+ * @see AccessibilityNodeInfo
+ */
+ public AccessibilityNodeInfo findAccessibilitiyFocus(int virtualViewId) {
+ return null;
+ }
+
+ /**
+ * Finds {@link AccessibilityNodeInfo} to take accessibility focus in the given
+ * <code>direction</code>. The search is relative to the virtual view, i.e. a
+ * descendant of the host View, with the given <code>virtualViewId</code> or
+ * the host View itself <code>virtualViewId</code> equals to {@link View#NO_ID}.
+ *
+ * <strong>Note:</strong> Normally the system is responsible to transparently find
+ * the next view to take accessibility focus but for virtual view hierarchies
+ * it is a responsibility of this provider's implementor to compute the next
+ * focusable.
+ *
+ * @param direction The direction in which to search for a focus candidate.
+ * Values are
+ * {@link View#ACCESSIBILITY_FOCUS_IN},
+ * {@link View#ACCESSIBILITY_FOCUS_OUT},
+ * {@link View#ACCESSIBILITY_FOCUS_FORWARD},
+ * {@link View#ACCESSIBILITY_FOCUS_BACKWARD},
+ * {@link View#ACCESSIBILITY_FOCUS_UP},
+ * {@link View#ACCESSIBILITY_FOCUS_DOWN},
+ * {@link View#ACCESSIBILITY_FOCUS_LEFT},
+ * {@link View#ACCESSIBILITY_FOCUS_RIGHT}.
+ * @param virtualViewId A client defined virtual view id which defined
+ * the root of the tree in which to perform the search.
+ * @return A list of node info.
+ *
+ * @see #createAccessibilityNodeInfo(int)
+ * @see AccessibilityNodeInfo
+ */
+ public AccessibilityNodeInfo accessibilityFocusSearch(int direction, int virtualViewId) {
+ return null;
+ }
}
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index d25b3db..78a7d46 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -62,6 +62,7 @@ public class AccessibilityRecord {
private static final int PROPERTY_PASSWORD = 0x00000004;
private static final int PROPERTY_FULL_SCREEN = 0x00000080;
private static final int PROPERTY_SCROLLABLE = 0x00000100;
+ private static final int PROPERTY_IMPORTANT_FOR_ACCESSIBILITY = 0x00000200;
private static final int GET_SOURCE_PREFETCH_FLAGS =
AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS
@@ -77,7 +78,7 @@ public class AccessibilityRecord {
private boolean mIsInPool;
boolean mSealed;
- int mBooleanProperties;
+ int mBooleanProperties = PROPERTY_IMPORTANT_FOR_ACCESSIBILITY;
int mCurrentItemIndex = UNDEFINED;
int mItemCount = UNDEFINED;
int mFromIndex = UNDEFINED;
@@ -134,6 +135,8 @@ public class AccessibilityRecord {
*/
public void setSource(View root, int virtualDescendantId) {
enforceNotSealed();
+ final boolean important = (root != null) ? root.isImportantForAccessibility() : true;
+ setBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY, important);
mSourceWindowId = (root != null) ? root.getAccessibilityWindowId() : UNDEFINED;
final int rootViewId = (root != null) ? root.getAccessibilityViewId() : UNDEFINED;
mSourceNodeId = AccessibilityNodeInfo.makeNodeId(rootViewId, virtualDescendantId);
@@ -274,6 +277,23 @@ public class AccessibilityRecord {
}
/**
+ * Gets if the source is important for accessibility.
+ *
+ * <strong>Note:</strong> Used only internally to determine whether
+ * to deliver the event to a given accessibility service since some
+ * services may want to regard all views for accessibility while others
+ * may want to regard only the important views for accessibility.
+ *
+ * @return True if the source is important for accessibility,
+ * false otherwise.
+ *
+ * @hide
+ */
+ public boolean isImportantForAccessibility() {
+ return getBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY);
+ }
+
+ /**
* Gets the number of items that can be visited.
*
* @return The number of items.
@@ -755,7 +775,7 @@ public class AccessibilityRecord {
*/
void clear() {
mSealed = false;
- mBooleanProperties = 0;
+ mBooleanProperties = PROPERTY_IMPORTANT_FOR_ACCESSIBILITY;
mCurrentItemIndex = UNDEFINED;
mItemCount = UNDEFINED;
mFromIndex = UNDEFINED;
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
index fc3651c..8182d29 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
@@ -28,18 +28,26 @@ import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
oneway interface IAccessibilityInteractionConnection {
void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int prefetchFlags,
- int interrogatingPid, long interrogatingTid);
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+ long interrogatingTid);
void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int id, int interactionId,
- IAccessibilityInteractionConnectionCallback callback,
- int interrogatingPid, long interrogatingTid);
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+ long interrogatingTid);
void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int interrogatingPid,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+ long interrogatingTid);
+
+ void findFocus(long accessibilityNodeId, int interactionId, int focusType,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+ long interrogatingTid);
+
+ void focusSearch(long accessibilityNodeId, int interactionId, int direction,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
long interrogatingTid);
void performAccessibilityAction(long accessibilityNodeId, int action, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int interrogatingPid,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
long interrogatingTid);
}
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 320c75d..5b5134a 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -19,7 +19,7 @@ package android.view.accessibility;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceConnection;
-import android.accessibilityservice.IEventListener;
+import android.accessibilityservice.IAccessibilityServiceClient;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
@@ -49,7 +49,8 @@ interface IAccessibilityManager {
void removeAccessibilityInteractionConnection(IWindow windowToken);
- void registerUiTestAutomationService(IEventListener listener, in AccessibilityServiceInfo info);
+ void registerUiTestAutomationService(IAccessibilityServiceClient client,
+ in AccessibilityServiceInfo info);
- void unregisterUiTestAutomationService(IEventListener listener);
+ void unregisterUiTestAutomationService(IAccessibilityServiceClient client);
}
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index 800ebc8..64fbdd5 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -147,6 +147,40 @@ class CallbackProxy extends Handler {
}
}
+ private class JsResultReceiver implements JsResult.ResultReceiver {
+ // This prevents a user from interacting with the result before WebCore is
+ // ready to handle it.
+ private boolean mReady;
+ // Tells us if the user tried to confirm or cancel the result before WebCore
+ // is ready.
+ private boolean mTriedToNotifyBeforeReady;
+
+ public JsPromptResult mJsResult = new JsPromptResult(this);
+
+ final void setReady() {
+ mReady = true;
+ if (mTriedToNotifyBeforeReady) {
+ notifyCallbackProxy();
+ }
+ }
+
+ /* Wake up the WebCore thread. */
+ @Override
+ public void onJsResultComplete(JsResult result) {
+ if (mReady) {
+ notifyCallbackProxy();
+ } else {
+ mTriedToNotifyBeforeReady = true;
+ }
+ }
+
+ private void notifyCallbackProxy() {
+ synchronized (CallbackProxy.this) {
+ CallbackProxy.this.notify();
+ }
+ }
+}
+
/**
* Construct a new CallbackProxy.
*/
@@ -479,18 +513,18 @@ class CallbackProxy extends Handler {
String databaseIdentifier =
(String) map.get("databaseIdentifier");
String url = (String) map.get("url");
- long currentQuota =
- ((Long) map.get("currentQuota")).longValue();
- long totalUsedQuota =
- ((Long) map.get("totalUsedQuota")).longValue();
- long estimatedSize =
- ((Long) map.get("estimatedSize")).longValue();
+ long quota =
+ ((Long) map.get("quota")).longValue();
+ long totalQuota =
+ ((Long) map.get("totalQuota")).longValue();
+ long estimatedDatabaseSize =
+ ((Long) map.get("estimatedDatabaseSize")).longValue();
WebStorage.QuotaUpdater quotaUpdater =
(WebStorage.QuotaUpdater) map.get("quotaUpdater");
mWebChromeClient.onExceededDatabaseQuota(url,
- databaseIdentifier, currentQuota, estimatedSize,
- totalUsedQuota, quotaUpdater);
+ databaseIdentifier, quota, estimatedDatabaseSize,
+ totalQuota, quotaUpdater);
}
break;
@@ -498,15 +532,15 @@ class CallbackProxy extends Handler {
if (mWebChromeClient != null) {
HashMap<String, Object> map =
(HashMap<String, Object>) msg.obj;
- long spaceNeeded =
- ((Long) map.get("spaceNeeded")).longValue();
- long totalUsedQuota =
- ((Long) map.get("totalUsedQuota")).longValue();
+ long requiredStorage =
+ ((Long) map.get("requiredStorage")).longValue();
+ long quota =
+ ((Long) map.get("quota")).longValue();
WebStorage.QuotaUpdater quotaUpdater =
(WebStorage.QuotaUpdater) map.get("quotaUpdater");
- mWebChromeClient.onReachedMaxAppCacheSize(spaceNeeded,
- totalUsedQuota, quotaUpdater);
+ mWebChromeClient.onReachedMaxAppCacheSize(requiredStorage,
+ quota, quotaUpdater);
}
break;
@@ -531,14 +565,15 @@ class CallbackProxy extends Handler {
case JS_ALERT:
if (mWebChromeClient != null) {
- final JsResult res = (JsResult) msg.obj;
+ final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
+ final JsResult res = receiver.mJsResult;
String message = msg.getData().getString("message");
String url = msg.getData().getString("url");
if (!mWebChromeClient.onJsAlert(mWebView.getWebView(), url, message,
res)) {
if (!canShowAlertDialog()) {
res.cancel();
- res.setReady();
+ receiver.setReady();
break;
}
new AlertDialog.Builder(mContext)
@@ -561,20 +596,21 @@ class CallbackProxy extends Handler {
})
.show();
}
- res.setReady();
+ receiver.setReady();
}
break;
case JS_CONFIRM:
if (mWebChromeClient != null) {
- final JsResult res = (JsResult) msg.obj;
+ final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
+ final JsResult res = receiver.mJsResult;
String message = msg.getData().getString("message");
String url = msg.getData().getString("url");
if (!mWebChromeClient.onJsConfirm(mWebView.getWebView(), url, message,
res)) {
if (!canShowAlertDialog()) {
res.cancel();
- res.setReady();
+ receiver.setReady();
break;
}
new AlertDialog.Builder(mContext)
@@ -605,13 +641,14 @@ class CallbackProxy extends Handler {
}
// Tell the JsResult that it is ready for client
// interaction.
- res.setReady();
+ receiver.setReady();
}
break;
case JS_PROMPT:
if (mWebChromeClient != null) {
- final JsPromptResult res = (JsPromptResult) msg.obj;
+ final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
+ final JsPromptResult res = receiver.mJsResult;
String message = msg.getData().getString("message");
String defaultVal = msg.getData().getString("default");
String url = msg.getData().getString("url");
@@ -619,7 +656,7 @@ class CallbackProxy extends Handler {
defaultVal, res)) {
if (!canShowAlertDialog()) {
res.cancel();
- res.setReady();
+ receiver.setReady();
break;
}
final LayoutInflater factory = LayoutInflater
@@ -662,20 +699,21 @@ class CallbackProxy extends Handler {
}
// Tell the JsResult that it is ready for client
// interaction.
- res.setReady();
+ receiver.setReady();
}
break;
case JS_UNLOAD:
if (mWebChromeClient != null) {
- final JsResult res = (JsResult) msg.obj;
+ final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
+ final JsResult res = receiver.mJsResult;
String message = msg.getData().getString("message");
String url = msg.getData().getString("url");
if (!mWebChromeClient.onJsBeforeUnload(mWebView.getWebView(), url,
message, res)) {
if (!canShowAlertDialog()) {
res.cancel();
- res.setReady();
+ receiver.setReady();
break;
}
final String m = mContext.getString(
@@ -700,19 +738,20 @@ class CallbackProxy extends Handler {
})
.show();
}
- res.setReady();
+ receiver.setReady();
}
break;
case JS_TIMEOUT:
if(mWebChromeClient != null) {
- final JsResult res = (JsResult) msg.obj;
+ final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
+ final JsResult res = receiver.mJsResult;
if(mWebChromeClient.onJsTimeout()) {
res.confirm();
} else {
res.cancel();
}
- res.setReady();
+ receiver.setReady();
}
break;
@@ -791,7 +830,8 @@ class CallbackProxy extends Handler {
case OPEN_FILE_CHOOSER:
if (mWebChromeClient != null) {
UploadFileMessageData data = (UploadFileMessageData)msg.obj;
- mWebChromeClient.openFileChooser(data.getUploadFile(), data.getAcceptType());
+ mWebChromeClient.openFileChooser(data.getUploadFile(), data.getAcceptType(),
+ data.getCapture());
}
break;
@@ -1331,7 +1371,7 @@ class CallbackProxy extends Handler {
if (mWebChromeClient == null) {
return;
}
- JsResult result = new JsResult(this, false);
+ JsResultReceiver result = new JsResultReceiver();
Message alert = obtainMessage(JS_ALERT, result);
alert.getData().putString("message", message);
alert.getData().putString("url", url);
@@ -1352,7 +1392,7 @@ class CallbackProxy extends Handler {
if (mWebChromeClient == null) {
return false;
}
- JsResult result = new JsResult(this, false);
+ JsResultReceiver result = new JsResultReceiver();
Message confirm = obtainMessage(JS_CONFIRM, result);
confirm.getData().putString("message", message);
confirm.getData().putString("url", url);
@@ -1365,7 +1405,7 @@ class CallbackProxy extends Handler {
Log.e(LOGTAG, Log.getStackTraceString(e));
}
}
- return result.getResult();
+ return result.mJsResult.getResult();
}
public String onJsPrompt(String url, String message, String defaultValue) {
@@ -1374,7 +1414,7 @@ class CallbackProxy extends Handler {
if (mWebChromeClient == null) {
return null;
}
- JsPromptResult result = new JsPromptResult(this);
+ JsResultReceiver result = new JsResultReceiver();
Message prompt = obtainMessage(JS_PROMPT, result);
prompt.getData().putString("message", message);
prompt.getData().putString("default", defaultValue);
@@ -1388,7 +1428,7 @@ class CallbackProxy extends Handler {
Log.e(LOGTAG, Log.getStackTraceString(e));
}
}
- return result.getStringResult();
+ return result.mJsResult.getStringResult();
}
public boolean onJsBeforeUnload(String url, String message) {
@@ -1397,7 +1437,7 @@ class CallbackProxy extends Handler {
if (mWebChromeClient == null) {
return true;
}
- JsResult result = new JsResult(this, true);
+ JsResultReceiver result = new JsResultReceiver();
Message confirm = obtainMessage(JS_UNLOAD, result);
confirm.getData().putString("message", message);
confirm.getData().putString("url", url);
@@ -1410,7 +1450,7 @@ class CallbackProxy extends Handler {
Log.e(LOGTAG, Log.getStackTraceString(e));
}
}
- return result.getResult();
+ return result.mJsResult.getResult();
}
/**
@@ -1422,19 +1462,21 @@ class CallbackProxy extends Handler {
* @param url The URL that caused the quota overflow.
* @param databaseIdentifier The identifier of the database that the
* transaction that caused the overflow was running on.
- * @param currentQuota The current quota the origin is allowed.
- * @param estimatedSize The estimated size of the database.
- * @param totalUsedQuota is the sum of all origins' quota.
+ * @param quota The current quota the origin is allowed.
+ * @param estimatedDatabaseSize The estimated size of the database.
+ * @param totalQuota is the sum of all origins' quota.
* @param quotaUpdater An instance of a class encapsulating a callback
* to WebViewCore to run when the decision to allow or deny more
* quota has been made.
*/
public void onExceededDatabaseQuota(
- String url, String databaseIdentifier, long currentQuota,
- long estimatedSize, long totalUsedQuota,
+ String url, String databaseIdentifier, long quota,
+ long estimatedDatabaseSize, long totalQuota,
WebStorage.QuotaUpdater quotaUpdater) {
if (mWebChromeClient == null) {
- quotaUpdater.updateQuota(currentQuota);
+ // Native-side logic prevents the quota being updated to a smaller
+ // value.
+ quotaUpdater.updateQuota(quota);
return;
}
@@ -1442,9 +1484,9 @@ class CallbackProxy extends Handler {
HashMap<String, Object> map = new HashMap();
map.put("databaseIdentifier", databaseIdentifier);
map.put("url", url);
- map.put("currentQuota", currentQuota);
- map.put("estimatedSize", estimatedSize);
- map.put("totalUsedQuota", totalUsedQuota);
+ map.put("quota", quota);
+ map.put("estimatedDatabaseSize", estimatedDatabaseSize);
+ map.put("totalQuota", totalQuota);
map.put("quotaUpdater", quotaUpdater);
exceededQuota.obj = map;
sendMessage(exceededQuota);
@@ -1453,24 +1495,26 @@ class CallbackProxy extends Handler {
/**
* Called by WebViewCore to inform the Java side that the appcache has
* exceeded its max size.
- * @param spaceNeeded is the amount of disk space that would be needed
- * in order for the last appcache operation to succeed.
- * @param totalUsedQuota is the sum of all origins' quota.
+ * @param requiredStorage is the amount of storage, in bytes, that would be
+ * needed in order for the last appcache operation to succeed.
+ * @param quota is the current quota (for all origins).
* @param quotaUpdater An instance of a class encapsulating a callback
* to WebViewCore to run when the decision to allow or deny a bigger
* app cache size has been made.
*/
- public void onReachedMaxAppCacheSize(long spaceNeeded,
- long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
+ public void onReachedMaxAppCacheSize(long requiredStorage,
+ long quota, WebStorage.QuotaUpdater quotaUpdater) {
if (mWebChromeClient == null) {
- quotaUpdater.updateQuota(0);
+ // Native-side logic prevents the quota being updated to a smaller
+ // value.
+ quotaUpdater.updateQuota(quota);
return;
}
Message msg = obtainMessage(REACHED_APPCACHE_MAXSIZE);
HashMap<String, Object> map = new HashMap();
- map.put("spaceNeeded", spaceNeeded);
- map.put("totalUsedQuota", totalUsedQuota);
+ map.put("requiredStorage", requiredStorage);
+ map.put("quota", quota);
map.put("quotaUpdater", quotaUpdater);
msg.obj = map;
sendMessage(msg);
@@ -1540,7 +1584,7 @@ class CallbackProxy extends Handler {
if (mWebChromeClient == null) {
return true;
}
- JsResult result = new JsResult(this, true);
+ JsResultReceiver result = new JsResultReceiver();
Message timeout = obtainMessage(JS_TIMEOUT, result);
synchronized (this) {
sendMessage(timeout);
@@ -1551,7 +1595,7 @@ class CallbackProxy extends Handler {
Log.e(LOGTAG, Log.getStackTraceString(e));
}
}
- return result.getResult();
+ return result.mJsResult.getResult();
}
public void getVisitedHistory(ValueCallback<String[]> callback) {
@@ -1566,10 +1610,12 @@ class CallbackProxy extends Handler {
private static class UploadFileMessageData {
private UploadFile mCallback;
private String mAcceptType;
+ private String mCapture;
- public UploadFileMessageData(UploadFile uploadFile, String acceptType) {
+ public UploadFileMessageData(UploadFile uploadFile, String acceptType, String capture) {
mCallback = uploadFile;
mAcceptType = acceptType;
+ mCapture = capture;
}
public UploadFile getUploadFile() {
@@ -1579,6 +1625,10 @@ class CallbackProxy extends Handler {
public String getAcceptType() {
return mAcceptType;
}
+
+ public String getCapture() {
+ return mCapture;
+ }
}
private class UploadFile implements ValueCallback<Uri> {
@@ -1597,13 +1647,13 @@ class CallbackProxy extends Handler {
/**
* Called by WebViewCore to open a file chooser.
*/
- /* package */ Uri openFileChooser(String acceptType) {
+ /* package */ Uri openFileChooser(String acceptType, String capture) {
if (mWebChromeClient == null) {
return null;
}
Message myMessage = obtainMessage(OPEN_FILE_CHOOSER);
UploadFile uploadFile = new UploadFile();
- UploadFileMessageData data = new UploadFileMessageData(uploadFile, acceptType);
+ UploadFileMessageData data = new UploadFileMessageData(uploadFile, acceptType, capture);
myMessage.obj = data;
synchronized (this) {
sendMessage(myMessage);
diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java
index 5f7ef41..2997c1a 100644
--- a/core/java/android/webkit/CookieManager.java
+++ b/core/java/android/webkit/CookieManager.java
@@ -26,7 +26,7 @@ import android.util.Log;
* Manages the cookies used by an application's {@link WebView} instances.
* Cookies are manipulated according to RFC2109.
*/
-public final class CookieManager {
+public class CookieManager {
private static CookieManager sRef;
diff --git a/core/java/android/webkit/GeolocationPermissions.java b/core/java/android/webkit/GeolocationPermissions.java
index 93eb082..1441541 100755
--- a/core/java/android/webkit/GeolocationPermissions.java
+++ b/core/java/android/webkit/GeolocationPermissions.java
@@ -50,7 +50,7 @@ import java.util.Vector;
// Within WebKit, Geolocation permissions may be applied either temporarily
// (for the duration of the page) or permanently. This class deals only with
// permanent permissions.
-public final class GeolocationPermissions {
+public class GeolocationPermissions {
/**
* A callback interface used by the host application to set the Geolocation
* permission state for an origin.
@@ -293,6 +293,16 @@ public final class GeolocationPermissions {
postMessage(Message.obtain(null, CLEAR_ALL));
}
+ /**
+ * This class should not be instantiated directly, applications must only use
+ * {@link #getInstance()} to obtain the instance.
+ * Note this constructor was erroneously public and published in SDK levels prior to 16, but
+ * applications using it would receive a non-functional instance of this class (there was no
+ * way to call createHandler() and createUIHandler(), so it would not work).
+ * @hide
+ */
+ public GeolocationPermissions() {}
+
// Native functions, run on the WebKit thread.
private static native Set nativeGetOrigins();
private static native boolean nativeGetAllowed(String origin);
diff --git a/core/java/android/webkit/JsPromptResult.java b/core/java/android/webkit/JsPromptResult.java
index 9fcd1bc..a1bf124 100644
--- a/core/java/android/webkit/JsPromptResult.java
+++ b/core/java/android/webkit/JsPromptResult.java
@@ -18,11 +18,11 @@ package android.webkit;
/**
- * Public class for handling javascript prompt requests. A
- * JsDialogHandlerInterface implentation will receive a jsPrompt call with a
- * JsPromptResult parameter. This parameter is used to return a result to
- * WebView. The client can call cancel() to cancel the dialog or confirm() with
- * the user's input to confirm the dialog.
+ * Public class for handling JavaScript prompt requests. The WebChromeClient will receive a
+ * {@link WebChromeClient#onJsPrompt(WebView, String, String, String, JsPromptResult)} call with a
+ * JsPromptResult instance as a parameter. This parameter is used to return the result of this user
+ * dialog prompt back to the WebView instance. The client can call cancel() to cancel the dialog or
+ * confirm() with the user's input to confirm the dialog.
*/
public class JsPromptResult extends JsResult {
// String result of the prompt
@@ -36,17 +36,17 @@ public class JsPromptResult extends JsResult {
confirm();
}
- /*package*/ JsPromptResult(CallbackProxy proxy) {
- super(proxy, /* unused */ false);
+ /**
+ * @hide Only for use by WebViewProvider implementations
+ */
+ public JsPromptResult(ResultReceiver receiver) {
+ super(receiver);
}
- /*package*/ String getStringResult() {
+ /**
+ * @hide Only for use by WebViewProvider implementations
+ */
+ public String getStringResult() {
return mStringResult;
}
-
- @Override
- /*package*/ void handleDefault() {
- mStringResult = null;
- super.handleDefault();
- }
}
diff --git a/core/java/android/webkit/JsResult.java b/core/java/android/webkit/JsResult.java
index e61ab21..e4e6851 100644
--- a/core/java/android/webkit/JsResult.java
+++ b/core/java/android/webkit/JsResult.java
@@ -16,23 +16,24 @@
package android.webkit;
-
+/**
+ * An instance of this class is passed as a parameter in various {@link WebChromeClient} action
+ * notifications. The object is used as a handle onto the underlying JavaScript-originated request,
+ * and provides a means for the client to indicate whether this action should proceed.
+ */
public class JsResult {
- // This prevents a user from interacting with the result before WebCore is
- // ready to handle it.
- private boolean mReady;
- // Tells us if the user tried to confirm or cancel the result before WebCore
- // is ready.
- private boolean mTriedToNotifyBeforeReady;
- // This is a basic result of a confirm or prompt dialog.
- protected boolean mResult;
/**
- * This is the caller of the prompt and is the object that is waiting.
- * @hide
+ * Callback interface, implemented by the WebViewProvider implementation to receive
+ * notifications when the JavaScript result represented by a JsResult instance has
+ * @hide Only for use by WebViewProvider implementations
*/
- protected final CallbackProxy mProxy;
- // This is the default value of the result.
- private final boolean mDefaultValue;
+ public interface ResultReceiver {
+ public void onJsResultComplete(JsResult result);
+ }
+ // This is the caller of the prompt and is the object that is waiting.
+ private final ResultReceiver mReceiver;
+ // This is a basic result of a confirm or prompt dialog.
+ private boolean mResult;
/**
* Handle the result if the user cancelled the dialog.
@@ -50,36 +51,22 @@ public class JsResult {
wakeUp();
}
- /*package*/ JsResult(CallbackProxy proxy, boolean defaultVal) {
- mProxy = proxy;
- mDefaultValue = defaultVal;
+ /**
+ * @hide Only for use by WebViewProvider implementations
+ */
+ public JsResult(ResultReceiver receiver) {
+ mReceiver = receiver;
}
- /*package*/ final boolean getResult() {
+ /**
+ * @hide Only for use by WebViewProvider implementations
+ */
+ public final boolean getResult() {
return mResult;
}
- /*package*/ final void setReady() {
- mReady = true;
- if (mTriedToNotifyBeforeReady) {
- wakeUp();
- }
- }
-
- /*package*/ void handleDefault() {
- setReady();
- mResult = mDefaultValue;
- wakeUp();
- }
-
- /* Wake up the WebCore thread. */
- protected final void wakeUp() {
- if (mReady) {
- synchronized (mProxy) {
- mProxy.notify();
- }
- } else {
- mTriedToNotifyBeforeReady = true;
- }
+ /* Notify the caller that the JsResult has completed */
+ private final void wakeUp() {
+ mReceiver.onJsResultComplete(this);
}
}
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index a6ef0ce..4e8790b 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -216,37 +216,54 @@ public class WebChromeClient {
}
/**
- * Tell the client that the database quota for the origin has been exceeded.
- * @param url The URL that triggered the notification
- * @param databaseIdentifier The identifier of the database that caused the
- * quota overflow.
- * @param currentQuota The current quota for the origin.
- * @param estimatedSize The estimated size of the database.
- * @param totalUsedQuota is the sum of all origins' quota.
- * @param quotaUpdater A callback to inform the WebCore thread that a new
- * quota is available. This callback must always be executed at some
- * point to ensure that the sleeping WebCore thread is woken up.
+ * Tell the client that the quota has been exceeded for the Web SQL Database
+ * API for a particular origin and request a new quota. The client must
+ * respond by invoking the
+ * {@link WebStorage.QuotaUpdater#updateQuota(long) updateQuota(long)}
+ * method of the supplied {@link WebStorage.QuotaUpdater} instance. The
+ * minimum value that can be set for the new quota is the current quota. The
+ * default implementation responds with the current quota, so the quota will
+ * not be increased.
+ * @param url The URL of the page that triggered the notification
+ * @param databaseIdentifier The identifier of the database where the quota
+ * was exceeded.
+ * @param quota The quota for the origin, in bytes
+ * @param estimatedDatabaseSize The estimated size of the offending
+ * database, in bytes
+ * @param totalQuota The total quota for all origins, in bytes
+ * @param quotaUpdater An instance of {@link WebStorage.QuotaUpdater} which
+ * must be used to inform the WebView of the new quota.
*/
+ // Note that the callback must always be executed at some point to ensure
+ // that the sleeping WebCore thread is woken up.
public void onExceededDatabaseQuota(String url, String databaseIdentifier,
- long currentQuota, long estimatedSize, long totalUsedQuota,
- WebStorage.QuotaUpdater quotaUpdater) {
+ long quota, long estimatedDatabaseSize, long totalQuota,
+ WebStorage.QuotaUpdater quotaUpdater) {
// This default implementation passes the current quota back to WebCore.
// WebCore will interpret this that new quota was declined.
- quotaUpdater.updateQuota(currentQuota);
+ quotaUpdater.updateQuota(quota);
}
/**
- * Tell the client that the Application Cache has exceeded its max size.
- * @param spaceNeeded is the amount of disk space that would be needed
- * in order for the last appcache operation to succeed.
- * @param totalUsedQuota is the sum of all origins' quota.
- * @param quotaUpdater A callback to inform the WebCore thread that a new
- * app cache size is available. This callback must always be executed at
- * some point to ensure that the sleeping WebCore thread is woken up.
+ * Tell the client that the quota has been reached for the Application Cache
+ * API and request a new quota. The client must respond by invoking the
+ * {@link WebStorage.QuotaUpdater#updateQuota(long) updateQuota(long)}
+ * method of the supplied {@link WebStorage.QuotaUpdater} instance. The
+ * minimum value that can be set for the new quota is the current quota. The
+ * default implementation responds with the current quota, so the quota will
+ * not be increased.
+ * @param requiredStorage The amount of storage required by the Application
+ * Cache operation that triggered this notification,
+ * in bytes.
+ * @param quota The quota, in bytes
+ * @param quotaUpdater An instance of {@link WebStorage.QuotaUpdater} which
+ * must be used to inform the WebView of the new quota.
*/
- public void onReachedMaxAppCacheSize(long spaceNeeded, long totalUsedQuota,
+ // Note that the callback must always be executed at some point to ensure
+ // that the sleeping WebCore thread is woken up.
+ public void onReachedMaxAppCacheSize(long requiredStorage, long quota,
WebStorage.QuotaUpdater quotaUpdater) {
- quotaUpdater.updateQuota(0);
+ quotaUpdater.updateQuota(quota);
}
/**
@@ -346,9 +363,11 @@ public class WebChromeClient {
* onReceiveValue must be called to wake up the thread.a
* @param acceptType The value of the 'accept' attribute of the input tag
* associated with this file picker.
+ * @param capture The value of the 'capture' attribute of the input tag
+ * associated with this file picker.
* @hide
*/
- public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType) {
+ public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType, String capture) {
uploadFile.onReceiveValue(null);
}
diff --git a/core/java/android/webkit/WebIconDatabase.java b/core/java/android/webkit/WebIconDatabase.java
index 54dfab3..9299b71 100644
--- a/core/java/android/webkit/WebIconDatabase.java
+++ b/core/java/android/webkit/WebIconDatabase.java
@@ -35,7 +35,7 @@ import java.util.Vector;
* WebIconDatabase object is a single instance and all methods operate on that
* single object.
*/
-public final class WebIconDatabase {
+public class WebIconDatabase {
private static final String LOGTAG = "WebIconDatabase";
// Global instance of a WebIconDatabase
private static WebIconDatabase sIconDatabase;
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index cddd7ab..105285c 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -170,45 +170,62 @@ public class WebSettings {
}
/**
- * Set whether the WebView supports zoom
+ * Sets whether the WebView should support zooming using its on-screen zoom
+ * controls and gestures. The particular zoom mechanisms that should be used
+ * can be set with {@link #setBuiltInZoomControls}. This setting does not
+ * affect zooming performed using the {@link WebView#zoomIn()} and
+ * {@link WebView#zoomOut()} methods.
+ * @param support Whether the WebView should support zoom.
*/
public void setSupportZoom(boolean support) {
throw new MustOverrideException();
}
/**
- * Returns whether the WebView supports zoom
+ * Returns true if the WebView supports zoom. The default is true.
+ * @return True if the WebView supports zoom.
*/
public boolean supportZoom() {
throw new MustOverrideException();
}
/**
- * Sets whether the zoom mechanism built into WebView is used.
+ * Sets whether the WebView should use its built-in zoom mechanisms, as
+ * opposed to separate zoom controls. The built-in zoom mechanisms comprise
+ * on-screen zoom controls, which are displayed over the WebView's content,
+ * and the use of a pinch gesture to control zooming. Whether or not these
+ * on-screen controls are displayed can be set with {@link #setDisplayZoomControls}.
+ * The separate zoom controls are no longer supported, so it is recommended
+ * that this setting is always enabled.
+ * @param enabled Whether the WebView should use the built-in zoom mechanism.
*/
public void setBuiltInZoomControls(boolean enabled) {
throw new MustOverrideException();
}
/**
- * Returns true if the zoom mechanism built into WebView is being used.
+ * Returns true if the zoom mechanisms built into WebView are being used.
+ * The default is false.
+ * @return True if the zoom mechanisms built into WebView are being used.
*/
public boolean getBuiltInZoomControls() {
throw new MustOverrideException();
}
/**
- * Sets whether the on screen zoom buttons are used.
- * A combination of built in zoom controls enabled
- * and on screen zoom controls disabled allows for pinch to zoom
- * to work without the on screen controls
+ * Sets whether the WebView should display on-screen zoom controls when
+ * using the built-in zoom mechanisms. See {@link #setBuiltInZoomControls}.
+ * @param enabled Whether the WebView should display on-screen zoom controls.
*/
public void setDisplayZoomControls(boolean enabled) {
throw new MustOverrideException();
}
/**
- * Returns true if the on screen zoom buttons are being used.
+ * Returns true if the WebView displays on-screen zoom controls when using
+ * the built-in zoom mechanisms. The default is true.
+ * @return True if the WebView displays on-screen zoom controls when using
+ * the built-in zoom mechanisms.
*/
public boolean getDisplayZoomControls() {
throw new MustOverrideException();
diff --git a/core/java/android/webkit/WebStorage.java b/core/java/android/webkit/WebStorage.java
index 2300c2e..041791b 100644
--- a/core/java/android/webkit/WebStorage.java
+++ b/core/java/android/webkit/WebStorage.java
@@ -25,19 +25,34 @@ import java.util.Map;
import java.util.Set;
/**
- * Functionality for manipulating the webstorage databases.
+ * This class is used to manage the JavaScript storage APIs provided by the
+ * {@link WebView}. It manages the Application Cache API, the Web SQL Database
+ * API and the HTML5 Web Storage API.
+ *
+ * The Web SQL Database API provides storage which is private to a given
+ * origin, where an origin comprises the host, scheme and port of a URI.
+ * Similarly, use of the Application Cache API can be attributed to an origin.
+ * This class provides access to the storage use and quotas for these APIs for
+ * a given origin. Origins are represented using {@link WebStorage.Origin}.
*/
-public final class WebStorage {
+public class WebStorage {
/**
- * Encapsulates a callback function to be executed when a new quota is made
- * available. We primarily want this to allow us to call back the sleeping
- * WebCore thread from outside the WebViewCore class (as the native call
- * is private). It is imperative that this the setDatabaseQuota method is
- * executed once a decision to either allow or deny new quota is made,
- * otherwise the WebCore thread will remain asleep.
+ * Encapsulates a callback function which is used to provide a new quota
+ * for a JavaScript storage API. See
+ * {@link WebChromeClient#onExceededDatabaseQuota} and
+ * {@link WebChromeClient#onReachedMaxAppCacheSize}.
*/
+ // We primarily want this to allow us to call back the sleeping WebCore
+ // thread from outside the WebViewCore class (as the native call is
+ // private). It is imperative that the setDatabaseQuota method is
+ // executed after a decision to either allow or deny new quota is made,
+ // otherwise the WebCore thread will remain asleep.
public interface QuotaUpdater {
+ /**
+ * Provide a new quota, specified in bytes.
+ * @param newQuota The new quota, in bytes
+ */
public void updateQuota(long newQuota);
};
@@ -70,7 +85,9 @@ public final class WebStorage {
private Handler mUIHandler = null;
/**
- * Class containing the HTML5 database quota and usage for an origin.
+ * This class encapsulates information about the amount of storage
+ * currently used by an origin for the JavaScript storage APIs.
+ * See {@link WebStorage} for details.
*/
public static class Origin {
private String mOrigin = null;
@@ -93,28 +110,32 @@ public final class WebStorage {
}
/**
- * An origin string is created using WebCore::SecurityOrigin::toString().
- * Note that WebCore::SecurityOrigin uses 0 (which is not printed) for
- * the port if the port is the default for the protocol. Eg
- * http://www.google.com and http://www.google.com:80 both record a port
- * of 0 and hence toString() == 'http://www.google.com' for both.
- * @return The origin string.
+ * Get the string representation of this origin.
+ * @return The string representation of this origin
*/
+ // An origin string is created using WebCore::SecurityOrigin::toString().
+ // Note that WebCore::SecurityOrigin uses 0 (which is not printed) for
+ // the port if the port is the default for the protocol. Eg
+ // http://www.google.com and http://www.google.com:80 both record a port
+ // of 0 and hence toString() == 'http://www.google.com' for both.
public String getOrigin() {
return mOrigin;
}
/**
- * Returns the quota for this origin's HTML5 database.
- * @return The quota in bytes.
+ * Get the quota for this origin, for the Web SQL Database API, in
+ * bytes. If this origin does not use the Web SQL Database API, this
+ * quota will be set to zero.
+ * @return The quota, in bytes.
*/
public long getQuota() {
return mQuota;
}
/**
- * Returns the usage for this origin's HTML5 database.
- * @return The usage in bytes.
+ * Get the total amount of storage currently being used by this origin,
+ * for all JavaScript storage APIs, in bytes.
+ * @return The total amount of storage, in bytes.
*/
public long getUsage() {
return mUsage;
@@ -122,8 +143,8 @@ public final class WebStorage {
}
/**
- * @hide
* Message handler, UI side
+ * @hide
*/
public void createUIHandler() {
if (mUIHandler == null) {
@@ -156,8 +177,8 @@ public final class WebStorage {
}
/**
+ * Message handler, WebCore side
* @hide
- * Message handler, webcore side
*/
public synchronized void createHandler() {
if (mHandler == null) {
@@ -231,19 +252,22 @@ public final class WebStorage {
/*
* When calling getOrigins(), getUsageForOrigin() and getQuotaForOrigin(),
- * we need to get the values from webcore, but we cannot block while doing so
- * as we used to do, as this could result in a full deadlock (other webcore
+ * we need to get the values from WebCore, but we cannot block while doing so
+ * as we used to do, as this could result in a full deadlock (other WebCore
* messages received while we are still blocked here, see http://b/2127737).
*
* We have to do everything asynchronously, by providing a callback function.
- * We post a message on the webcore thread (mHandler) that will get the result
- * from webcore, and we post it back on the UI thread (using mUIHandler).
+ * We post a message on the WebCore thread (mHandler) that will get the result
+ * from WebCore, and we post it back on the UI thread (using mUIHandler).
* We can then use the callback function to return the value.
*/
/**
- * Returns a list of origins having a database. The Map is of type
- * Map<String, Origin>.
+ * Get the origins currently using either the Application Cache or Web SQL
+ * Database APIs. This method operates asynchronously, with the result
+ * being provided via a {@link ValueCallback}. The origins are provided as
+ * a map, of type {@code Map<String, WebStorage.Origin>}, from the string
+ * representation of the origin to a {@link WebStorage.Origin} object.
*/
public void getOrigins(ValueCallback<Map> callback) {
if (callback != null) {
@@ -269,7 +293,11 @@ public final class WebStorage {
}
/**
- * Returns the use for a given origin
+ * Get the amount of storage currently being used by both the Application
+ * Cache and Web SQL Database APIs by the given origin. The amount is given
+ * in bytes and the origin is specified using its string representation.
+ * This method operates asynchronously, with the result being provided via
+ * a {@link ValueCallback}.
*/
public void getUsageForOrigin(String origin, ValueCallback<Long> callback) {
if (callback == null) {
@@ -292,7 +320,11 @@ public final class WebStorage {
}
/**
- * Returns the quota for a given origin
+ * Get the storage quota for the Web SQL Database API for the given origin.
+ * The quota is given in bytes and the origin is specified using its string
+ * representation. This method operates asynchronously, with the result
+ * being provided via a {@link ValueCallback}. Note that a quota is not
+ * enforced on a per-origin basis for the Application Cache API.
*/
public void getQuotaForOrigin(String origin, ValueCallback<Long> callback) {
if (callback == null) {
@@ -315,7 +347,10 @@ public final class WebStorage {
}
/**
- * Set the quota for a given origin
+ * Set the storage quota for the Web SQL Database API for the given origin.
+ * The quota is specified in bytes and the origin is specified using its string
+ * representation. Note that a quota is not enforced on a per-origin basis
+ * for the Application Cache API.
*/
public void setQuotaForOrigin(String origin, long quota) {
if (origin != null) {
@@ -329,7 +364,9 @@ public final class WebStorage {
}
/**
- * Delete a given origin
+ * Clear the storage currently being used by both the Application Cache and
+ * Web SQL Database APIs by the given origin. The origin is specified using
+ * its string representation.
*/
public void deleteOrigin(String origin) {
if (origin != null) {
@@ -343,7 +380,9 @@ public final class WebStorage {
}
/**
- * Delete all databases
+ * Clear all storage currently being used by the JavaScript storage APIs.
+ * This includes the Application Cache, Web SQL Database and the HTML5 Web
+ * Storage APIs.
*/
public void deleteAllData() {
if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
@@ -381,8 +420,8 @@ public final class WebStorage {
}
/**
- * Get the global instance of WebStorage.
- * @return A single instance of WebStorage.
+ * Get the singleton instance of this class.
+ * @return The singleton {@link WebStorage} instance.
*/
public static WebStorage getInstance() {
if (sWebStorage == null) {
@@ -404,7 +443,7 @@ public final class WebStorage {
}
/**
- * Run on the webcore thread
+ * Run on the WebCore thread
* set the local values with the current ones
*/
private void syncValues() {
@@ -418,6 +457,16 @@ public final class WebStorage {
}
}
+ /**
+ * This class should not be instantiated directly, applications must only use
+ * {@link #getInstance()} to obtain the instance.
+ * Note this constructor was erroneously public and published in SDK levels prior to 16, but
+ * applications using it would receive a non-functional instance of this class (there was no
+ * way to call createHandler() and createUIHandler(), so it would not work).
+ * @hide
+ */
+ public WebStorage() {}
+
// Native functions
private static native Set nativeGetOrigins();
private static native long nativeGetUsageForOrigin(String origin);
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index d1cfc6b..5498622 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -1281,8 +1281,10 @@ public class WebView extends AbsoluteLayout
* @deprecated {@link #findAllAsync} is preferred.
* @see #setFindListener
*/
+ @Deprecated
public int findAll(String find) {
checkThread();
+ StrictMode.noteSlowCall("findAll blocks UI: prefer findAllAsync");
return mProvider.findAll(find);
}
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index 9895a87..893849b 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -828,6 +828,13 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
// 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;
+
+ // The rate at which edit text is scrolled in content pixels per millisecond
+ static private final float TEXT_SCROLL_RATE = 0.01f;
+
+ // The presumed scroll rate for the first scroll of edit text
+ static private final long TEXT_SCROLL_FIRST_SCROLL_MS = 16;
+
// true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK
private boolean mAutoRedraw;
@@ -853,6 +860,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
boolean mIsEditingText = false;
ArrayList<Message> mBatchedTextChanges = new ArrayList<Message>();
boolean mIsBatchingTextChanges = false;
+ private long mLastEditScroll = 0;
private static class OnTrimMemoryListener implements ComponentCallbacks2 {
private static OnTrimMemoryListener sInstance = null;
@@ -1037,9 +1045,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
// pages with the space bar, in pixels.
private static final int PAGE_SCROLL_OVERLAP = 24;
- // Time between successive calls to text scroll fling animation
- private static final int TEXT_SCROLL_ANIMATION_DELAY_MS = 16;
-
/**
* These prevent calling requestLayout if either dimension is fixed. This
* depends on the layout parameters and the measure specs.
@@ -1207,7 +1212,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
static final int RELOCATE_AUTO_COMPLETE_POPUP = 146;
static final int FOCUS_NODE_CHANGED = 147;
static final int AUTOFILL_FORM = 148;
- static final int ANIMATE_TEXT_SCROLL = 149;
+ static final int SCROLL_EDIT_TEXT = 149;
static final int EDIT_TEXT_SIZE_CHANGED = 150;
static final int SHOW_CARET_HANDLE = 151;
static final int UPDATE_CONTENT_BOUNDS = 152;
@@ -4672,6 +4677,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
* Select the word at the indicated content coordinates.
*/
boolean selectText(int x, int y) {
+ if (mWebViewCore == null) {
+ return false;
+ }
mWebViewCore.sendMessage(EventHub.SELECT_WORD_AT, x, y);
return true;
}
@@ -6002,9 +6010,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
data.mNativeLayer = nativeScrollableLayer(
contentX, contentY, data.mNativeLayerRect, null);
data.mSlop = viewToContentDimension(mNavSlop);
- mTouchHighlightRegion.setEmpty();
+ removeTouchHighlight();
if (!mBlockWebkitViewMessages) {
- mTouchHighlightRequested = System.currentTimeMillis();
+ mTouchHighlightRequested = SystemClock.uptimeMillis();
mWebViewCore.sendMessageAtFrontOfQueue(
EventHub.HIT_TEST, data);
}
@@ -6091,6 +6099,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mSelectDraggingTextQuad.containsPoint(handleX, handleY);
boolean inEditBounds = mEditTextContentBounds
.contains(handleX, handleY);
+ if (mIsEditingText && !inEditBounds) {
+ beginScrollEdit();
+ } else {
+ endScrollEdit();
+ }
if (inCursorText || (mIsEditingText && !inEditBounds)) {
snapDraggingCursor();
}
@@ -6240,6 +6253,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
break;
}
case MotionEvent.ACTION_UP: {
+ endScrollEdit();
if (!mConfirmMove && mIsEditingText && mSelectionStarted &&
mIsCaretSelection) {
showPasteWindow();
@@ -6335,6 +6349,86 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
}
+ /**
+ * Returns the text scroll speed in content pixels per millisecond based on
+ * the touch location.
+ * @param coordinate The x or y touch coordinate in content space
+ * @param min The minimum coordinate (x or y) of the edit content bounds
+ * @param max The maximum coordinate (x or y) of the edit content bounds
+ */
+ private static float getTextScrollSpeed(int coordinate, int min, int max) {
+ if (coordinate < min) {
+ return (coordinate - min) * TEXT_SCROLL_RATE;
+ } else if (coordinate >= max) {
+ return (coordinate - max + 1) * TEXT_SCROLL_RATE;
+ } else {
+ return 0.0f;
+ }
+ }
+
+ private void beginScrollEdit() {
+ if (mLastEditScroll == 0) {
+ mLastEditScroll = SystemClock.uptimeMillis() -
+ TEXT_SCROLL_FIRST_SCROLL_MS;
+ scrollEditWithCursor();
+ }
+ }
+
+ private void endScrollEdit() {
+ mLastEditScroll = 0;
+ }
+
+ private static int getTextScrollDelta(float speed, long deltaT) {
+ float distance = speed * deltaT;
+ int intDistance = (int)Math.floor(distance);
+ float probability = distance - intDistance;
+ if (Math.random() < probability) {
+ intDistance++;
+ }
+ return intDistance;
+ }
+ /**
+ * Scrolls edit text a distance based on the last touch point,
+ * the last scroll time, and the edit text content bounds.
+ */
+ private void scrollEditWithCursor() {
+ if (mLastEditScroll != 0) {
+ int x = viewToContentX(mLastTouchX + getScrollX() + mSelectDraggingOffset.x);
+ float scrollSpeedX = getTextScrollSpeed(x, mEditTextContentBounds.left,
+ mEditTextContentBounds.right);
+ int y = viewToContentY(mLastTouchY + getScrollY() + mSelectDraggingOffset.y);
+ float scrollSpeedY = getTextScrollSpeed(y, mEditTextContentBounds.top,
+ mEditTextContentBounds.bottom);
+ if (scrollSpeedX == 0.0f && scrollSpeedY == 0.0f) {
+ endScrollEdit();
+ } else {
+ long currentTime = SystemClock.uptimeMillis();
+ long timeSinceLastUpdate = currentTime - mLastEditScroll;
+ int deltaX = getTextScrollDelta(scrollSpeedX, timeSinceLastUpdate);
+ int deltaY = getTextScrollDelta(scrollSpeedY, timeSinceLastUpdate);
+ mLastEditScroll = currentTime;
+ if (deltaX == 0 && deltaY == 0) {
+ // By probability no text scroll this time. Try again later.
+ mPrivateHandler.sendEmptyMessageDelayed(SCROLL_EDIT_TEXT,
+ TEXT_SCROLL_FIRST_SCROLL_MS);
+ } else {
+ int scrollX = getTextScrollX() + deltaX;
+ scrollX = Math.min(getMaxTextScrollX(), scrollX);
+ scrollX = Math.max(0, scrollX);
+ int scrollY = getTextScrollY() + deltaY;
+ scrollY = Math.min(getMaxTextScrollY(), scrollY);
+ scrollY = Math.max(0, scrollY);
+ scrollEditText(scrollX, scrollY);
+ int cursorX = mSelectDraggingCursor.x;
+ int cursorY = mSelectDraggingCursor.y;
+ mSelectDraggingCursor.set(x - deltaX, y - deltaY);
+ updateWebkitSelection();
+ mSelectDraggingCursor.set(cursorX, cursorY);
+ }
+ }
+ }
+ }
+
private void startTouch(float x, float y, long eventTime) {
// Remember where the motion event started
mStartTouchX = mLastTouchX = Math.round(x);
@@ -7053,7 +7147,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
if (mFindIsUp) return false;
boolean result = false;
result = mWebViewPrivate.super_requestFocus(direction, previouslyFocusedRect);
- if (mWebViewCore.getSettings().getNeedInitialFocus() && !mWebView.isInTouchMode()) {
+ if (mWebViewCore.getSettings().getNeedInitialFocus()
+ && !mWebView.isInTouchMode()) {
// For cases such as GMail, where we gain focus from a direction,
// we want to move to the first available link.
// FIXME: If there are no visible links, we may not want to
@@ -7074,7 +7169,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
default:
return result;
}
- // TODO: Send initial focus request to webkit (b/6108927)
+ mWebViewCore.sendMessage(EventHub.SET_INITIAL_FOCUS, fakeKeyDirection);
}
return result;
}
@@ -7673,10 +7768,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
msg.arg1, /* unused */0);
break;
- case ANIMATE_TEXT_SCROLL:
- computeEditTextScroll();
- break;
-
case EDIT_TEXT_SIZE_CHANGED:
if (msg.arg1 == mFieldPointer) {
mEditTextContent.set((Rect)msg.obj);
@@ -7695,6 +7786,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mEditTextContentBounds.set((Rect) msg.obj);
break;
+ case SCROLL_EDIT_TEXT:
+ scrollEditWithCursor();
+ break;
+
default:
super.handleMessage(msg);
break;
@@ -7777,13 +7872,16 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
if (mFocusedNode.mHasFocus && mFocusedNode.mEditable) {
return false;
}
- long delay = System.currentTimeMillis() - mTouchHighlightRequested;
+ long delay = SystemClock.uptimeMillis() - mTouchHighlightRequested;
if (delay < ViewConfiguration.getTapTimeout()) {
Rect r = mTouchHighlightRegion.getBounds();
mWebView.postInvalidateDelayed(delay, r.left, r.top, r.right, r.bottom);
return false;
}
- return true;
+ if (mInputDispatcher == null) {
+ return false;
+ }
+ return mInputDispatcher.shouldShowTapHighlight();
}
@@ -7893,8 +7991,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
if (viewRect.width() < getWidth() >> 1
|| viewRect.height() < getHeight() >> 1) {
mTouchHighlightRegion.union(viewRect);
- } else {
- Log.w(LOGTAG, "Skip the huge selection rect:"
+ } else if (DebugFlags.WEB_VIEW) {
+ Log.d(LOGTAG, "Skip the huge selection rect:"
+ viewRect);
}
}
@@ -7995,16 +8093,17 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mWebView.invalidate();
}
- if (mPictureListener != null) {
- mPictureListener.onNewPicture(getWebView(), capturePicture());
- }
-
// update the zoom information based on the new picture
mZoomManager.onNewPicture(draw);
if (isPictureAfterFirstLayout) {
mViewManager.postReadyToDrawAll();
}
+ scrollEditWithCursor();
+
+ if (mPictureListener != null) {
+ mPictureListener.onNewPicture(getWebView(), capturePicture());
+ }
}
/**
@@ -8047,13 +8146,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
invalidate();
}
- private void computeEditTextScroll() {
- if (mEditTextScroller.computeScrollOffset()) {
- scrollEditText(mEditTextScroller.getCurrX(),
- mEditTextScroller.getCurrY());
- }
- }
-
private void scrollEditText(int scrollX, int scrollY) {
// Scrollable edit text. Scroll it.
float maxScrollX = getMaxTextScrollX();
@@ -8061,8 +8153,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mEditTextContent.offsetTo(-scrollX, -scrollY);
mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SCROLL_TEXT_INPUT, 0,
scrollY, (Float)scrollPercentX);
- mPrivateHandler.sendEmptyMessageDelayed(ANIMATE_TEXT_SCROLL,
- TEXT_SCROLL_ANIMATION_DELAY_MS);
}
private void beginTextBatch() {
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 15a2d48..e2880d6 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -394,10 +394,12 @@ public final class WebViewCore {
* Called by JNI. Open a file chooser to upload a file.
* @param acceptType The value of the 'accept' attribute of the
* input tag associated with this file picker.
+ * @param capture The value of the 'capture' attribute of the
+ * input tag associated with this file picker.
* @return String version of the URI.
*/
- private String openFileChooser(String acceptType) {
- Uri uri = mCallbackProxy.openFileChooser(acceptType);
+ private String openFileChooser(String acceptType, String capture) {
+ Uri uri = mCallbackProxy.openFileChooser(acceptType, capture);
if (uri != null) {
String filePath = "";
// Note - querying for MediaStore.Images.Media.DATA
@@ -428,38 +430,38 @@ public final class WebViewCore {
* Notify the browser that the origin has exceeded it's database quota.
* @param url The URL that caused the overflow.
* @param databaseIdentifier The identifier of the database.
- * @param currentQuota The current quota for the origin.
- * @param estimatedSize The estimated size of the database.
+ * @param quota The current quota for the origin.
+ * @param estimatedDatabaseSize The estimated size of the database.
*/
protected void exceededDatabaseQuota(String url,
String databaseIdentifier,
- long currentQuota,
- long estimatedSize) {
+ long quota,
+ long estimatedDatabaseSize) {
// Inform the callback proxy of the quota overflow. Send an object
// that encapsulates a call to the nativeSetDatabaseQuota method to
// awaken the sleeping webcore thread when a decision from the
// client to allow or deny quota is available.
mCallbackProxy.onExceededDatabaseQuota(url, databaseIdentifier,
- currentQuota, estimatedSize, getUsedQuota(),
+ quota, estimatedDatabaseSize, getUsedQuota(),
new WebStorage.QuotaUpdater() {
@Override
- public void updateQuota(long quota) {
- nativeSetNewStorageLimit(mNativeClass, quota);
+ public void updateQuota(long newQuota) {
+ nativeSetNewStorageLimit(mNativeClass, newQuota);
}
});
}
/**
* Notify the browser that the appcache has exceeded its max size.
- * @param spaceNeeded is the amount of disk space that would be needed
- * in order for the last appcache operation to succeed.
+ * @param requiredStorage is the amount of storage, in bytes, that would be
+ * needed in order for the last appcache operation to succeed.
*/
- protected void reachedMaxAppCacheSize(long spaceNeeded) {
- mCallbackProxy.onReachedMaxAppCacheSize(spaceNeeded, getUsedQuota(),
+ protected void reachedMaxAppCacheSize(long requiredStorage) {
+ mCallbackProxy.onReachedMaxAppCacheSize(requiredStorage, getUsedQuota(),
new WebStorage.QuotaUpdater() {
@Override
- public void updateQuota(long quota) {
- nativeSetNewStorageLimit(mNativeClass, quota);
+ public void updateQuota(long newQuota) {
+ nativeSetNewStorageLimit(mNativeClass, newQuota);
}
});
}
@@ -1174,6 +1176,7 @@ public final class WebViewCore {
// key was pressed (down and up)
static final int KEY_PRESS = 223;
+ static final int SET_INITIAL_FOCUS = 224;
// Private handler for WebCore messages.
private Handler mHandler;
@@ -1746,6 +1749,9 @@ public final class WebViewCore {
WebViewClassic.UPDATE_MATCH_COUNT, request).sendToTarget();
break;
}
+ case SET_INITIAL_FOCUS:
+ nativeSetInitialFocus(mNativeClass, msg.arg1);
+ break;
}
}
};
@@ -3069,6 +3075,7 @@ public final class WebViewCore {
private native void nativeClearTextSelection(int nativeClass);
private native boolean nativeSelectWordAt(int nativeClass, int x, int y);
private native void nativeSelectAll(int nativeClass);
+ private native void nativeSetInitialFocus(int nativeClass, int keyDirection);
private static native void nativeCertTrustChanged();
}
diff --git a/core/java/android/webkit/WebViewInputDispatcher.java b/core/java/android/webkit/WebViewInputDispatcher.java
index e7024d9..0a0afaa 100644
--- a/core/java/android/webkit/WebViewInputDispatcher.java
+++ b/core/java/android/webkit/WebViewInputDispatcher.java
@@ -269,9 +269,8 @@ final class WebViewInputDispatcher {
*/
public boolean postPointerEvent(MotionEvent event,
int webKitXOffset, int webKitYOffset, float webKitScale) {
- if (event == null
- || (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) {
- throw new IllegalArgumentException("event must be a pointer event");
+ if (event == null) {
+ throw new IllegalArgumentException("event cannot be null");
}
if (DEBUG) {
@@ -349,6 +348,12 @@ final class WebViewInputDispatcher {
}
}
+ public boolean shouldShowTapHighlight() {
+ synchronized (mLock) {
+ return mPostLongPressScheduled || mPostClickScheduled;
+ }
+ }
+
private void postLongPress() {
synchronized (mLock) {
if (!mPostLongPressScheduled) {
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 057aabe..e68049c 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -2062,6 +2062,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
child = mAdapter.getView(position, scrapView, this);
+ if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+
if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(child, ViewDebug.RecyclerTraceType.BIND_VIEW,
position, getChildCount());
@@ -2082,6 +2086,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
} else {
child = mAdapter.getView(position, null, this);
+
+ if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+
if (mCacheColorHint != 0) {
child.setDrawingCacheBackgroundColor(mCacheColorHint);
}
diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java
index efdfae3..f279f8e 100644
--- a/core/java/android/widget/AbsSpinner.java
+++ b/core/java/android/widget/AbsSpinner.java
@@ -191,6 +191,10 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
if (view == null) {
// Make a new one
view = mAdapter.getView(selectedPosition, null, this);
+
+ if (view.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ view.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
}
if (view != null) {
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index 97a864c..abfc577 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -24,14 +24,15 @@ import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
+import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
-
/**
* An AdapterView is a view whose children are determined by an {@link Adapter}.
*
@@ -232,6 +233,11 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
public AdapterView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+
+ // If not explicitly specified this view is important for accessibility.
+ if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
}
/**
@@ -643,6 +649,12 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
public void setEmptyView(View emptyView) {
mEmptyView = emptyView;
+ // If not explicitly specified this view is important for accessibility.
+ if (emptyView != null
+ && emptyView.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ emptyView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+
final T adapter = getAdapter();
final boolean empty = ((adapter == null) || adapter.isEmpty());
updateEmptyStatus(empty);
@@ -846,12 +858,14 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
}
} else {
fireOnSelected();
+ performAccessibilityActionsOnSelected();
}
}
}
void selectionChanged() {
- if (mOnItemSelectedListener != null) {
+ if (mOnItemSelectedListener != null
+ || AccessibilityManager.getInstance(mContext).isEnabled()) {
if (mInLayout || mBlockLayoutRequests) {
// If we are in a layout traversal, defer notification
// by posting. This ensures that the view tree is
@@ -863,20 +877,16 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
post(mSelectionNotifier);
} else {
fireOnSelected();
+ performAccessibilityActionsOnSelected();
}
}
-
- // we fire selection events here not in View
- if (mSelectedPosition != ListView.INVALID_POSITION && isShown() && !isInTouchMode()) {
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
- }
}
private void fireOnSelected() {
- if (mOnItemSelectedListener == null)
+ if (mOnItemSelectedListener == null) {
return;
-
- int selection = this.getSelectedItemPosition();
+ }
+ final int selection = getSelectedItemPosition();
if (selection >= 0) {
View v = getSelectedView();
mOnItemSelectedListener.onItemSelected(this, v, selection,
@@ -886,6 +896,17 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
}
}
+ private void performAccessibilityActionsOnSelected() {
+ if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
+ return;
+ }
+ final int position = getSelectedItemPosition();
+ if (position >= 0) {
+ // we fire selection events here not in View
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+ }
+ }
+
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
View selectedView = getSelectedView();
@@ -936,6 +957,24 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
event.setItemCount(getCount());
}
+ /**
+ * @hide
+ */
+ @Override
+ public boolean onRequestAccessibilityFocusFromHover(float x, float y) {
+ // We prefer to five focus to the child instead of this view.
+ // Usually the children are not actionable for accessibility,
+ // and they will not take accessibility focus, so we give it.
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ if (isTransformedTouchPointInView(x, y, child, null)) {
+ return child.requestAccessibilityFocus();
+ }
+ }
+ return super.onRequestAccessibilityFocusFromHover(x, y);
+ }
+
private boolean isScrollableForAccessibility() {
T adapter = getAdapter();
if (adapter != null) {
@@ -1012,6 +1051,9 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
mNeedSync = false;
checkSelectionChanged();
}
+
+ //TODO: Hmm, we do not know the old state so this is sub-optimal
+ notifyAccessibilityStateChanged();
}
void checkSelectionChanged() {
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index bb00049..c557963 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -414,6 +414,10 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
// get the fresh child from the adapter
final View updatedChild = mAdapter.getView(modulo(i, adapterCount), null, this);
+ if (updatedChild.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ updatedChild.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+
if (mViewsMap.containsKey(index)) {
final FrameLayout fl = (FrameLayout) mViewsMap.get(index).view;
// add the new child to the frame, if it exists
diff --git a/core/java/android/widget/CheckBox.java b/core/java/android/widget/CheckBox.java
index 0685eea..858c415 100644
--- a/core/java/android/widget/CheckBox.java
+++ b/core/java/android/widget/CheckBox.java
@@ -21,8 +21,6 @@ import android.util.AttributeSet;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
-import com.android.internal.R;
-
/**
* <p>
@@ -71,16 +69,6 @@ public class CheckBox extends CompoundButton {
}
@Override
- public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
- super.onPopulateAccessibilityEvent(event);
- if (isChecked()) {
- event.getText().add(mContext.getString(R.string.checkbox_checked));
- } else {
- event.getText().add(mContext.getString(R.string.checkbox_not_checked));
- }
- }
-
- @Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
event.setClassName(CheckBox.class.getName());
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index c5066b6..108b720 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -279,8 +279,13 @@ public class DatePicker extends FrameLayout {
// re-order the number spinners to match the current date format
reorderSpinners();
- // set content descriptions
+ // accessibility
setContentDescriptions();
+
+ // If not explicitly specified this view is important for accessibility.
+ if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
}
/**
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index cbff58c..040a385 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -1681,7 +1681,7 @@ public class Editor {
final int itemCount = clipData.getItemCount();
for (int i=0; i < itemCount; i++) {
Item item = clipData.getItemAt(i);
- content.append(item.coerceToText(mTextView.getContext()));
+ content.append(item.coerceToStyledText(mTextView.getContext()));
}
final int offset = mTextView.getOffsetForPosition(event.getX(), event.getY());
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
index 60dd55c..1cb676f 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Insets;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
@@ -559,9 +560,9 @@ public class GridLayout extends ViewGroup {
int flags = (gravity & mask) >> shift;
switch (flags) {
case (AXIS_SPECIFIED | AXIS_PULL_BEFORE):
- return LEADING;
+ return horizontal ? LEFT : TOP;
case (AXIS_SPECIFIED | AXIS_PULL_AFTER):
- return TRAILING;
+ return horizontal ? RIGHT : BOTTOM;
case (AXIS_SPECIFIED | AXIS_PULL_BEFORE | AXIS_PULL_AFTER):
return FILL;
case AXIS_SPECIFIED:
@@ -1042,12 +1043,15 @@ public class GridLayout extends ViewGroup {
int rightMargin = getMargin(c, true, false);
int bottomMargin = getMargin(c, false, false);
+ int sumMarginsX = leftMargin + rightMargin;
+ int sumMarginsY = topMargin + bottomMargin;
+
// Alignment offsets: the location of the view relative to its alignment group.
- int alignmentOffsetX = boundsX.getOffset(c, hAlign, leftMargin + pWidth + rightMargin);
- int alignmentOffsetY = boundsY.getOffset(c, vAlign, topMargin + pHeight + bottomMargin);
+ int alignmentOffsetX = boundsX.getOffset(this, c, hAlign, pWidth + sumMarginsX, true);
+ int alignmentOffsetY = boundsY.getOffset(this, c, vAlign, pHeight + sumMarginsY, false);
- int width = hAlign.getSizeInCell(c, pWidth, cellWidth - leftMargin - rightMargin);
- int height = vAlign.getSizeInCell(c, pHeight, cellHeight - topMargin - bottomMargin);
+ int width = hAlign.getSizeInCell(c, pWidth, cellWidth - sumMarginsX);
+ int height = vAlign.getSizeInCell(c, pHeight, cellHeight - sumMarginsY);
int dx = x1 + gravityOffsetX + alignmentOffsetX;
@@ -1181,7 +1185,7 @@ public class GridLayout extends ViewGroup {
View c = getChildAt(i);
LayoutParams lp = getLayoutParams(c);
Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
- groupBounds.getValue(i).include(c, spec, GridLayout.this, this);
+ groupBounds.getValue(i).include(GridLayout.this, c, spec, this);
}
}
@@ -2138,16 +2142,30 @@ public class GridLayout extends ViewGroup {
return before + after;
}
- protected int getOffset(View c, Alignment alignment, int size) {
- return before - alignment.getAlignmentValue(c, size);
+ private int getAlignmentValue(GridLayout gl, View c, int size, Alignment a, boolean horiz) {
+ boolean useLayoutBounds = gl.getLayoutMode() == LAYOUT_BOUNDS;
+ if (!useLayoutBounds) {
+ return a.getAlignmentValue(c, size);
+ } else {
+ Insets insets = c.getLayoutInsets();
+ int leadingInset = horiz ? insets.left : insets.top; // RTL?
+ int trailingInset = horiz ? insets.right : insets.bottom; // RTL?
+ int totalInset = leadingInset + trailingInset;
+ return leadingInset + a.getAlignmentValue(c, size - totalInset);
+ }
+ }
+
+ protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean horizontal) {
+ return before - getAlignmentValue(gl, c, size, a, horizontal);
}
- protected final void include(View c, Spec spec, GridLayout gridLayout, Axis axis) {
+ protected final void include(GridLayout gl, View c, Spec spec, Axis axis) {
this.flexibility &= spec.getFlexibility();
- int size = gridLayout.getMeasurementIncludingMargin(c, axis.horizontal);
- Alignment alignment = gridLayout.getAlignment(spec.alignment, axis.horizontal);
+ boolean horizontal = axis.horizontal;
+ int size = gl.getMeasurementIncludingMargin(c, horizontal);
+ Alignment alignment = gl.getAlignment(spec.alignment, horizontal);
// todo test this works correctly when the returned value is UNDEFINED
- int before = alignment.getAlignmentValue(c, size);
+ int before = getAlignmentValue(gl, c, size, alignment, horizontal);
include(before, size - before);
}
@@ -2614,8 +2632,8 @@ public class GridLayout extends ViewGroup {
}
@Override
- protected int getOffset(View c, Alignment alignment, int size) {
- return max(0, super.getOffset(c, alignment, size));
+ protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean hrz) {
+ return max(0, super.getOffset(gl, c, a, size, hrz));
}
};
}
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 91e2e49..6c7ea67 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -105,11 +105,11 @@ public class ImageView extends View {
super(context);
initImageView();
}
-
+
public ImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
-
+
public ImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initImageView();
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index d897a39..992849d 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -112,8 +112,7 @@ public class NumberPicker extends LinearLayout {
private static final int SELECTOR_ADJUSTMENT_DURATION_MILLIS = 800;
/**
- * The duration of scrolling to the next/previous value while snapping to
- * a given position.
+ * The duration of scrolling while snapping to a given position.
*/
private static final int SNAP_SCROLL_DURATION = 300;
@@ -579,7 +578,7 @@ public class NumberPicker extends LinearLayout {
throw new IllegalArgumentException("minWidth > maxWidth");
}
- mComputeMaxWidth = (mMaxWidth == Integer.MAX_VALUE);
+ mComputeMaxWidth = (mMaxWidth == SIZE_UNSPECIFIED);
attributesArray.recycle();
@@ -680,6 +679,11 @@ public class NumberPicker extends LinearLayout {
mAdjustScroller = new Scroller(getContext(), new DecelerateInterpolator(2.5f));
updateInputTextView();
+
+ // If not explicitly specified this view is important for accessibility.
+ if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
}
@Override
@@ -771,6 +775,8 @@ public class NumberPicker extends LinearLayout {
mLastDownEventTime = event.getEventTime();
mIngonreMoveEvents = false;
mShowSoftInputOnTap = false;
+ // Make sure we wupport flinging inside scrollables.
+ getParent().requestDisallowInterceptTouchEvent(true);
if (!mFlingScroller.isFinished()) {
mFlingScroller.forceFinished(true);
mAdjustScroller.forceFinished(true);
@@ -1096,12 +1102,7 @@ public class NumberPicker extends LinearLayout {
* @see #setMaxValue(int)
*/
public void setValue(int value) {
- if (mValue == value) {
- return;
- }
setValueInternal(value, false);
- initializeSelectorWheelIndices();
- invalidate();
}
/**
@@ -1498,6 +1499,8 @@ public class NumberPicker extends LinearLayout {
if (notifyChange) {
notifyChange(previous, current);
}
+ initializeSelectorWheelIndices();
+ invalidate();
}
/**
diff --git a/core/java/android/widget/ShareActionProvider.java b/core/java/android/widget/ShareActionProvider.java
index 22e9ef1..080b87d 100644
--- a/core/java/android/widget/ShareActionProvider.java
+++ b/core/java/android/widget/ShareActionProvider.java
@@ -80,16 +80,22 @@ public class ShareActionProvider extends ActionProvider {
/**
* Called when a share target has been selected. The client can
- * decide whether to handle the intent or rely on the default
- * behavior which is launching it.
+ * decide whether to perform some action before the sharing is
+ * actually performed.
* <p>
* <strong>Note:</strong> Modifying the intent is not permitted and
* any changes to the latter will be ignored.
* </p>
+ * <p>
+ * <strong>Note:</strong> You should <strong>not</strong> handle the
+ * intent here. This callback aims to notify the client that a
+ * sharing is being performed, so the client can update the UI
+ * if necessary.
+ * </p>
*
* @param source The source of the notification.
* @param intent The intent for launching the chosen share target.
- * @return Whether the client has handled the intent.
+ * @return The return result is ignored. Always return false for consistency.
*/
public boolean onShareTargetSelected(ShareActionProvider source, Intent intent);
}
@@ -308,7 +314,7 @@ public class ShareActionProvider extends ActionProvider {
@Override
public boolean onChooseActivity(ActivityChooserModel host, Intent intent) {
if (mOnShareTargetSelectedListener != null) {
- return mOnShareTargetSelectedListener.onShareTargetSelected(
+ mOnShareTargetSelectedListener.onShareTargetSelected(
ShareActionProvider.this, intent);
}
return false;
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index aef8a34..36d1ee0 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -907,7 +907,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
public void onItemClick(AdapterView parent, View v, int position, long id) {
Spinner.this.setSelection(position);
if (mOnItemClickListener != null) {
- Spinner.this.performItemClick(null, position, mAdapter.getItemId(position));
+ Spinner.this.performItemClick(v, position, mAdapter.getItemId(position));
}
dismiss();
}
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index a897cc3..0786909 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -806,5 +806,16 @@ public class Switch extends CompoundButton {
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName(Switch.class.getName());
+ CharSequence switchText = isChecked() ? mTextOn : mTextOff;
+ if (!TextUtils.isEmpty(switchText)) {
+ CharSequence oldText = info.getText();
+ if (TextUtils.isEmpty(oldText)) {
+ info.setText(switchText);
+ } else {
+ StringBuilder newText = new StringBuilder();
+ newText.append(oldText).append(' ').append(switchText);
+ info.setText(newText);
+ }
+ }
}
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 9867e47..37d9db7 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -1105,6 +1105,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
setLongClickable(longClickable);
if (mEditor != null) mEditor.prepareCursorControllers();
+
+ // If not explicitly specified this view is important for accessibility.
+ if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
}
private void setTypefaceByIndex(int typefaceIndex, int styleIndex) {
@@ -7710,7 +7715,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (clip != null) {
boolean didFirst = false;
for (int i=0; i<clip.getItemCount(); i++) {
- CharSequence paste = clip.getItemAt(i).coerceToText(getContext());
+ CharSequence paste = clip.getItemAt(i).coerceToStyledText(getContext());
if (paste != null) {
if (!didFirst) {
long minMax = prepareSpacesAroundPaste(min, max, paste);
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index bc88b62..18f7a91 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -251,6 +251,11 @@ public class TimePicker extends FrameLayout {
// set the content descriptions
setContentDescriptions();
+
+ // If not explicitly specified this view is important for accessibility.
+ if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
}
@Override