summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java68
-rw-r--r--core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl64
-rw-r--r--core/java/android/util/FinitePool.java5
-rw-r--r--core/java/android/util/Poolable.java2
-rw-r--r--core/java/android/view/VelocityTracker.java15
-rw-r--r--core/java/android/view/View.java146
-rw-r--r--core/java/android/view/ViewAncestor.java530
-rw-r--r--core/java/android/view/ViewGroup.java23
-rw-r--r--core/java/android/view/accessibility/AccessibilityEvent.java188
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java103
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.aidl19
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java947
-rw-r--r--core/java/android/view/accessibility/AccessibilityRecord.java146
-rw-r--r--core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl41
-rw-r--r--core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl36
-rw-r--r--core/java/android/view/accessibility/IAccessibilityManager.aidl12
-rw-r--r--core/java/android/widget/RelativeLayout.java9
-rw-r--r--core/java/android/widget/TextView.java25
-rw-r--r--core/java/com/android/internal/view/BaseIWindow.java4
19 files changed, 2297 insertions, 86 deletions
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 28fc21a..8bb305d 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -25,6 +25,7 @@ import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
/**
* An accessibility service runs in the background and receives callbacks by the system
@@ -51,7 +52,11 @@ import android.view.accessibility.AccessibilityEvent;
* enabling or disabling it in the device settings. After the system binds to a service it
* calls {@link AccessibilityService#onServiceConnected()}. This method can be
* overriden by clients that want to perform post binding setup.
+ * </p>
* <p>
+ * An accessibility service can be configured to receive specific types of accessibility events,
+ * listen only to specific packages, get events from each type only once in a given time frame,
+ * retrieve window content, specify a settings activity, etc.
* </p>
* There are two approaches for configuring an accessibility service:
* <ul>
@@ -59,20 +64,23 @@ import android.view.accessibility.AccessibilityEvent;
* Providing a {@link #SERVICE_META_DATA meta-data} entry in the manifest when declaring
* the service. A service declaration with a meta-data tag is presented below:
* <p>
- * <code>
- * &lt;service android:name=".MyAccessibilityService"&gt;<br>
- * &nbsp;&nbsp;&lt;intent-filter&gt;<br>
- * &nbsp;&nbsp;&nbsp;&nbsp;&lt;action android:name="android.accessibilityservice.AccessibilityService" /&gt;<br>
- * &nbsp;&nbsp;&lt;/intent-filter&gt;<br>
- * &nbsp;&nbsp;&lt;meta-data android:name="android.accessibilityservice.as" android:resource="@xml/accessibilityservice" /&gt;<br>
- * &lt;/service&gt;<br>
- * </code>
+ * <code>
+ * &lt;service android:name=".MyAccessibilityService"&gt;<br>
+ * &nbsp;&nbsp;&lt;intent-filter&gt;<br>
+ * &nbsp;&nbsp;&nbsp;&nbsp;&lt;action android:name="android.accessibilityservice.AccessibilityService" /&gt;<br>
+ * &nbsp;&nbsp;&lt;/intent-filter&gt;<br>
+ * &nbsp;&nbsp;&lt;meta-data android:name="android.accessibilityservice.as" android:resource="@xml/accessibilityservice" /&gt;<br>
+ * &lt;/service&gt;<br>
+ * </code>
* </p>
* <p>
* <strong>
* This approach enables setting all accessibility service properties.
* </strong>
* </p>
+ * <p>
+ * For more details refer to {@link #SERVICE_META_DATA}.
+ * </p>
* </li>
* <li>
* Calling {@link AccessibilityService#setServiceInfo(AccessibilityServiceInfo)}. Note
@@ -88,6 +96,9 @@ import android.view.accessibility.AccessibilityEvent;
* {@link AccessibilityServiceInfo#packageNames}
* </strong>
* </p>
+ * <p>
+ * For more details refer to {@link AccessibilityServiceInfo}.
+ * </p>
* </li>
* </ul>
* <p>
@@ -151,16 +162,49 @@ public abstract class AccessibilityService extends Service {
* <code>
* &lt;?xml version="1.0" encoding="utf-8"?&gt;<br>
* &lt;accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"<br>
- * &nbsp;&nbsp;android:eventTypes="typeViewClicked|typeViewFocused"<br>
+ * &nbsp;&nbsp;android:accessibilityEventTypes="typeViewClicked|typeViewFocused"<br>
* &nbsp;&nbsp;android:packageNames="foo.bar, foo.baz"<br>
- * &nbsp;&nbsp;android:feedbackType="feedbackSpoken"<br>
+ * &nbsp;&nbsp;android:accessibilityFeedbackType="feedbackSpoken"<br>
* &nbsp;&nbsp;android:notificationTimeout="100"<br>
- * &nbsp;&nbsp;android:flags="flagDefault"<br>
+ * &nbsp;&nbsp;android:accessibilityFlags="flagDefault"<br>
* &nbsp;&nbsp;android:settingsActivity="foo.bar.TestBackActivity"<br>
* &nbsp;&nbsp;. . .<br>
* /&gt;
* </code>
* </p>
+ * <p>
+ * <strong>Note:</strong> A service can retrieve only the content of the active window.
+ * An active window is the source of the most recent event of type
+ * {@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START},
+ * {@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END},
+ * {@link AccessibilityEvent#TYPE_VIEW_CLICKED},
+ * {@link AccessibilityEvent#TYPE_VIEW_FOCUSED},
+ * {@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER},
+ * {@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT},
+ * {@link AccessibilityEvent#TYPE_VIEW_LONG_CLICKED},
+ * {@link AccessibilityEvent#TYPE_VIEW_SELECTED},
+ * {@link AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED},
+ * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}.
+ * Therefore the service should:
+ * <ul>
+ * <li>
+ * Register for all event types with no notification timeout and keep track
+ * for the active window by calling
+ * {@link AccessibilityEvent#getAccessibilityWindowId()} of the last received
+ * event and compare this with the
+ * {@link AccessibilityNodeInfo#getAccessibilityWindowId()} before calling
+ * retrieval methods on the latter.
+ * </li>
+ * <li>
+ * Prepare that a retrieval method on {@link AccessibilityNodeInfo} may fail
+ * since the active window has changed and the service did not get the
+ * accessibility event. Note that it is possible to have a retrieval method
+ * failing event adopting the strategy specified in the previous bullet
+ * because the accessibility event dispatch is asynchronous and crosses
+ * process boundaries.
+ * </li>
+ * <ul>
+ * </p>
*/
public static final String SERVICE_META_DATA = "android.accessibilityservice";
@@ -224,7 +268,7 @@ public abstract class AccessibilityService extends Service {
/**
* Implement to return the implementation of the internal accessibility
- * service interface. Subclasses should not override.
+ * service interface.
*/
@Override
public final IBinder onBind(Intent intent) {
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 7157def..19f0bf0 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -17,14 +17,72 @@
package android.accessibilityservice;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.view.accessibility.AccessibilityNodeInfo;
/**
- * Interface AccessibilityManagerService#Service implements, and passes to an
- * AccessibilityService so it can dynamically configure how the system handles it.
+ * Interface given to an AccessibilitySerivce to talk to the AccessibilityManagerService.
*
* @hide
*/
-oneway interface IAccessibilityServiceConnection {
+interface IAccessibilityServiceConnection {
void setServiceInfo(in AccessibilityServiceInfo info);
+
+ /**
+ * Finds an {@link AccessibilityNodeInfo} by accessibility id.
+ * <p>
+ * <strong>
+ * It is a client responsibility to recycle the received info by
+ * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating
+ * of multiple instances.
+ * </strong>
+ * </p>
+ *
+ * @param accessibilityWindowId A unique window id.
+ * @param accessibilityViewId A unique View accessibility id.
+ * @return The node info.
+ */
+ AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
+ int accessibilityViewId);
+
+ /**
+ * Finds {@link AccessibilityNodeInfo}s by View text. The match is case
+ * insensitive containment.
+ * <p>
+ * <strong>
+ * It is a client responsibility to recycle the received infos by
+ * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating
+ * of multiple instances.
+ * </strong>
+ * </p>
+ *
+ * @param text The searched text.
+ * @return A list of node info.
+ */
+ List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewText(String text);
+
+ /**
+ * Finds an {@link AccessibilityNodeInfo} by View id.
+ * <p>
+ * <strong>
+ * It is a client responsibility to recycle the received info by
+ * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating
+ * of multiple instances.
+ * </strong>
+ * </p>
+ *
+ * @param id The id of the node.
+ * @return The node info.
+ */
+ AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int viewId);
+
+ /**
+ * Performs an accessibility action on an {@link AccessibilityNodeInfo}.
+ *
+ * @param accessibilityWindowId The id of the window.
+ * @param accessibilityViewId The of a view in the .
+ * @return Whether the action was performed.
+ */
+ boolean performAccessibilityAction(int accessibilityWindowId, int accessibilityViewId,
+ int action);
}
diff --git a/core/java/android/util/FinitePool.java b/core/java/android/util/FinitePool.java
index 3ef8293..4ae21ad 100644
--- a/core/java/android/util/FinitePool.java
+++ b/core/java/android/util/FinitePool.java
@@ -69,6 +69,7 @@ class FinitePool<T extends Poolable<T>> implements Pool<T> {
if (element != null) {
element.setNextPoolable(null);
+ element.setPooled(false);
mManager.onAcquired(element);
}
@@ -76,9 +77,13 @@ class FinitePool<T extends Poolable<T>> implements Pool<T> {
}
public void release(T element) {
+ if (element.isPooled()) {
+ throw new IllegalArgumentException("Element already in the pool.");
+ }
if (mInfinite || mPoolCount < mLimit) {
mPoolCount++;
element.setNextPoolable(mRoot);
+ element.setPooled(true);
mRoot = element;
}
mManager.onReleased(element);
diff --git a/core/java/android/util/Poolable.java b/core/java/android/util/Poolable.java
index fd9bd9b..87e0529 100644
--- a/core/java/android/util/Poolable.java
+++ b/core/java/android/util/Poolable.java
@@ -22,4 +22,6 @@ package android.util;
public interface Poolable<T> {
void setNextPoolable(T element);
T getNextPoolable();
+ boolean isPooled();
+ void setPooled(boolean isPooled);
}
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index fccef2b..5a91d31 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -50,6 +50,7 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
private int mPtr;
private VelocityTracker mNext;
+ private boolean mIsPooled;
private static native int nativeInitialize();
private static native void nativeDispose(int ptr);
@@ -93,6 +94,20 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
return mNext;
}
+ /**
+ * @hide
+ */
+ public boolean isPooled() {
+ return mIsPooled;
+ }
+
+ /**
+ * @hide
+ */
+ public void setPooled(boolean isPooled) {
+ mIsPooled = isPooled;
+ }
+
private VelocityTracker() {
mPtr = nativeInitialize();
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 014f19f..51eb13b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -61,6 +61,7 @@ import android.view.ContextMenu.ContextMenuInfo;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityEventSource;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.EditorInfo;
@@ -1492,6 +1493,11 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit
private static final Object sTagsLock = new Object();
/**
+ * The next available accessiiblity id.
+ */
+ private static int sNextAccessibilityViewId;
+
+ /**
* The animation currently associated with this view.
* @hide
*/
@@ -1533,6 +1539,11 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit
int mID = NO_ID;
/**
+ * The stable ID of this view for accessibility porposes.
+ */
+ int mAccessibilityViewId = NO_ID;
+
+ /**
* The view's tag.
* {@hide}
*
@@ -3649,6 +3660,7 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit
* @see #dispatchPopulateAccessibilityEvent(AccessibilityEvent)
*/
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ event.setSource(this);
event.setClassName(getClass().getName());
event.setPackageName(getContext().getPackageName());
event.setEnabled(isEnabled());
@@ -3664,6 +3676,112 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit
}
/**
+ * Returns an {@link AccessibilityNodeInfo} representing this view from the
+ * point of view of an {@link android.accessibilityservice.AccessibilityService}.
+ * This method is responsible for obtaining an accessibility node info from a
+ * pool of reusable instances and calling
+ * {@link #onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)} on this view to
+ * initialize the former.
+ * <p>
+ * Note: The client is responsible for recycling the obtained instance by calling
+ * {@link AccessibilityNodeInfo#recycle()} to minimize object creation.
+ * </p>
+ * @return A populated {@link AccessibilityNodeInfo}.
+ *
+ * @see AccessibilityNodeInfo
+ */
+ public AccessibilityNodeInfo createAccessibilityNodeInfo() {
+ AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(this);
+ onInitializeAccessibilityNodeInfo(info);
+ return info;
+ }
+
+ /**
+ * Initializes an {@link AccessibilityNodeInfo} with information about this view.
+ * The base implementation sets:
+ * <ul>
+ * <li>{@link AccessibilityNodeInfo#setParent(View)},</li>
+ * <li>{@link AccessibilityNodeInfo#setBounds(Rect)},</li>
+ * <li>{@link AccessibilityNodeInfo#setPackageName(CharSequence)},</li>
+ * <li>{@link AccessibilityNodeInfo#setClassName(CharSequence)},</li>
+ * <li>{@link AccessibilityNodeInfo#setContentDescription(CharSequence)},</li>
+ * <li>{@link AccessibilityNodeInfo#setEnabled(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfo#setClickable(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfo#setFocusable(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfo#setFocused(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfo#setLongClickable(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfo#setSelected(boolean)},</li>
+ * </ul>
+ * <p>
+ * Subclasses should override this method, call the super implementation,
+ * and set additional attributes.
+ * </p>
+ * @param info The instance to initialize.
+ */
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ Rect bounds = mAttachInfo.mTmpInvalRect;
+ getDrawingRect(bounds);
+ info.setBounds(bounds);
+
+ ViewParent parent = getParent();
+ if (parent instanceof View) {
+ View parentView = (View) parent;
+ info.setParent(parentView);
+ }
+
+ info.setPackageName(mContext.getPackageName());
+ info.setClassName(getClass().getName());
+ info.setContentDescription(getContentDescription());
+
+ info.setEnabled(isEnabled());
+ info.setClickable(isClickable());
+ info.setFocusable(isFocusable());
+ info.setFocused(isFocused());
+ info.setSelected(isSelected());
+ info.setLongClickable(isLongClickable());
+
+ // TODO: These make sense only if we are in an AdapterView but all
+ // views can be selected. Maybe from accessiiblity perspective
+ // we should report as selectable view in an AdapterView.
+ info.addAction(AccessibilityNodeInfo.ACTION_SELECT);
+ info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION);
+
+ if (isFocusable()) {
+ if (isFocused()) {
+ info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_FOCUS);
+ } else {
+ info.addAction(AccessibilityNodeInfo.ACTION_FOCUS);
+ }
+ }
+ }
+
+ /**
+ * Gets the unique identifier of this view on the screen for accessibility purposes.
+ * If this {@link View} is not attached to any window, {@value #NO_ID} is returned.
+ *
+ * @return The view accessibility id.
+ *
+ * @hide
+ */
+ public int getAccessibilityViewId() {
+ if (mAccessibilityViewId == NO_ID) {
+ mAccessibilityViewId = sNextAccessibilityViewId++;
+ }
+ return mAccessibilityViewId;
+ }
+
+ /**
+ * Gets the unique identifier of the window in which this View reseides.
+ *
+ * @return The window accessibility id.
+ *
+ * @hide
+ */
+ public int getAccessibilityWindowId() {
+ return mAttachInfo != null ? mAttachInfo.mAccessibilityWindowId : NO_ID;
+ }
+
+ /**
* Gets the {@link View} description. It briefly describes the view and is
* primarily used for accessibility support. Set this property to enable
* better accessibility support for your application. This is especially
@@ -4571,6 +4689,16 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit
}
/**
+ * Finds the Views that contain given text. The containment is case insensitive.
+ * As View's text is considered any text content that View renders.
+ *
+ * @param outViews The output list of matching Views.
+ * @param text The text to match against.
+ */
+ public void findViewsWithText(ArrayList<View> outViews, CharSequence text) {
+ }
+
+ /**
* Find and return all touchable views that are descendants of this view,
* possibly including this view if it is touchable itself.
*
@@ -4677,8 +4805,8 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit
// need to be focusable in touch mode if in touch mode
if (isInTouchMode() &&
- (FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
- return false;
+ (FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
+ return false;
}
// need to not have any parents blocking us
@@ -12719,6 +12847,7 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit
);
private InvalidateInfo mNext;
+ private boolean mIsPooled;
View target;
@@ -12742,6 +12871,14 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit
void release() {
sPool.release(this);
}
+
+ public boolean isPooled() {
+ return mIsPooled;
+ }
+
+ public void setPooled(boolean isPooled) {
+ mIsPooled = isPooled;
+ }
}
final IWindowSession mSession;
@@ -12952,6 +13089,11 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit
final ArrayList<View> mFocusablesTempList = new ArrayList<View>(24);
/**
+ * The id of the window for accessibility purposes.
+ */
+ int mAccessibilityWindowId = View.NO_ID;
+
+ /**
* Creates a new set of attachment information with the specified
* events handler and thread.
*
diff --git a/core/java/android/view/ViewAncestor.java b/core/java/android/view/ViewAncestor.java
index 1e72529..9ab4c82 100644
--- a/core/java/android/view/ViewAncestor.java
+++ b/core/java/android/view/ViewAncestor.java
@@ -50,18 +50,28 @@ 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.SparseArray;
import android.util.TypedValue;
import android.view.View.MeasureSpec;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.IAccessibilityInteractionConnection;
+import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.Scroller;
+
import com.android.internal.policy.PolicyManager;
+import com.android.internal.util.Predicate;
import com.android.internal.view.BaseSurfaceHolder;
import com.android.internal.view.IInputMethodCallback;
import com.android.internal.view.IInputMethodSession;
@@ -71,6 +81,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.List;
/**
* The top of a view hierarchy, implementing the needed protocol between View
@@ -248,6 +259,12 @@ public final class ViewAncestor extends Handler implements ViewParent,
*/
AudioManager mAudioManager;
+ final AccessibilityManager mAccessibilityManager;
+
+ AccessibilityInteractionController mAccessibilityInteractionContrtoller;
+
+ AccessibilityInteractionConnectionManager mAccessibilityInteractionConnectionManager;
+
private final int mDensity;
/**
@@ -285,7 +302,7 @@ public final class ViewAncestor extends Handler implements ViewParent,
// done here instead of in the static block because Zygote does not
// allow the spawning of threads.
getWindowSession(context.getMainLooper());
-
+
mThread = Thread.currentThread();
mLocation = new WindowLeaked(null);
mLocation.fillInStackTrace();
@@ -302,6 +319,11 @@ public final class ViewAncestor extends Handler implements ViewParent,
mPreviousTransparentRegion = new Region();
mFirst = true; // true for the first time the view is added
mAdded = false;
+ mAccessibilityManager = AccessibilityManager.getInstance(context);
+ mAccessibilityInteractionConnectionManager =
+ new AccessibilityInteractionConnectionManager();
+ mAccessibilityManager.addAccessibilityStateChangeListener(
+ mAccessibilityInteractionConnectionManager);
mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, this, this);
mViewConfiguration = ViewConfiguration.get(context);
mDensity = context.getResources().getDisplayMetrics().densityDpi;
@@ -490,10 +512,14 @@ public final class ViewAncestor extends Handler implements ViewParent,
InputQueue.registerInputChannel(mInputChannel, mInputHandler,
Looper.myQueue());
}
-
+
view.assignParent(this);
mAddedTouchMode = (res&WindowManagerImpl.ADD_FLAG_IN_TOUCH_MODE) != 0;
mAppVisible = (res&WindowManagerImpl.ADD_FLAG_APP_VISIBLE) != 0;
+
+ if (mAccessibilityManager.isEnabled()) {
+ mAccessibilityInteractionConnectionManager.ensureConnection();
+ }
}
}
}
@@ -2004,6 +2030,10 @@ public final class ViewAncestor extends Handler implements ViewParent,
mView.dispatchDetachedFromWindow();
}
+ mAccessibilityInteractionConnectionManager.ensureNoConnection();
+ mAccessibilityManager.removeAccessibilityStateChangeListener(
+ mAccessibilityInteractionConnectionManager);
+
mView = null;
mAttachInfo.mRootView = null;
mAttachInfo.mSurface = null;
@@ -2020,7 +2050,6 @@ public final class ViewAncestor extends Handler implements ViewParent,
InputQueue.unregisterInputChannel(mInputChannel);
}
}
-
try {
sWindowSession.remove(mWindow);
} catch (RemoteException e) {
@@ -2098,6 +2127,10 @@ public final class ViewAncestor extends Handler implements ViewParent,
public final static int DISPATCH_DRAG_LOCATION_EVENT = 1016;
public final static int DISPATCH_SYSTEM_UI_VISIBILITY = 1017;
public final static int DISPATCH_GENERIC_MOTION = 1018;
+ public final static int DO_PERFORM_ACCESSIBILITY_ACTION = 1019;
+ public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 1020;
+ public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 1021;
+ public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT = 1022;
@Override
public void handleMessage(Message msg) {
@@ -2298,9 +2331,25 @@ public final class ViewAncestor extends Handler implements ViewParent,
case DISPATCH_SYSTEM_UI_VISIBILITY: {
handleDispatchSystemUiVisibilityChanged(msg.arg1);
} break;
+ case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
+ getAccessibilityInteractionController()
+ .findAccessibilityNodeInfoByAccessibilityIdUiThread(msg);
+ } break;
+ case DO_PERFORM_ACCESSIBILITY_ACTION: {
+ getAccessibilityInteractionController()
+ .perfromAccessibilityActionUiThread(msg);
+ } break;
+ case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID: {
+ getAccessibilityInteractionController()
+ .findAccessibilityNodeInfoByViewIdUiThread(msg);
+ } break;
+ case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT: {
+ getAccessibilityInteractionController()
+ .findAccessibilityNodeInfosByViewTextUiThread(msg);
+ } break;
}
}
-
+
private void startInputEvent(InputQueue.FinishedCallback finishedCallback) {
if (mFinishedCallback != null) {
Slog.w(TAG, "Received a new input event from the input queue but there is "
@@ -3220,6 +3269,17 @@ public final class ViewAncestor extends Handler implements ViewParent,
return mAudioManager;
}
+ public AccessibilityInteractionController getAccessibilityInteractionController() {
+ if (mView == null) {
+ throw new IllegalStateException("getAccessibilityInteractionController"
+ + " called when there is no mView");
+ }
+ if (mAccessibilityInteractionContrtoller == null) {
+ mAccessibilityInteractionContrtoller = new AccessibilityInteractionController();
+ }
+ return mAccessibilityInteractionContrtoller;
+ }
+
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
@@ -3517,7 +3577,7 @@ public final class ViewAncestor extends Handler implements ViewParent,
* send an {@link AccessibilityEvent} to announce that.
*/
private void sendAccessibilityEvents() {
- if (!AccessibilityManager.getInstance(mView.getContext()).isEnabled()) {
+ if (!mAccessibilityManager.isEnabled()) {
return;
}
mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
@@ -3545,7 +3605,7 @@ public final class ViewAncestor extends Handler implements ViewParent,
if (mView == null) {
return false;
}
- AccessibilityManager.getInstance(child.mContext).sendAccessibilityEvent(event);
+ mAccessibilityManager.sendAccessibilityEvent(event);
return true;
}
@@ -3608,18 +3668,18 @@ public final class ViewAncestor extends Handler implements ViewParent,
static class InputMethodCallback extends IInputMethodCallback.Stub {
private WeakReference<ViewAncestor> mViewAncestor;
- public InputMethodCallback(ViewAncestor viewRoot) {
- mViewAncestor = new WeakReference<ViewAncestor>(viewRoot);
+ public InputMethodCallback(ViewAncestor viewAncestor) {
+ mViewAncestor = new WeakReference<ViewAncestor>(viewAncestor);
}
public void finishedEvent(int seq, boolean handled) {
- final ViewAncestor viewRoot = mViewAncestor.get();
- if (viewRoot != null) {
- viewRoot.dispatchFinishedEvent(seq, handled);
+ final ViewAncestor viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchFinishedEvent(seq, handled);
}
}
- public void sessionCreated(IInputMethodSession session) throws RemoteException {
+ public void sessionCreated(IInputMethodSession session) {
// Stub -- not for use in the client.
}
}
@@ -3627,36 +3687,37 @@ public final class ViewAncestor extends Handler implements ViewParent,
static class W extends IWindow.Stub {
private final WeakReference<ViewAncestor> mViewAncestor;
- W(ViewAncestor viewRoot) {
- mViewAncestor = new WeakReference<ViewAncestor>(viewRoot);
+ W(ViewAncestor viewAncestor) {
+ mViewAncestor = new WeakReference<ViewAncestor>(viewAncestor);
}
public void resized(int w, int h, Rect coveredInsets, Rect visibleInsets,
boolean reportDraw, Configuration newConfig) {
- final ViewAncestor viewRoot = mViewAncestor.get();
- if (viewRoot != null) {
- viewRoot.dispatchResized(w, h, coveredInsets, visibleInsets, reportDraw, newConfig);
+ final ViewAncestor viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchResized(w, h, coveredInsets, visibleInsets, reportDraw,
+ newConfig);
}
}
public void dispatchAppVisibility(boolean visible) {
- final ViewAncestor viewRoot = mViewAncestor.get();
- if (viewRoot != null) {
- viewRoot.dispatchAppVisibility(visible);
+ final ViewAncestor viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchAppVisibility(visible);
}
}
public void dispatchGetNewSurface() {
- final ViewAncestor viewRoot = mViewAncestor.get();
- if (viewRoot != null) {
- viewRoot.dispatchGetNewSurface();
+ final ViewAncestor viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchGetNewSurface();
}
}
public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
- final ViewAncestor viewRoot = mViewAncestor.get();
- if (viewRoot != null) {
- viewRoot.windowFocusChanged(hasFocus, inTouchMode);
+ final ViewAncestor viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.windowFocusChanged(hasFocus, inTouchMode);
}
}
@@ -3674,9 +3735,9 @@ public final class ViewAncestor extends Handler implements ViewParent,
}
public void executeCommand(String command, String parameters, ParcelFileDescriptor out) {
- final ViewAncestor viewRoot = mViewAncestor.get();
- if (viewRoot != null) {
- final View view = viewRoot.mView;
+ final ViewAncestor viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ final View view = viewAncestor.mView;
if (view != null) {
if (checkCallingPermission(Manifest.permission.DUMP) !=
PackageManager.PERMISSION_GRANTED) {
@@ -3705,9 +3766,9 @@ public final class ViewAncestor extends Handler implements ViewParent,
}
public void closeSystemDialogs(String reason) {
- final ViewAncestor viewRoot = mViewAncestor.get();
- if (viewRoot != null) {
- viewRoot.dispatchCloseSystemDialogs(reason);
+ final ViewAncestor viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchCloseSystemDialogs(reason);
}
}
@@ -3720,7 +3781,7 @@ public final class ViewAncestor extends Handler implements ViewParent,
}
}
}
-
+
public void dispatchWallpaperCommand(String action, int x, int y,
int z, Bundle extras, boolean sync) {
if (sync) {
@@ -3733,17 +3794,16 @@ public final class ViewAncestor extends Handler implements ViewParent,
/* Drag/drop */
public void dispatchDragEvent(DragEvent event) {
- final ViewAncestor viewRoot = mViewAncestor.get();
- if (viewRoot != null) {
- viewRoot.dispatchDragEvent(event);
+ final ViewAncestor viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchDragEvent(event);
}
}
- @Override
public void dispatchSystemUiVisibilityChanged(int visibility) {
- final ViewAncestor viewRoot = mViewAncestor.get();
- if (viewRoot != null) {
- viewRoot.dispatchSystemUiVisibilityChanged(visibility);
+ final ViewAncestor viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchSystemUiVisibilityChanged(visibility);
}
}
}
@@ -4053,5 +4113,395 @@ public final class ViewAncestor extends Handler implements ViewParent,
}
}
+ /**
+ * Class for managing the accessibility interaction connection
+ * based on the global accessibility state.
+ */
+ final class AccessibilityInteractionConnectionManager
+ implements AccessibilityStateChangeListener {
+ public void onAccessibilityStateChanged(boolean enabled) {
+ if (enabled) {
+ ensureConnection();
+ } else {
+ ensureNoConnection();
+ }
+ }
+
+ public void ensureConnection() {
+ final boolean registered = mAttachInfo.mAccessibilityWindowId != View.NO_ID;
+ if (!registered) {
+ mAttachInfo.mAccessibilityWindowId =
+ mAccessibilityManager.addAccessibilityInteractionConnection(mWindow,
+ new AccessibilityInteractionConnection(ViewAncestor.this));
+ }
+ }
+
+ public void ensureNoConnection() {
+ final boolean registered = mAttachInfo.mAccessibilityWindowId != View.NO_ID;
+ if (registered) {
+ mAttachInfo.mAccessibilityWindowId = View.NO_ID;
+ mAccessibilityManager.removeAccessibilityInteractionConnection(mWindow);
+ }
+ }
+ }
+
+ /**
+ * This class is an interface this ViewAncestor provides to the
+ * AccessibilityManagerService to the latter can interact with
+ * the view hierarchy in this ViewAncestor.
+ */
+ final class AccessibilityInteractionConnection
+ extends IAccessibilityInteractionConnection.Stub {
+ private final WeakReference<ViewAncestor> mViewAncestor;
+
+ AccessibilityInteractionConnection(ViewAncestor viewAncestor) {
+ mViewAncestor = new WeakReference<ViewAncestor>(viewAncestor);
+ }
+
+ public void findAccessibilityNodeInfoByAccessibilityId(int accessibilityId,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback) {
+ final ViewAncestor viewAncestor = mViewAncestor.get();
+ if (viewAncestor == null) {
+ return;
+ }
+ getAccessibilityInteractionController()
+ .findAccessibilityNodeInfoByAccessibilityIdClientThread(accessibilityId,
+ interactionId, callback);
+ }
+
+ public void performAccessibilityAction(int accessibilityId, int action,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback) {
+ final ViewAncestor viewAncestor = mViewAncestor.get();
+ if (viewAncestor == null) {
+ return;
+ }
+ getAccessibilityInteractionController()
+ .performAccessibilityActionClientThread(accessibilityId, action, interactionId,
+ callback);
+ }
+
+ public void findAccessibilityNodeInfoByViewId(int viewId,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback) {
+ final ViewAncestor viewAncestor = mViewAncestor.get();
+ if (viewAncestor == null) {
+ return;
+ }
+ getAccessibilityInteractionController()
+ .findAccessibilityNodeInfoByViewIdClientThread(viewId, interactionId, callback);
+ }
+
+ public void findAccessibilityNodeInfosByViewText(String text, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback) {
+ final ViewAncestor viewAncestor = mViewAncestor.get();
+ if (viewAncestor == null) {
+ return;
+ }
+ getAccessibilityInteractionController()
+ .findAccessibilityNodeInfosByViewTextClientThread(text, interactionId, callback);
+ }
+ }
+
+ /**
+ * 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 FindByAccessibilitytIdPredicate mFindByAccessibilityIdPredicate =
+ new FindByAccessibilitytIdPredicate();
+
+ 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(int accessibilityId,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback) {
+ Message message = Message.obtain();
+ message.what = DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID;
+ message.arg1 = accessibilityId;
+ message.arg2 = interactionId;
+ message.obj = callback;
+ sendMessage(message);
+ }
+
+ public void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
+ final int accessibilityId = message.arg1;
+ final int interactionId = message.arg2;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) message.obj;
+
+ View root = ViewAncestor.this.mView;
+ if (root == null) {
+ return;
+ }
+
+ FindByAccessibilitytIdPredicate predicate = mFindByAccessibilityIdPredicate;
+ predicate.init(accessibilityId);
+
+ View target = root.findViewByPredicate(predicate);
+ if (target != null) {
+ AccessibilityNodeInfo info = target.createAccessibilityNodeInfo();
+ try {
+ callback.setFindAccessibilityNodeInfoResult(info, interactionId);
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ }
+ }
+ }
+
+ public void findAccessibilityNodeInfoByViewIdClientThread(int viewId, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback) {
+ Message message = Message.obtain();
+ message.what = DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID;
+ message.arg1 = viewId;
+ message.arg2 = interactionId;
+ message.obj = callback;
+ sendMessage(message);
+ }
+
+ public void findAccessibilityNodeInfoByViewIdUiThread(Message message) {
+ final int viewId = message.arg1;
+ final int interactionId = message.arg2;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) message.obj;
+
+ View root = ViewAncestor.this.mView;
+ if (root == null) {
+ return;
+ }
+ View target = root.findViewById(viewId);
+ if (target != null) {
+ AccessibilityNodeInfo info = target.createAccessibilityNodeInfo();
+ try {
+ callback.setFindAccessibilityNodeInfoResult(info, interactionId);
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ }
+ }
+ }
+
+ public void findAccessibilityNodeInfosByViewTextClientThread(String text, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback) {
+ Message message = Message.obtain();
+ message.what = DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT;
+ SomeArgs args = mPool.acquire();
+ args.arg1 = text;
+ args.argi1 = interactionId;
+ args.arg2 = callback;
+ message.obj = args;
+ sendMessage(message);
+ }
+
+ public void findAccessibilityNodeInfosByViewTextUiThread(Message message) {
+ SomeArgs args = (SomeArgs) message.obj;
+ final String text = (String) args.arg1;
+ final int interactionId = args.argi1;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) args.arg2;
+ mPool.release(args);
+
+ View root = ViewAncestor.this.mView;
+ if (root == null) {
+ return;
+ }
+
+ ArrayList<View> foundViews = mAttachInfo.mFocusablesTempList;
+ foundViews.clear();
+
+ root.findViewsWithText(foundViews, text);
+ if (foundViews.isEmpty()) {
+ return;
+ }
+
+ List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
+ infos.clear();
+
+ final int viewCount = foundViews.size();
+ for (int i = 0; i < viewCount; i++) {
+ View foundView = foundViews.get(i);
+ infos.add(foundView.createAccessibilityNodeInfo());
+ }
+
+ try {
+ callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ }
+ }
+
+ public void performAccessibilityActionClientThread(int accessibilityId, int action,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback) {
+ Message message = Message.obtain();
+ message.what = DO_PERFORM_ACCESSIBILITY_ACTION;
+ SomeArgs args = mPool.acquire();
+ args.argi1 = accessibilityId;
+ args.argi2 = action;
+ args.argi3 = interactionId;
+ args.arg1 = callback;
+ message.obj = args;
+ sendMessage(message);
+ }
+
+ public void perfromAccessibilityActionUiThread(Message message) {
+ SomeArgs args = (SomeArgs) message.obj;
+ final int accessibilityId = args.argi1;
+ final int action = args.argi2;
+ final int interactionId = args.argi3;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) args.arg1;
+ mPool.release(args);
+
+ if (ViewAncestor.this.mView == null) {
+ return;
+ }
+
+ boolean succeeded = false;
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_FOCUS: {
+ succeeded = performActionFocus(accessibilityId);
+ } break;
+ case AccessibilityNodeInfo.ACTION_CLEAR_FOCUS: {
+ succeeded = performActionClearFocus(accessibilityId);
+ } break;
+ case AccessibilityNodeInfo.ACTION_SELECT: {
+ succeeded = performActionSelect(accessibilityId);
+ } break;
+ case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
+ succeeded = performActionClearSelection(accessibilityId);
+ } break;
+ }
+
+ try {
+ callback.setPerformAccessibilityActionResult(succeeded, interactionId);
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ }
+ }
+
+ private boolean performActionFocus(int accessibilityId) {
+ View target = findViewByAccessibilityId(accessibilityId);
+ if (target == null) {
+ return false;
+ }
+ // Get out of touch mode since accessibility wants to move focus around.
+ ensureTouchMode(false);
+ return target.requestFocus();
+ }
+
+ private boolean performActionClearFocus(int accessibilityId) {
+ View target = findViewByAccessibilityId(accessibilityId);
+ if (target == null) {
+ return false;
+ }
+ if (!target.isFocused()) {
+ return false;
+ }
+ target.clearFocus();
+ return !target.isFocused();
+ }
+
+ private boolean performActionSelect(int accessibilityId) {
+ View target = findViewByAccessibilityId(accessibilityId);
+ if (target == null) {
+ return false;
+ }
+ if (target.isSelected()) {
+ return false;
+ }
+ target.setSelected(true);
+ return target.isSelected();
+ }
+
+ private boolean performActionClearSelection(int accessibilityId) {
+ View target = findViewByAccessibilityId(accessibilityId);
+ if (target == null) {
+ return false;
+ }
+ if (!target.isSelected()) {
+ return false;
+ }
+ target.setSelected(false);
+ return !target.isSelected();
+ }
+
+ private View findViewByAccessibilityId(int accessibilityId) {
+ View root = ViewAncestor.this.mView;
+ if (root == null) {
+ return null;
+ }
+ mFindByAccessibilityIdPredicate.init(accessibilityId);
+ return root.findViewByPredicate(mFindByAccessibilityIdPredicate);
+ }
+
+ private final class FindByAccessibilitytIdPredicate implements Predicate<View> {
+ public int mSerchedId;
+
+ public void init(int searchedId) {
+ mSerchedId = searchedId;
+ }
+
+ public boolean apply(View view) {
+ return (view.getAccessibilityViewId() == mSerchedId);
+ }
+ }
+ }
+
private static native void nativeShowFPS(Canvas canvas, int durationMillis);
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index f504b90..57ee8a0 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -35,6 +35,7 @@ import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
@@ -772,6 +773,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
+ @Override
+ public void findViewsWithText(ArrayList<View> outViews, CharSequence text) {
+ final int childrenCount = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < childrenCount; i++) {
+ View child = children[i];
+ if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+ child.findViewsWithText(outViews, text);
+ }
+ }
+ }
+
/**
* {@inheritDoc}
*/
@@ -2007,6 +2020,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
return false;
}
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+
+ for (int i = 0, count = mChildrenCount; i < count; i++) {
+ View child = mChildren[i];
+ info.addChild(child);
+ }
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 7b80797..32bfa2f 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -16,9 +16,12 @@
package android.view.accessibility;
+import android.accessibilityservice.IAccessibilityServiceConnection;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
import android.text.TextUtils;
+import android.view.View;
import java.util.ArrayList;
@@ -159,6 +162,7 @@ import java.util.ArrayList;
* @see android.accessibilityservice.AccessibilityService
*/
public final class AccessibilityEvent extends AccessibilityRecord implements Parcelable {
+ private static final boolean DEBUG = false;
/**
* Invalid selection/focus position.
@@ -256,21 +260,38 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
private static final Object sPoolLock = new Object();
private static AccessibilityEvent sPool;
private static int sPoolSize;
-
private AccessibilityEvent mNext;
private boolean mIsInPool;
private int mEventType;
+ private int mSourceAccessibilityViewId;
+ private int mSourceAccessibilityWindowId;
private CharSequence mPackageName;
private long mEventTime;
private final ArrayList<AccessibilityRecord> mRecords = new ArrayList<AccessibilityRecord>();
+ private IAccessibilityServiceConnection mConnection;
+
/*
* Hide constructor from clients.
*/
private AccessibilityEvent() {
+ }
+ /**
+ * Initialize an event from another one.
+ *
+ * @param event The event to initialize from.
+ */
+ void init(AccessibilityEvent event) {
+ super.init(event);
+ mEventType = event.mEventType;
+ mEventTime = event.mEventTime;
+ mSourceAccessibilityWindowId = event.mSourceAccessibilityWindowId;
+ mSourceAccessibilityViewId = event.mSourceAccessibilityViewId;
+ mPackageName = event.mPackageName;
+ mConnection = event.mConnection;
}
/**
@@ -286,8 +307,11 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
* Appends an {@link AccessibilityRecord} to the end of event records.
*
* @param record The record to append.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
*/
public void appendRecord(AccessibilityRecord record) {
+ enforceNotSealed();
mRecords.add(record);
}
@@ -311,11 +335,89 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
}
/**
+ * Sets the event source.
+ *
+ * @param source The source.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setSource(View source) {
+ enforceNotSealed();
+ if (source != null) {
+ mSourceAccessibilityWindowId = source.getAccessibilityWindowId();
+ mSourceAccessibilityViewId = source.getAccessibilityViewId();
+ } else {
+ mSourceAccessibilityWindowId = View.NO_ID;
+ mSourceAccessibilityViewId = View.NO_ID;
+ }
+ }
+
+ /**
+ * Gets the {@link AccessibilityNodeInfo} of the event source.
+ * <p>
+ * <strong>
+ * It is a client responsibility to recycle the received info by
+ * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating
+ * of multiple instances.
+ * </strong>
+ * </p>
+ * @return The info.
+ */
+ public AccessibilityNodeInfo getSource() {
+ enforceSealed();
+ if (mSourceAccessibilityWindowId == View.NO_ID
+ || mSourceAccessibilityViewId == View.NO_ID) {
+ return null;
+ }
+ try {
+ return mConnection.findAccessibilityNodeInfoByAccessibilityId(
+ mSourceAccessibilityWindowId, mSourceAccessibilityViewId);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Gets the id of the window from which the event comes from.
+ *
+ * @return The window id.
+ */
+ public int getAccessibilityWindowId() {
+ return mSourceAccessibilityWindowId;
+ }
+
+ /**
+ * Sets the client token for the accessibility service that
+ * provided this node info.
+ *
+ * @param connection The connection.
+ *
+ * @hide
+ */
+ public final void setConnection(IAccessibilityServiceConnection connection) {
+ mConnection = connection;
+ }
+
+ /**
+ * Gets the accessibility window id of the source window.
+ *
+ * @return The id.
+ *
+ * @hide
+ */
+ public int getSourceAccessibilityWindowId() {
+ return mSourceAccessibilityWindowId;
+ }
+
+ /**
* Sets the event type.
*
* @param eventType The event type.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
*/
public void setEventType(int eventType) {
+ enforceNotSealed();
mEventType = eventType;
}
@@ -332,8 +434,11 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
* Sets the time in which this event was sent.
*
* @param eventTime The event time.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
*/
public void setEventTime(long eventTime) {
+ enforceNotSealed();
mEventTime = eventTime;
}
@@ -350,8 +455,11 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
* Sets the package name of the source.
*
* @param packageName The package name.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
*/
public void setPackageName(CharSequence packageName) {
+ enforceNotSealed();
mPackageName = packageName;
}
@@ -370,6 +478,27 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
/**
* Returns a cached instance if such is available or a new one is
+ * instantiated with type property set.
+ *
+ * @param event The other event.
+ * @return An instance.
+ */
+ public static AccessibilityEvent obtain(AccessibilityEvent event) {
+ AccessibilityEvent eventClone = AccessibilityEvent.obtain();
+ eventClone.init(event);
+
+ final int recordCount = event.mRecords.size();
+ for (int i = 0; i < recordCount; i++) {
+ AccessibilityRecord record = event.mRecords.get(i);
+ AccessibilityRecord recordClone = AccessibilityRecord.obtain(record);
+ eventClone.mRecords.add(recordClone);
+ }
+
+ return eventClone;
+ }
+
+ /**
+ * Returns a cached instance if such is available or a new one is
* instantiated.
*
* @return An instance.
@@ -413,11 +542,16 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
/**
* Clears the state of this instance.
+ *
+ * @hide
*/
@Override
protected void clear() {
super.clear();
+ mConnection = null;
mEventType = 0;
+ mSourceAccessibilityViewId = View.NO_ID;
+ mSourceAccessibilityWindowId = View.NO_ID;
mPackageName = null;
mEventTime = 0;
while (!mRecords.isEmpty()) {
@@ -432,7 +566,14 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
* @param parcel A parcel containing the state of a {@link AccessibilityEvent}.
*/
public void initFromParcel(Parcel parcel) {
+ if (parcel.readInt() == 1) {
+ mConnection = IAccessibilityServiceConnection.Stub.asInterface(
+ parcel.readStrongBinder());
+ }
+ setSealed(parcel.readInt() == 1);
mEventType = parcel.readInt();
+ mSourceAccessibilityWindowId = parcel.readInt();
+ mSourceAccessibilityViewId = parcel.readInt();
mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
mEventTime = parcel.readLong();
readAccessibilityRecordFromParcel(this, parcel);
@@ -471,7 +612,16 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
* {@inheritDoc}
*/
public void writeToParcel(Parcel parcel, int flags) {
+ if (mConnection == null) {
+ parcel.writeInt(0);
+ } else {
+ parcel.writeInt(1);
+ parcel.writeStrongBinder(mConnection.asBinder());
+ }
+ parcel.writeInt(isSealed() ? 1 : 0);
parcel.writeInt(mEventType);
+ parcel.writeInt(mSourceAccessibilityWindowId);
+ parcel.writeInt(mSourceAccessibilityViewId);
TextUtils.writeToParcel(mPackageName, parcel, 0);
parcel.writeLong(mEventTime);
writeAccessibilityRecordToParcel(this, parcel, flags);
@@ -519,18 +669,36 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
builder.append("; EventType: ").append(eventTypeToString(mEventType));
builder.append("; EventTime: ").append(mEventTime);
builder.append("; PackageName: ").append(mPackageName);
- builder.append(" \n{\n");
builder.append(super.toString());
- builder.append("\n");
- for (int i = 0; i < mRecords.size(); i++) {
- AccessibilityRecord record = mRecords.get(i);
- builder.append(" Record ");
- builder.append(i);
- builder.append(":");
- builder.append(record.toString());
+ if (DEBUG) {
builder.append("\n");
+ builder.append("; sourceAccessibilityWindowId: ").append(mSourceAccessibilityWindowId);
+ builder.append("; sourceAccessibilityViewId: ").append(mSourceAccessibilityViewId);
+ for (int i = 0; i < mRecords.size(); i++) {
+ AccessibilityRecord record = mRecords.get(i);
+ builder.append(" Record ");
+ builder.append(i);
+ builder.append(":");
+ builder.append(" [ ClassName: " + record.mClassName);
+ builder.append("; Text: " + record.mText);
+ builder.append("; ContentDescription: " + record.mContentDescription);
+ builder.append("; ItemCount: " + record.mItemCount);
+ builder.append("; CurrentItemIndex: " + record.mCurrentItemIndex);
+ builder.append("; IsEnabled: " + record.isEnabled());
+ builder.append("; IsPassword: " + record.isPassword());
+ builder.append("; IsChecked: " + record.isChecked());
+ builder.append("; IsFullScreen: " + record.isFullScreen());
+ builder.append("; BeforeText: " + record.mBeforeText);
+ builder.append("; FromIndex: " + record.mFromIndex);
+ builder.append("; AddedCount: " + record.mAddedCount);
+ builder.append("; RemovedCount: " + record.mRemovedCount);
+ builder.append("; ParcelableData: " + record.mParcelableData);
+ builder.append(" ]");
+ builder.append("\n");
+ }
+ } else {
+ builder.append("; recordCount: ").append(getAddedCount());
}
- builder.append("}\n");
return builder.toString();
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 88f8878..eece64a 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -28,10 +28,13 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.util.Log;
+import android.view.IWindow;
+import android.view.View;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* System level service that serves as an event dispatch for {@link AccessibilityEvent}s.
@@ -62,6 +65,21 @@ public final class AccessibilityManager {
boolean mIsEnabled;
+ final CopyOnWriteArrayList<AccessibilityStateChangeListener> mAccessibilityStateChangeListeners =
+ new CopyOnWriteArrayList<AccessibilityStateChangeListener>();
+
+ /**
+ * Listener for the accessibility state.
+ */
+ public interface AccessibilityStateChangeListener {
+ /**
+ * Called back on change in the accessibility state.
+ *
+ * @param enabled
+ */
+ public void onAccessibilityStateChanged(boolean enabled);
+ }
+
final IAccessibilityManagerClient.Stub mClient = new IAccessibilityManagerClient.Stub() {
public void setEnabled(boolean enabled) {
mHandler.obtainMessage(DO_SET_ENABLED, enabled ? 1 : 0, 0).sendToTarget();
@@ -78,9 +96,8 @@ public final class AccessibilityManager {
public void handleMessage(Message message) {
switch (message.what) {
case DO_SET_ENABLED :
- synchronized (mHandler) {
- mIsEnabled = (message.arg1 == 1);
- }
+ final boolean isEnabled = (message.arg1 == 1);
+ setAccessibilityState(isEnabled);
return;
default :
Log.w(LOG_TAG, "Unknown message type: " + message.what);
@@ -117,7 +134,8 @@ public final class AccessibilityManager {
mService = service;
try {
- mIsEnabled = mService.addClient(mClient);
+ final boolean isEnabled = mService.addClient(mClient);
+ setAccessibilityState(isEnabled);
} catch (RemoteException re) {
Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
}
@@ -253,4 +271,81 @@ public final class AccessibilityManager {
}
return Collections.unmodifiableList(services);
}
+
+ /**
+ * Registers an {@link AccessibilityStateChangeListener}.
+ *
+ * @param listener The listener.
+ * @return True if successfully registered.
+ */
+ public boolean addAccessibilityStateChangeListener(
+ AccessibilityStateChangeListener listener) {
+ return mAccessibilityStateChangeListeners.add(listener);
+ }
+
+ /**
+ * Unregisters an {@link AccessibilityStateChangeListener}.
+ *
+ * @param listener The listener.
+ * @return True if successfully unregistered.
+ */
+ public boolean removeAccessibilityStateChangeListener(
+ AccessibilityStateChangeListener listener) {
+ return mAccessibilityStateChangeListeners.remove(listener);
+ }
+
+ /**
+ * Sets the enabled state.
+ *
+ * @param isEnabled The accessibility state.
+ */
+ private void setAccessibilityState(boolean isEnabled) {
+ synchronized (mHandler) {
+ if (isEnabled != mIsEnabled) {
+ mIsEnabled = isEnabled;
+ notifyAccessibilityStateChanged();
+ }
+ }
+ }
+
+ /**
+ * Notifies the registered {@link AccessibilityStateChangeListener}s.
+ */
+ private void notifyAccessibilityStateChanged() {
+ final int listenerCount = mAccessibilityStateChangeListeners.size();
+ for (int i = 0; i < listenerCount; i++) {
+ mAccessibilityStateChangeListeners.get(i).onAccessibilityStateChanged(mIsEnabled);
+ }
+ }
+
+ /**
+ * Adds an accessibility interaction connection interface for a given window.
+ * @param windowToken The window token to which a connection is added.
+ * @param connection The connection.
+ *
+ * @hide
+ */
+ public int addAccessibilityInteractionConnection(IWindow windowToken,
+ IAccessibilityInteractionConnection connection) {
+ try {
+ return mService.addAccessibilityInteractionConnection(windowToken, connection);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re);
+ }
+ return View.NO_ID;
+ }
+
+ /**
+ * Removed an accessibility interaction connection interface for a given window.
+ * @param windowToken The window token to which a connection is removed.
+ *
+ * @hide
+ */
+ public void removeAccessibilityInteractionConnection(IWindow windowToken) {
+ try {
+ mService.removeAccessibilityInteractionConnection(windowToken);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re);
+ }
+ }
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.aidl b/core/java/android/view/accessibility/AccessibilityNodeInfo.aidl
new file mode 100644
index 0000000..59175ce
--- /dev/null
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2011, 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.accessibility;
+
+parcelable AccessibilityNodeInfo;
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
new file mode 100644
index 0000000..752f864
--- /dev/null
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -0,0 +1,947 @@
+/*
+ * Copyright (C) 2011 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.accessibility;
+
+import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.View;
+
+/**
+ * This class represents a node of the screen content. From the point of
+ * view of an accessibility service the screen content is presented as tree
+ * of accessibility nodes.
+ *
+ * TODO(svertoslavganov): Update the documentation, add sample, and describe
+ * the security policy.
+ */
+public class AccessibilityNodeInfo implements Parcelable {
+
+ private static final boolean DEBUG = false;
+
+ // Actions.
+
+ /**
+ * Action that focuses the node.
+ */
+ public static final int ACTION_FOCUS = 0x00000001;
+
+ /**
+ * Action that unfocuses the node.
+ */
+ public static final int ACTION_CLEAR_FOCUS = 0x00000002;
+
+ /**
+ * Action that selects the node.
+ */
+ public static final int ACTION_SELECT = 0x00000004;
+
+ /**
+ * Action that unselects the node.
+ */
+ public static final int ACTION_CLEAR_SELECTION = 0x00000008;
+
+ // Boolean attributes.
+
+ private static final int PROPERTY_CHECKABLE = 0x00000001;
+
+ private static final int PROPERTY_CHECKED = 0x00000002;
+
+ private static final int PROPERTY_FOCUSABLE = 0x00000004;
+
+ private static final int PROPERTY_FOCUSED = 0x00000008;
+
+ private static final int PROPERTY_SELECTED = 0x00000010;
+
+ private static final int PROPERTY_CLICKABLE = 0x00000020;
+
+ private static final int PROPERTY_LONG_CLICKABLE = 0x00000040;
+
+ private static final int PROPERTY_ENABLED = 0x00000080;
+
+ private static final int PROPERTY_PASSWORD = 0x00000100;
+
+ // Readable representations - lazily initialized.
+ private static SparseArray<String> sActionSymbolicNames;
+
+ // Housekeeping.
+ private static final int MAX_POOL_SIZE = 50;
+ private static final Object sPoolLock = new Object();
+ private static AccessibilityNodeInfo sPool;
+ private static int sPoolSize;
+ private AccessibilityNodeInfo mNext;
+ private boolean mIsInPool;
+ private boolean mSealed;
+
+ // Data.
+ private int mAccessibilityViewId;
+ private int mAccessibilityWindowId;
+ private int mParentAccessibilityViewId;
+ private int mBooleanProperties;
+ private final Rect mBounds = new Rect();
+
+ private CharSequence mPackageName;
+ private CharSequence mClassName;
+ private CharSequence mText;
+ private CharSequence mContentDescription;
+
+ private final SparseIntArray mChildAccessibilityIds = new SparseIntArray();
+ private int mActions;
+
+ private IAccessibilityServiceConnection mConnection;
+
+ /**
+ * Hide constructor from clients.
+ */
+ private AccessibilityNodeInfo() {
+ /* do nothing */
+ }
+
+ /**
+ * Sets the source.
+ *
+ * @param source The info source.
+ */
+ public void setSource(View source) {
+ enforceNotSealed();
+ mAccessibilityViewId = source.getAccessibilityViewId();
+ mAccessibilityWindowId = source.getAccessibilityWindowId();
+ }
+
+ /**
+ * Gets the id of the window from which the info comes from.
+ *
+ * @return The window id.
+ */
+ public int getAccessibilityWindowId() {
+ return mAccessibilityWindowId;
+ }
+
+ /**
+ * Gets the number of children.
+ *
+ * @return The child count.
+ */
+ public int getChildCount() {
+ return mChildAccessibilityIds.size();
+ }
+
+ /**
+ * Get the child at given index.
+ * <p>
+ * <strong>
+ * It is a client responsibility to recycle the received info by
+ * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating
+ * of multiple instances.
+ * </strong>
+ * </p>
+ * @param index The child index.
+ * @return The child node.
+ *
+ * @throws IllegalStateException If called outside of an AccessibilityService.
+ *
+ */
+ public AccessibilityNodeInfo getChild(int index) {
+ enforceSealed();
+ final int childAccessibilityViewId = mChildAccessibilityIds.get(index);
+ try {
+ return mConnection.findAccessibilityNodeInfoByAccessibilityId(mAccessibilityWindowId,
+ childAccessibilityViewId);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Adds a child.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param child The child.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void addChild(View child) {
+ enforceNotSealed();
+ final int childAccessibilityViewId = child.getAccessibilityViewId();
+ final int index = mChildAccessibilityIds.size();
+ mChildAccessibilityIds.put(index, childAccessibilityViewId);
+ }
+
+ /**
+ * Gets the actions that can be performed on the node.
+ *
+ * @return The bit mask of with actions.
+ *
+ * @see AccessibilityNodeInfo#ACTION_FOCUS
+ * @see AccessibilityNodeInfo#ACTION_CLEAR_FOCUS
+ * @see AccessibilityNodeInfo#ACTION_SELECT
+ * @see AccessibilityNodeInfo#ACTION_CLEAR_SELECTION
+ */
+ public int getActions() {
+ return mActions;
+ }
+
+ /**
+ * Adds an action that can be performed on the node.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param action The action.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void addAction(int action) {
+ enforceNotSealed();
+ mActions |= action;
+ }
+
+ /**
+ * Performs an action on the node.
+ * <p>
+ * Note: An action can be performed only if the request is made
+ * from an {@link android.accessibilityservice.AccessibilityService}.
+ * </p>
+ * @param action The action to perform.
+ * @return True if the action was performed.
+ *
+ * @throws IllegalStateException If called outside of an AccessibilityService.
+ */
+ public boolean performAction(int action) {
+ enforceSealed();
+ try {
+ return mConnection.performAccessibilityAction(mAccessibilityWindowId,
+ mAccessibilityViewId, action);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Gets the unique id identifying this node's parent.
+ * <p>
+ * <strong>
+ * It is a client responsibility to recycle the received info by
+ * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating
+ * of multiple instances.
+ * </strong>
+ * </p>
+ * @return The node's patent id.
+ */
+ public AccessibilityNodeInfo getParent() {
+ enforceSealed();
+ try {
+ return mConnection.findAccessibilityNodeInfoByAccessibilityId(mAccessibilityWindowId,
+ mParentAccessibilityViewId);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Sets the parent.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param parent The parent.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setParent(View parent) {
+ enforceNotSealed();
+ mParentAccessibilityViewId = parent.getAccessibilityViewId();
+ }
+
+ /**
+ * Gets the node bounds in parent coordinates.
+ *
+ * @param outBounds The output node bounds.
+ */
+ public void getBounds(Rect outBounds) {
+ outBounds.set(mBounds.left, mBounds.top, mBounds.right, mBounds.bottom);
+ }
+
+ /**
+ * Sets the node bounds in parent coordinates.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param bounds The node bounds.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setBounds(Rect bounds) {
+ enforceNotSealed();
+ mBounds.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
+ }
+
+ /**
+ * Gets whether this node is checkable.
+ *
+ * @return True if the node is checkable.
+ */
+ public boolean isCheckable() {
+ return getBooleanProperty(PROPERTY_CHECKABLE);
+ }
+
+ /**
+ * Sets whether this node is checkable.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param checkable True if the node is checkable.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setCheckable(boolean checkable) {
+ setBooleanProperty(PROPERTY_CHECKABLE, checkable);
+ }
+
+ /**
+ * Gets whether this node is checked.
+ *
+ * @return True if the node is checked.
+ */
+ public boolean isChecked() {
+ return getBooleanProperty(PROPERTY_CHECKED);
+ }
+
+ /**
+ * Sets whether this node is checked.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param checked True if the node is checked.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setChecked(boolean checked) {
+ setBooleanProperty(PROPERTY_CHECKED, checked);
+ }
+
+ /**
+ * Gets whether this node is focusable.
+ *
+ * @return True if the node is focusable.
+ */
+ public boolean isFocusable() {
+ return getBooleanProperty(PROPERTY_FOCUSABLE);
+ }
+
+ /**
+ * Sets whether this node is focusable.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param focusable True if the node is focusable.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setFocusable(boolean focusable) {
+ setBooleanProperty(PROPERTY_FOCUSABLE, focusable);
+ }
+
+ /**
+ * Gets whether this node is focused.
+ *
+ * @return True if the node is focused.
+ */
+ public boolean isFocused() {
+ return getBooleanProperty(PROPERTY_FOCUSED);
+ }
+
+ /**
+ * Sets whether this node is focused.
+ * <p>
+ * Note: 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 focused.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setFocused(boolean focused) {
+ setBooleanProperty(PROPERTY_FOCUSED, focused);
+ }
+
+ /**
+ * Gets whether this node is selected.
+ *
+ * @return True if the node is selected.
+ */
+ public boolean isSelected() {
+ return getBooleanProperty(PROPERTY_SELECTED);
+ }
+
+ /**
+ * Sets whether this node is selected.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param selected True if the node is selected.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setSelected(boolean selected) {
+ setBooleanProperty(PROPERTY_SELECTED, selected);
+ }
+
+ /**
+ * Gets whether this node is clickable.
+ *
+ * @return True if the node is clickable.
+ */
+ public boolean isClickable() {
+ return getBooleanProperty(PROPERTY_CLICKABLE);
+ }
+
+ /**
+ * Sets whether this node is clickable.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param clickable True if the node is clickable.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setClickable(boolean clickable) {
+ setBooleanProperty(PROPERTY_CLICKABLE, clickable);
+ }
+
+ /**
+ * Gets whether this node is long clickable.
+ *
+ * @return True if the node is long clickable.
+ */
+ public boolean isLongClickable() {
+ return getBooleanProperty(PROPERTY_LONG_CLICKABLE);
+ }
+
+ /**
+ * Sets whether this node is long clickable.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param longClickable True if the node is long clickable.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setLongClickable(boolean longClickable) {
+ setBooleanProperty(PROPERTY_LONG_CLICKABLE, longClickable);
+ }
+
+ /**
+ * Gets whether this node is enabled.
+ *
+ * @return True if the node is enabled.
+ */
+ public boolean isEnabled() {
+ return getBooleanProperty(PROPERTY_ENABLED);
+ }
+
+ /**
+ * Sets whether this node is enabled.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param enabled True if the node is enabled.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setEnabled(boolean enabled) {
+ setBooleanProperty(PROPERTY_ENABLED, enabled);
+ }
+
+ /**
+ * Gets whether this node is a password.
+ *
+ * @return True if the node is a password.
+ */
+ public boolean isPassword() {
+ return getBooleanProperty(PROPERTY_PASSWORD);
+ }
+
+ /**
+ * Sets whether this node is a password.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param password True if the node is a password.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setPassword(boolean password) {
+ setBooleanProperty(PROPERTY_PASSWORD, password);
+ }
+
+ /**
+ * Gets the package this node comes from.
+ *
+ * @return The package name.
+ */
+ public CharSequence getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Sets the package this node comes from.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param packageName The package name.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setPackageName(CharSequence packageName) {
+ enforceNotSealed();
+ mPackageName = packageName;
+ }
+
+ /**
+ * Gets the class this node comes from.
+ *
+ * @return The class name.
+ */
+ public CharSequence getClassName() {
+ return mClassName;
+ }
+
+ /**
+ * Sets the class this node comes from.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param className The class name.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setClassName(CharSequence className) {
+ enforceNotSealed();
+ mClassName = className;
+ }
+
+ /**
+ * Gets the text of this node.
+ *
+ * @return The text.
+ */
+ public CharSequence getText() {
+ return mText;
+ }
+
+ /**
+ * Sets the text of this node.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param text The text.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setText(CharSequence text) {
+ enforceNotSealed();
+ mText = text;
+ }
+
+ /**
+ * Gets the content description of this node.
+ *
+ * @return The content description.
+ */
+ public CharSequence getContentDescription() {
+ return mContentDescription;
+ }
+
+ /**
+ * Sets the content description of this node.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param contentDescription The content description.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setContentDescription(CharSequence contentDescription) {
+ enforceNotSealed();
+ mContentDescription = contentDescription;
+ }
+
+ /**
+ * Gets the value of a boolean property.
+ *
+ * @param property The property.
+ * @return The value.
+ */
+ private boolean getBooleanProperty(int property) {
+ return (mBooleanProperties & property) != 0;
+ }
+
+ /**
+ * Sets a boolean property.
+ *
+ * @param property The property.
+ * @param value The value.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ private void setBooleanProperty(int property, boolean value) {
+ enforceNotSealed();
+ if (value) {
+ mBooleanProperties |= property;
+ } else {
+ mBooleanProperties &= ~property;
+ }
+ }
+
+ /**
+ * Sets the connection for interacting with the system.
+ *
+ * @param connection The client token.
+ *
+ * @hide
+ */
+ public final void setConnection(IAccessibilityServiceConnection connection) {
+ mConnection = connection;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Sets if this instance is sealed.
+ *
+ * @param sealed Whether is sealed.
+ *
+ * @hide
+ */
+ public void setSealed(boolean sealed) {
+ mSealed = sealed;
+ }
+
+ /**
+ * Gets if this instance is sealed.
+ *
+ * @return Whether is sealed.
+ *
+ * @hide
+ */
+ public boolean isSealed() {
+ return mSealed;
+ }
+
+ /**
+ * Enforces that this instance is sealed.
+ *
+ * @throws IllegalStateException If this instance is not sealed.
+ *
+ * @hide
+ */
+ protected void enforceSealed() {
+ if (!isSealed()) {
+ throw new IllegalStateException("Cannot perform this "
+ + "action on a not sealed instance.");
+ }
+ }
+
+ /**
+ * Enforces that this instance is not sealed.
+ *
+ * @throws IllegalStateException If this instance is sealed.
+ *
+ * @hide
+ */
+ protected void enforceNotSealed() {
+ if (isSealed()) {
+ throw new IllegalStateException("Cannot perform this "
+ + "action on an sealed instance.");
+ }
+ }
+
+ /**
+ * Returns a cached instance if such is available otherwise a new one
+ * and sets the source.
+ *
+ * @return An instance.
+ *
+ * @see #setSource(View)
+ */
+ public static AccessibilityNodeInfo obtain(View source) {
+ AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+ info.setSource(source);
+ return info;
+ }
+
+ /**
+ * Returns a cached instance if such is available otherwise a new one.
+ *
+ * @return An instance.
+ */
+ public static AccessibilityNodeInfo obtain() {
+ synchronized (sPoolLock) {
+ if (sPool != null) {
+ AccessibilityNodeInfo info = sPool;
+ sPool = sPool.mNext;
+ sPoolSize--;
+ info.mNext = null;
+ info.mIsInPool = false;
+ }
+ return new AccessibilityNodeInfo();
+ }
+ }
+
+ /**
+ * Return an instance back to be reused.
+ * <p>
+ * <b>Note: You must not touch the object after calling this function.</b>
+ *
+ * @throws IllegalStateException If the info is already recycled.
+ */
+ public void recycle() {
+ if (mIsInPool) {
+ throw new IllegalStateException("Info already recycled!");
+ }
+ clear();
+ synchronized (sPoolLock) {
+ if (sPoolSize <= MAX_POOL_SIZE) {
+ mNext = sPool;
+ sPool = this;
+ mIsInPool = true;
+ sPoolSize++;
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * <b>Note: After the instance is written to a parcel it is recycled.
+ * You must not touch the object after calling this function.</b>
+ * </p>
+ */
+ public void writeToParcel(Parcel parcel, int flags) {
+ if (mConnection == null) {
+ parcel.writeInt(0);
+ } else {
+ parcel.writeInt(1);
+ parcel.writeStrongBinder(mConnection.asBinder());
+ }
+ parcel.writeInt(isSealed() ? 1 : 0);
+ parcel.writeInt(mAccessibilityViewId);
+ parcel.writeInt(mAccessibilityWindowId);
+ parcel.writeInt(mParentAccessibilityViewId);
+
+ SparseIntArray childIds = mChildAccessibilityIds;
+ final int childIdsSize = childIds.size();
+ parcel.writeInt(childIdsSize);
+ for (int i = 0; i < childIdsSize; i++) {
+ parcel.writeInt(childIds.valueAt(i));
+ }
+
+ parcel.writeInt(mBounds.top);
+ parcel.writeInt(mBounds.bottom);
+ parcel.writeInt(mBounds.left);
+ parcel.writeInt(mBounds.right);
+
+ parcel.writeInt(mActions);
+
+ parcel.writeInt(mBooleanProperties);
+
+ TextUtils.writeToParcel(mPackageName, parcel, flags);
+ TextUtils.writeToParcel(mClassName, parcel, flags);
+ TextUtils.writeToParcel(mText, parcel, flags);
+ TextUtils.writeToParcel(mContentDescription, parcel, flags);
+
+ // Since instances of this class are fetched via synchronous i.e. blocking
+ // calls in IPCs and we always recycle as soon as the instance is marshaled.
+ recycle();
+ }
+
+ /**
+ * Creates a new instance from a {@link Parcel}.
+ *
+ * @param parcel A parcel containing the state of a {@link AccessibilityNodeInfo}.
+ */
+ private void initFromParcel(Parcel parcel) {
+ if (parcel.readInt() == 1) {
+ mConnection = IAccessibilityServiceConnection.Stub.asInterface(
+ parcel.readStrongBinder());
+ }
+ mSealed = (parcel.readInt() == 1);
+ mAccessibilityViewId = parcel.readInt();
+ mAccessibilityWindowId = parcel.readInt();
+ mParentAccessibilityViewId = parcel.readInt();
+
+ SparseIntArray childIds = mChildAccessibilityIds;
+ final int childrenSize = parcel.readInt();
+ for (int i = 0; i < childrenSize; i++) {
+ final int childId = parcel.readInt();
+ childIds.put(i, childId);
+ }
+
+ mBounds.top = parcel.readInt();
+ mBounds.bottom = parcel.readInt();
+ mBounds.left = parcel.readInt();
+ mBounds.right = parcel.readInt();
+
+ mActions = parcel.readInt();
+
+ mBooleanProperties = parcel.readInt();
+
+ mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ }
+
+ /**
+ * Clears the state of this instance.
+ */
+ private void clear() {
+ mSealed = false;
+ mConnection = null;
+ mAccessibilityViewId = View.NO_ID;
+ mParentAccessibilityViewId = View.NO_ID;
+ mChildAccessibilityIds.clear();
+ mBounds.set(0, 0, 0, 0);
+ mBooleanProperties = 0;
+ mPackageName = null;
+ mClassName = null;
+ mText = null;
+ mContentDescription = null;
+ mActions = 0;
+ }
+
+ /**
+ * Gets the human readable action symbolic name.
+ *
+ * @param action The action.
+ * @return The symbolic name.
+ */
+ private static String getActionSymbolicName(int action) {
+ SparseArray<String> actionSymbolicNames = sActionSymbolicNames;
+ if (actionSymbolicNames == null) {
+ actionSymbolicNames = sActionSymbolicNames = new SparseArray<String>();
+ actionSymbolicNames.put(ACTION_FOCUS, "ACTION_FOCUS");
+ actionSymbolicNames.put(ACTION_CLEAR_FOCUS, "ACTION_UNFOCUS");
+ actionSymbolicNames.put(ACTION_SELECT, "ACTION_SELECT");
+ actionSymbolicNames.put(ACTION_CLEAR_SELECTION, "ACTION_UNSELECT");
+ }
+ return actionSymbolicNames.get(action);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mAccessibilityViewId;
+ result = prime * result + mAccessibilityWindowId;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(super.toString());
+
+ if (DEBUG) {
+ builder.append("; accessibilityId: " + mAccessibilityViewId);
+ builder.append("; parentAccessibilityId: " + mParentAccessibilityViewId);
+ SparseIntArray childIds = mChildAccessibilityIds;
+ builder.append("; childAccessibilityIds: [");
+ for (int i = 0, count = childIds.size(); i < count; i++) {
+ builder.append(childIds.valueAt(i));
+ if (i < count - 1) {
+ builder.append(", ");
+ }
+ }
+ builder.append("]");
+ }
+
+ builder.append("; bounds: " + mBounds);
+
+ builder.append("; packageName: ").append(mPackageName);
+ builder.append("; className: ").append(mClassName);
+ builder.append("; text: ").append(mText);
+ builder.append("; contentDescription: ").append(mContentDescription);
+
+ builder.append("; checkable: ").append(isCheckable());
+ builder.append("; checked: ").append(isChecked());
+ builder.append("; focusable: ").append(isFocusable());
+ builder.append("; focused: ").append(isFocused());
+ builder.append("; selected: ").append(isSelected());
+ builder.append("; clickable: ").append(isClickable());
+ builder.append("; longClickable: ").append(isLongClickable());
+ builder.append("; enabled: ").append(isEnabled());
+ builder.append("; password: ").append(isPassword());
+
+ builder.append("; [");
+
+ for (int actionBits = mActions; actionBits != 0;) {
+ final int action = 1 << Integer.numberOfTrailingZeros(actionBits);
+ actionBits &= ~action;
+ builder.append(getActionSymbolicName(action));
+ if (actionBits != 0) {
+ builder.append(", ");
+ }
+ }
+
+ builder.append("]");
+
+ return builder.toString();
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ public static final Parcelable.Creator<AccessibilityNodeInfo> CREATOR =
+ new Parcelable.Creator<AccessibilityNodeInfo>() {
+ public AccessibilityNodeInfo createFromParcel(Parcel parcel) {
+ AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+ info.initFromParcel(parcel);
+ return info;
+ }
+
+ public AccessibilityNodeInfo[] newArray(int size) {
+ return new AccessibilityNodeInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index 7819b17..4bf03a7 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -38,13 +38,14 @@ public class AccessibilityRecord {
private static final int PROPERTY_PASSWORD = 0x00000004;
private static final int PROPERTY_FULL_SCREEN = 0x00000080;
+ // Housekeeping
private static final int MAX_POOL_SIZE = 10;
private static final Object sPoolLock = new Object();
private static AccessibilityRecord sPool;
private static int sPoolSize;
-
private AccessibilityRecord mNext;
private boolean mIsInPool;
+ private boolean mSealed;
protected int mBooleanProperties;
protected int mCurrentItemIndex;
@@ -68,6 +69,26 @@ public class AccessibilityRecord {
}
/**
+ * Initialize this record from another one.
+ *
+ * @param record The to initialize from.
+ */
+ void init(AccessibilityRecord record) {
+ mSealed = record.isSealed();
+ mBooleanProperties = record.mBooleanProperties;
+ mCurrentItemIndex = record.mCurrentItemIndex;
+ mItemCount = record.mItemCount;
+ mFromIndex = record.mFromIndex;
+ mAddedCount = record.mAddedCount;
+ mRemovedCount = record.mRemovedCount;
+ mClassName = record.mClassName;
+ mContentDescription = record.mContentDescription;
+ mBeforeText = record.mBeforeText;
+ mParcelableData = record.mParcelableData;
+ mText.addAll(record.mText);
+ }
+
+ /**
* Gets if the source is checked.
*
* @return True if the view is checked, false otherwise.
@@ -80,8 +101,11 @@ public class AccessibilityRecord {
* Sets if the source is checked.
*
* @param isChecked True if the view is checked, false otherwise.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
*/
public void setChecked(boolean isChecked) {
+ enforceNotSealed();
setBooleanProperty(PROPERTY_CHECKED, isChecked);
}
@@ -98,8 +122,11 @@ public class AccessibilityRecord {
* Sets if the source is enabled.
*
* @param isEnabled True if the view is enabled, false otherwise.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
*/
public void setEnabled(boolean isEnabled) {
+ enforceNotSealed();
setBooleanProperty(PROPERTY_ENABLED, isEnabled);
}
@@ -116,27 +143,33 @@ public class AccessibilityRecord {
* Sets if the source is a password field.
*
* @param isPassword True if the view is a password field, false otherwise.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
*/
public void setPassword(boolean isPassword) {
+ enforceNotSealed();
setBooleanProperty(PROPERTY_PASSWORD, isPassword);
}
/**
- * Sets if the source is taking the entire screen.
+ * Gets if the source is taking the entire screen.
*
- * @param isFullScreen True if the source is full screen, false otherwise.
+ * @return True if the source is full screen, false otherwise.
*/
- public void setFullScreen(boolean isFullScreen) {
- setBooleanProperty(PROPERTY_FULL_SCREEN, isFullScreen);
+ public boolean isFullScreen() {
+ return getBooleanProperty(PROPERTY_FULL_SCREEN);
}
/**
- * Gets if the source is taking the entire screen.
+ * Sets if the source is taking the entire screen.
*
- * @return True if the source is full screen, false otherwise.
+ * @param isFullScreen True if the source is full screen, false otherwise.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
*/
- public boolean isFullScreen() {
- return getBooleanProperty(PROPERTY_FULL_SCREEN);
+ public void setFullScreen(boolean isFullScreen) {
+ enforceNotSealed();
+ setBooleanProperty(PROPERTY_FULL_SCREEN, isFullScreen);
}
/**
@@ -152,8 +185,11 @@ public class AccessibilityRecord {
* Sets the number of items that can be visited.
*
* @param itemCount The number of items.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
*/
public void setItemCount(int itemCount) {
+ enforceNotSealed();
mItemCount = itemCount;
}
@@ -170,8 +206,11 @@ public class AccessibilityRecord {
* Sets the index of the source in the list of items that can be visited.
*
* @param currentItemIndex The current item index.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
*/
public void setCurrentItemIndex(int currentItemIndex) {
+ enforceNotSealed();
mCurrentItemIndex = currentItemIndex;
}
@@ -188,8 +227,11 @@ public class AccessibilityRecord {
* Sets the index of the first character of the changed sequence.
*
* @param fromIndex The index of the first character.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
*/
public void setFromIndex(int fromIndex) {
+ enforceNotSealed();
mFromIndex = fromIndex;
}
@@ -206,8 +248,11 @@ public class AccessibilityRecord {
* Sets the number of added characters.
*
* @param addedCount The number of added characters.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
*/
public void setAddedCount(int addedCount) {
+ enforceNotSealed();
mAddedCount = addedCount;
}
@@ -224,8 +269,11 @@ public class AccessibilityRecord {
* Sets the number of removed characters.
*
* @param removedCount The number of removed characters.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
*/
public void setRemovedCount(int removedCount) {
+ enforceNotSealed();
mRemovedCount = removedCount;
}
@@ -242,8 +290,11 @@ public class AccessibilityRecord {
* Sets the class name of the source.
*
* @param className The lass name.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
*/
public void setClassName(CharSequence className) {
+ enforceNotSealed();
mClassName = className;
}
@@ -270,8 +321,11 @@ public class AccessibilityRecord {
* Sets the text before a change.
*
* @param beforeText The text before the change.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
*/
public void setBeforeText(CharSequence beforeText) {
+ enforceNotSealed();
mBeforeText = beforeText;
}
@@ -288,8 +342,11 @@ public class AccessibilityRecord {
* Sets the description of the source.
*
* @param contentDescription The description.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
*/
public void setContentDescription(CharSequence contentDescription) {
+ enforceNotSealed();
mContentDescription = contentDescription;
}
@@ -306,18 +363,71 @@ public class AccessibilityRecord {
* Sets the {@link Parcelable} data of the event.
*
* @param parcelableData The parcelable data.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
*/
public void setParcelableData(Parcelable parcelableData) {
+ enforceNotSealed();
mParcelableData = parcelableData;
}
/**
+ * Sets if this instance is sealed.
+ *
+ * @param sealed Whether is sealed.
+ *
+ * @hide
+ */
+ public void setSealed(boolean sealed) {
+ mSealed = sealed;
+ }
+
+ /**
+ * Gets if this instance is sealed.
+ *
+ * @return Whether is sealed.
+ *
+ * @hide
+ */
+ public boolean isSealed() {
+ return mSealed;
+ }
+
+ /**
+ * Enforces that this instance is sealed.
+ *
+ * @throws IllegalStateException If this instance is not sealed.
+ *
+ * @hide
+ */
+ protected void enforceSealed() {
+ if (!isSealed()) {
+ throw new IllegalStateException("Cannot perform this "
+ + "action on a not sealed instance.");
+ }
+ }
+
+ /**
+ * Enforces that this instance is not sealed.
+ *
+ * @throws IllegalStateException If this instance is sealed.
+ *
+ * @hide
+ */
+ protected void enforceNotSealed() {
+ if (isSealed()) {
+ throw new IllegalStateException("Cannot perform this "
+ + "action on an sealed instance.");
+ }
+ }
+
+ /**
* Gets the value of a boolean property.
*
* @param property The property.
* @return The value.
*/
- public boolean getBooleanProperty(int property) {
+ private boolean getBooleanProperty(int property) {
return (mBooleanProperties & property) == property;
}
@@ -337,6 +447,19 @@ public class AccessibilityRecord {
/**
* Returns a cached instance if such is available or a new one is
+ * instantiated. The instance is initialized with data from the
+ * given record.
+ *
+ * @return An instance.
+ */
+ public static AccessibilityRecord obtain(AccessibilityRecord record) {
+ AccessibilityRecord clone = AccessibilityRecord.obtain();
+ clone.init(record);
+ return clone;
+ }
+
+ /**
+ * Returns a cached instance if such is available or a new one is
* instantiated.
*
* @return An instance.
@@ -379,8 +502,11 @@ public class AccessibilityRecord {
/**
* Clears the state of this instance.
+ *
+ * @hide
*/
protected void clear() {
+ mSealed = false;
mBooleanProperties = 0;
mCurrentItemIndex = INVALID_POSITION;
mItemCount = 0;
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
new file mode 100644
index 0000000..77dcd07
--- /dev/null
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2011 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.accessibility;
+
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+
+/**
+ * Interface for interaction between the AccessibilityManagerService
+ * and the ViewRoot in a given window.
+ *
+ * @hide
+ */
+oneway interface IAccessibilityInteractionConnection {
+
+ void findAccessibilityNodeInfoByAccessibilityId(int accessibilityViewId, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback);
+
+ void findAccessibilityNodeInfoByViewId(int id, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback);
+
+ void findAccessibilityNodeInfosByViewText(String text, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback);
+
+ void performAccessibilityAction(int accessibilityId, int action, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback);
+}
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
new file mode 100644
index 0000000..9c5e8dc
--- /dev/null
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2011 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.accessibility;
+
+import android.view.accessibility.AccessibilityNodeInfo;
+import java.util.List;
+
+/**
+ * Callback for specifying the result for an asynchronous request made
+ * via calling a method on IAccessibilityInteractionConnectionCallback.
+ *
+ * @hide
+ */
+oneway interface IAccessibilityInteractionConnectionCallback {
+
+ void setFindAccessibilityNodeInfoResult(in AccessibilityNodeInfo info, int interactionId);
+
+ void setFindAccessibilityNodeInfosResult(in List<AccessibilityNodeInfo> infos,
+ int interactionId);
+
+ void setPerformAccessibilityActionResult(boolean succeeded, int interactionId);
+}
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 6b2aae2..b14f02a 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -18,8 +18,13 @@
package android.view.accessibility;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.accessibilityservice.IEventListener;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.IAccessibilityInteractionConnection;
import android.view.accessibility.IAccessibilityManagerClient;
+import android.view.IWindow;
/**
* Interface implemented by the AccessibilityManagerService called by
@@ -38,4 +43,11 @@ interface IAccessibilityManager {
List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType);
void interrupt();
+
+ int addAccessibilityInteractionConnection(IWindow windowToken,
+ in IAccessibilityInteractionConnection connection);
+
+ void removeAccessibilityInteractionConnection(IWindow windowToken);
+
+ IAccessibilityServiceConnection registerEventListener(IEventListener client);
}
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index 5562bf7..a4771d5 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -1441,6 +1441,7 @@ public class RelativeLayout extends ViewGroup {
);
private Node mNext;
+ private boolean mIsPooled;
public void setNextPoolable(Node element) {
mNext = element;
@@ -1450,6 +1451,14 @@ public class RelativeLayout extends ViewGroup {
return mNext;
}
+ public boolean isPooled() {
+ return mIsPooled;
+ }
+
+ public void setPooled(boolean isPooled) {
+ mIsPooled = isPooled;
+ }
+
static Node acquire(View view) {
final Node node = sPool.acquire();
node.view = view;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 1d123f6..eba9d37 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -113,6 +113,7 @@ import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
@@ -7647,7 +7648,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
protected int computeVerticalScrollExtent() {
return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
}
-
+
+ @Override
+ public void findViewsWithText(ArrayList<View> outViews, CharSequence text) {
+ CharSequence thisText = getText();
+ if (TextUtils.isEmpty(thisText)) {
+ return;
+ }
+ if (thisText.toString().toLowerCase().contains(text)) {
+ outViews.add(this);
+ }
+ }
+
public enum BufferType {
NORMAL, SPANNABLE, EDITABLE,
}
@@ -7899,6 +7911,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
event.setPassword(isPassword);
}
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+
+ final boolean isPassword = hasPasswordTransformationMethod();
+ if (!isPassword) {
+ info.setText(getText());
+ }
+ info.setPassword(isPassword);
+ }
+
void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
int fromIndex, int removedCount, int addedCount) {
AccessibilityEvent event =
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index c41b2cb..b9948fe 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -16,8 +16,6 @@
package com.android.internal.view;
-import android.content.ClipData;
-import android.content.ClipDescription;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
@@ -26,8 +24,6 @@ import android.os.RemoteException;
import android.view.DragEvent;
import android.view.IWindow;
import android.view.IWindowSession;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
public class BaseIWindow extends IWindow.Stub {
private IWindowSession mSession;