Interrogation of the view hierarchy from an AccessibilityService.

1. Views are represented as AccessibilityNodeInfos to AccessibilityServices.

2. An accessibility service receives AccessibilityEvents and can ask
   for its source and gets an AccessibilityNodeInfo which can be used
   to get its parent and children infos and so on.

3. AccessibilityNodeInfo contains some attributes and actions that
   can be performed on the source.

4. AccessibilityService can request the system to preform an action
   on the source of an AccessibilityNodeInfo.

5. ViewAncestor provides an interaction connection to the
   AccessibiltyManagerService and an accessibility service uses
   its connection to the latter to interact with screen content.

6. AccessibilityService can interact ONLY with the focused window
   and all calls are routed through the AccessibilityManagerService
   which imposes security.

7. Hidden APIs on AccessibilityService can find AccessibilityNodeInfos
   based on some criteria. These API go through the AccessibilityManagerServcie
   for security check.

8. Some actions are hidden and are exposes only to eng builds for UI testing.

Change-Id: Ie34fa4219f350eb3f4f6f9f45b24f709bd98783c
diff --git a/Android.mk b/Android.mk
index e987f91..90ffcd3 100644
--- a/Android.mk
+++ b/Android.mk
@@ -127,11 +127,13 @@
 	core/java/android/os/INetworkManagementService.aidl \
 	core/java/android/os/IPermissionController.aidl \
 	core/java/android/os/IPowerManager.aidl \
-    core/java/android/os/IRemoteCallback.aidl \
+	core/java/android/os/IRemoteCallback.aidl \
 	core/java/android/os/IVibratorService.aidl \
-    core/java/android/service/wallpaper/IWallpaperConnection.aidl \
-    core/java/android/service/wallpaper/IWallpaperEngine.aidl \
-    core/java/android/service/wallpaper/IWallpaperService.aidl \
+	core/java/android/service/wallpaper/IWallpaperConnection.aidl \
+	core/java/android/service/wallpaper/IWallpaperEngine.aidl \
+	core/java/android/service/wallpaper/IWallpaperService.aidl \
+	core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl\
+	core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl\
 	core/java/android/view/accessibility/IAccessibilityManager.aidl \
 	core/java/android/view/accessibility/IAccessibilityManagerClient.aidl \
 	core/java/android/view/IApplicationToken.aidl \
diff --git a/api/14.txt b/api/14.txt
index 58d49d9..ac93afb 100644
--- a/api/14.txt
+++ b/api/14.txt
@@ -22250,10 +22250,8 @@
 
   public class AccessibilityRecord {
     ctor protected AccessibilityRecord();
-    method protected void clear();
     method public int getAddedCount();
     method public java.lang.CharSequence getBeforeText();
-    method public boolean getBooleanProperty(int);
     method public java.lang.CharSequence getClassName();
     method public java.lang.CharSequence getContentDescription();
     method public int getCurrentItemIndex();
diff --git a/api/current.txt b/api/current.txt
index e0d5ef0..852a533 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -90,6 +90,7 @@
     field public static final java.lang.String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
     field public static final java.lang.String REORDER_TASKS = "android.permission.REORDER_TASKS";
     field public static final deprecated java.lang.String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
+    field public static final java.lang.String RETRIEVE_WINDOW_CONTENT = "android.permission.RETRIEVE_WINDOW_CONTENT";
     field public static final java.lang.String SEND_SMS = "android.permission.SEND_SMS";
     field public static final java.lang.String SET_ACTIVITY_WATCHER = "android.permission.SET_ACTIVITY_WATCHER";
     field public static final java.lang.String SET_ALARM = "com.android.alarm.permission.SET_ALARM";
@@ -21478,6 +21479,7 @@
     method protected int computeVerticalScrollExtent();
     method protected int computeVerticalScrollOffset();
     method protected int computeVerticalScrollRange();
+    method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo();
     method public void createContextMenu(android.view.ContextMenu);
     method public void destroyDrawingCache();
     method public void dispatchConfigurationChanged(android.content.res.Configuration);
@@ -21506,6 +21508,7 @@
     method public android.view.View findFocus();
     method public final android.view.View findViewById(int);
     method public final android.view.View findViewWithTag(java.lang.Object);
+    method public void findViewsWithText(java.util.ArrayList<android.view.View>, java.lang.CharSequence);
     method protected boolean fitSystemWindows(android.graphics.Rect);
     method public android.view.View focusSearch(int);
     method public void forceLayout();
@@ -21674,6 +21677,7 @@
     method public boolean onGenericMotionEvent(android.view.MotionEvent);
     method public boolean onHoverEvent(android.view.MotionEvent);
     method public void onInitializeAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
+    method public void onInitializeAccessibilityNodeInfo(android.view.accessibility.AccessibilityNodeInfo);
     method public boolean onKeyDown(int, android.view.KeyEvent);
     method public boolean onKeyLongPress(int, android.view.KeyEvent);
     method public boolean onKeyMultiple(int, int, android.view.KeyEvent);
@@ -22567,17 +22571,21 @@
     method public void appendRecord(android.view.accessibility.AccessibilityRecord);
     method public int describeContents();
     method public static java.lang.String eventTypeToString(int);
+    method public int getAccessibilityWindowId();
     method public long getEventTime();
     method public int getEventType();
     method public java.lang.CharSequence getPackageName();
     method public android.view.accessibility.AccessibilityRecord getRecord(int);
     method public int getRecordCount();
+    method public android.view.accessibility.AccessibilityNodeInfo getSource();
     method public void initFromParcel(android.os.Parcel);
     method public static android.view.accessibility.AccessibilityEvent obtain(int);
+    method public static android.view.accessibility.AccessibilityEvent obtain(android.view.accessibility.AccessibilityEvent);
     method public static android.view.accessibility.AccessibilityEvent obtain();
     method public void setEventTime(long);
     method public void setEventType(int);
     method public void setPackageName(java.lang.CharSequence);
+    method public void setSource(android.view.View);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator CREATOR;
     field public static final int INVALID_POSITION = -1; // 0xffffffff
@@ -22602,20 +22610,75 @@
   }
 
   public final class AccessibilityManager {
+    method public boolean addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
     method public deprecated java.util.List<android.content.pm.ServiceInfo> getAccessibilityServiceList();
     method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int);
     method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getInstalledAccessibilityServiceList();
     method public void interrupt();
     method public boolean isEnabled();
+    method public boolean removeAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
     method public void sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
   }
 
+  public static abstract interface AccessibilityManager.AccessibilityStateChangeListener {
+    method public abstract void onAccessibilityStateChanged(boolean);
+  }
+
+  public class AccessibilityNodeInfo implements android.os.Parcelable {
+    method public void addAction(int);
+    method public void addChild(android.view.View);
+    method public int describeContents();
+    method public int getAccessibilityWindowId();
+    method public int getActions();
+    method public void getBounds(android.graphics.Rect);
+    method public android.view.accessibility.AccessibilityNodeInfo getChild(int);
+    method public int getChildCount();
+    method public java.lang.CharSequence getClassName();
+    method public java.lang.CharSequence getContentDescription();
+    method public java.lang.CharSequence getPackageName();
+    method public android.view.accessibility.AccessibilityNodeInfo getParent();
+    method public java.lang.CharSequence getText();
+    method public boolean isCheckable();
+    method public boolean isChecked();
+    method public boolean isClickable();
+    method public boolean isEnabled();
+    method public boolean isFocusable();
+    method public boolean isFocused();
+    method public boolean isLongClickable();
+    method public boolean isPassword();
+    method public boolean isSelected();
+    method public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View);
+    method public static android.view.accessibility.AccessibilityNodeInfo obtain();
+    method public boolean performAction(int);
+    method public void recycle();
+    method public void setBounds(android.graphics.Rect);
+    method public void setCheckable(boolean);
+    method public void setChecked(boolean);
+    method public void setClassName(java.lang.CharSequence);
+    method public void setClickable(boolean);
+    method public void setContentDescription(java.lang.CharSequence);
+    method public void setEnabled(boolean);
+    method public void setFocusable(boolean);
+    method public void setFocused(boolean);
+    method public void setLongClickable(boolean);
+    method public void setPackageName(java.lang.CharSequence);
+    method public void setParent(android.view.View);
+    method public void setPassword(boolean);
+    method public void setSelected(boolean);
+    method public void setSource(android.view.View);
+    method public void setText(java.lang.CharSequence);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int ACTION_CLEAR_FOCUS = 2; // 0x2
+    field public static final int ACTION_CLEAR_SELECTION = 8; // 0x8
+    field public static final int ACTION_FOCUS = 1; // 0x1
+    field public static final int ACTION_SELECT = 4; // 0x4
+    field public static final android.os.Parcelable.Creator CREATOR;
+  }
+
   public class AccessibilityRecord {
     ctor protected AccessibilityRecord();
-    method protected void clear();
     method public int getAddedCount();
     method public java.lang.CharSequence getBeforeText();
-    method public boolean getBooleanProperty(int);
     method public java.lang.CharSequence getClassName();
     method public java.lang.CharSequence getContentDescription();
     method public int getCurrentItemIndex();
@@ -22628,6 +22691,7 @@
     method public boolean isEnabled();
     method public boolean isFullScreen();
     method public boolean isPassword();
+    method public static android.view.accessibility.AccessibilityRecord obtain(android.view.accessibility.AccessibilityRecord);
     method public static android.view.accessibility.AccessibilityRecord obtain();
     method public void recycle();
     method public void setAddedCount(int);
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.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 @@
  * 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 @@
  *     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 @@
  *       {@link AccessibilityServiceInfo#packageNames}
  *     </strong>
  *     </p>
+ *     <p>
+ *       For more details refer to {@link AccessibilityServiceInfo}.
+ *     </p>
  *   </li>
  * </ul>
  * <p>
@@ -151,16 +162,49 @@
      * <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 @@
 
     /**
      * 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 @@
 
         if (element != null) {
             element.setNextPoolable(null);
+            element.setPooled(false);
             mManager.onAcquired(element);            
         }
 
@@ -76,9 +77,13 @@
     }
 
     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 @@
 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 @@
 
     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 @@
         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.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 @@
     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 @@
     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 @@
      * @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 @@
     }
 
     /**
+     * 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 @@
     }
 
     /**
+     * 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 @@
 
         // 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 @@
             );
 
             private InvalidateInfo mNext;
+            private boolean mIsPooled;
 
             View target;
 
@@ -12742,6 +12871,14 @@
             void release() {
                 sPool.release(this);
             }
+
+            public boolean isPooled() {
+                return mIsPooled;
+            }
+
+            public void setPooled(boolean isPooled) {
+                mIsPooled = isPooled;
+            }
         }
 
         final IWindowSession mSession;
@@ -12952,6 +13089,11 @@
         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.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.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 @@
      */
     AudioManager mAudioManager;
 
+    final AccessibilityManager mAccessibilityManager;
+
+    AccessibilityInteractionController mAccessibilityInteractionContrtoller;
+
+    AccessibilityInteractionConnectionManager mAccessibilityInteractionConnectionManager;
+
     private final int mDensity;
 
     /**
@@ -285,7 +302,7 @@
         // 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 @@
         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 @@
                     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 @@
             mView.dispatchDetachedFromWindow();
         }
 
+        mAccessibilityInteractionConnectionManager.ensureNoConnection();
+        mAccessibilityManager.removeAccessibilityStateChangeListener(
+                mAccessibilityInteractionConnectionManager);
+
         mView = null;
         mAttachInfo.mRootView = null;
         mAttachInfo.mSurface = null;
@@ -2020,7 +2050,6 @@
                 InputQueue.unregisterInputChannel(mInputChannel);
             }
         }
-        
         try {
             sWindowSession.remove(mWindow);
         } catch (RemoteException e) {
@@ -2098,6 +2127,10 @@
     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 @@
         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 @@
         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 @@
      * 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 @@
         if (mView == null) {
             return false;
         }
-        AccessibilityManager.getInstance(child.mContext).sendAccessibilityEvent(event);
+        mAccessibilityManager.sendAccessibilityEvent(event);
         return true;
     }
 
@@ -3608,18 +3668,18 @@
     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 @@
     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 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 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 void dispatchWallpaperCommand(String action, int x, int y,
                 int z, Bundle extras, boolean sync) {
             if (sync) {
@@ -3733,17 +3794,16 @@
 
         /* 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 @@
         }
     }
 
+    /**
+     * 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.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 @@
         }
     }
 
+    @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 @@
         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 @@
  * @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 @@
     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 @@
      * 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 @@
     }
 
     /**
+     * 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 @@
      * 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 @@
      * 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 @@
 
     /**
      * 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 @@
 
     /**
      * 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 @@
      * @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 @@
      * {@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 @@
         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.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 @@
 
     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 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 @@
         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 @@
         }
         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 @@
     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 @@
     }
 
     /**
+     * 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 @@
      * 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 @@
      * 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,21 +143,15 @@
      * 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.
-     *
-     * @param isFullScreen True if the source is full screen, false otherwise.
-     */
-    public void setFullScreen(boolean isFullScreen) {
-        setBooleanProperty(PROPERTY_FULL_SCREEN, isFullScreen);
-    }
-
-    /**
      * Gets if the source is taking the entire screen.
      *
      * @return True if the source is full screen, false otherwise.
@@ -140,6 +161,18 @@
     }
 
     /**
+     * Sets if the source is taking the entire screen.
+     *
+     * @param isFullScreen True if the source is full screen, false otherwise.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setFullScreen(boolean isFullScreen) {
+        enforceNotSealed();
+        setBooleanProperty(PROPERTY_FULL_SCREEN, isFullScreen);
+    }
+
+    /**
      * Gets the number of items that can be visited.
      *
      * @return The number of items.
@@ -152,8 +185,11 @@
      * 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 @@
      * 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 @@
      * 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 @@
      * 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 @@
      * 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 @@
      * 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 @@
      * 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 @@
      * 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 @@
      * 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 @@
 
     /**
      * 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 @@
 
     /**
      * 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 @@
     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 @@
             );
 
             private Node mNext;
+            private boolean mIsPooled;
 
             public void setNextPoolable(Node element) {
                 mNext = element;
@@ -1450,6 +1451,14 @@
                 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.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 @@
     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 @@
         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.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;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 110268e..3efb83c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -677,6 +677,14 @@
         android:label="@string/permlab_dump"
         android:description="@string/permdesc_dump" />
 
+    <!-- Allows an application to retrieve the content of the active window
+         An active window is the window that has fired an accessibility event. -->
+    <permission android:name="android.permission.RETRIEVE_WINDOW_CONTENT"
+        android:permissionGroup="android.permission-group.PERSONAL_INFO"
+        android:protectionLevel="signatureOrSystem"
+        android:label="@string/permlab_retrieve_window_content"
+        android:description="@string/permdesc_retrieve_window_content" />
+
     <!-- Allows an application to open windows using the type
          {@link android.view.WindowManager.LayoutParams#TYPE_SYSTEM_ALERT},
          shown on top of all other applications.  Very few applications
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 8a4b74b..29c23a6 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -562,6 +562,13 @@
         never normally need.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_retrieve_window_content">retrieve screen content</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_retrieve_window_content">Allows application to retrieve
+        the content of the active window. Malicious applications may retrieve
+        the entire window content and examine all its text except passwords.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_shutdown">partial shutdown</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_shutdown">Puts the activity manager into a shutdown
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 6084dd2..8e2d925 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -98,6 +98,9 @@
     <uses-permission android:name="android.permission.ASEC_RENAME" />
     <uses-permission android:name="android.permission.SHUTDOWN" />
 
+    <!-- accessibility test permissions -->
+    <uses-permission android:name="android.permission.RETRIEVE_WINDOW_CONTENT" />
+
     <application android:theme="@style/Theme">
         <uses-library android:name="android.test.runner" />
         <activity android:name="android.view.ViewAttachTestActivity" android:label="View Attach Test">
@@ -1225,6 +1228,13 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="android.accessibilityservice.InterrogationActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+            </intent-filter>
+        </activity>
+
     </application>
 
     <instrumentation android:name="android.test.InstrumentationTestRunner"
diff --git a/core/tests/coretests/res/layout/interrogation_activity.xml b/core/tests/coretests/res/layout/interrogation_activity.xml
new file mode 100644
index 0000000..28d965b
--- /dev/null
+++ b/core/tests/coretests/res/layout/interrogation_activity.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/root"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    >
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        >
+        <Button
+            android:id="@+id/button1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/button1"
+        />
+        <Button
+            android:id="@+id/button2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/button2"
+        />
+        <Button
+            android:id="@+id/button3"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/button3"
+        />
+    </LinearLayout>
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        >
+        <Button
+            android:id="@+id/button4"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/button4"
+        />
+        <Button
+            android:id="@+id/button5"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/button5"
+        />
+        <Button
+            android:id="@+id/button6"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/button6"
+        />
+    </LinearLayout>
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        >
+        <Button
+            android:id="@+id/button7"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/button7"
+        />
+        <Button
+            android:id="@+id/button8"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/button8"
+        />
+        <Button
+            android:id="@+id/button9"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/button9"
+        />
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/values/strings.xml b/core/tests/coretests/res/values/strings.xml
index 807386a..f51b08e 100644
--- a/core/tests/coretests/res/values/strings.xml
+++ b/core/tests/coretests/res/values/strings.xml
@@ -117,4 +117,16 @@
 
     <string name="searchable_label">SearchManager Test</string>
     <string name="searchable_hint">A search hint</string>
+
+    <!-- InterrogationActivity -->
+    <string name="button1">Button1</string>
+    <string name="button2">Button2</string>
+    <string name="button3">Button3</string>
+    <string name="button4">Button4</string>
+    <string name="button5">Button5</string>
+    <string name="button6">Button6</string>
+    <string name="button7">Button7</string>
+    <string name="button8">Button8</string>
+    <string name="button9">Button9</string>
+
 </resources>
diff --git a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivity.java b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivity.java
new file mode 100644
index 0000000..b4a0581
--- /dev/null
+++ b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivity.java
@@ -0,0 +1,47 @@
+/**
+ * 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.accessibilityservice;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+
+/**
+ * Activity for testing the accessibility APIs for "interrogation" of
+ * the screen content. These APIs allow exploring the screen and
+ * requesting an action to be performed on a given view from an
+ * AccessiiblityService.
+ */
+public class InterrogationActivity extends Activity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.interrogation_activity);
+
+        findViewById(R.id.button5).setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                /* do nothing */
+            }
+        });
+        findViewById(R.id.button5).setOnLongClickListener(new View.OnLongClickListener() {
+            public boolean onLongClick(View v) {
+                return true;
+            }
+        });
+    }
+}
diff --git a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java
new file mode 100644
index 0000000..a20cc1f
--- /dev/null
+++ b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java
@@ -0,0 +1,464 @@
+/**
+ * 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.accessibilityservice;
+
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_FOCUS;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_SELECTION;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SELECT;
+
+import com.android.frameworks.coretests.R;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.IAccessibilityManager;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+
+/**
+ * Activity for testing the accessibility APIs for "interrogation" of
+ * the screen content. These APIs allow exploring the screen and
+ * requesting an action to be performed on a given view from an
+ * AccessiiblityService.
+ */
+public class InterrogationActivityTest
+        extends ActivityInstrumentationTestCase2<InterrogationActivity> {
+
+    // Timeout before give up wait for the system to process an accessibility setting change.
+    private static final int TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING = 2000;
+
+    // Helpers to figure out the first and last test methods
+    // This is a workaround for the lack of such support in JUnit3
+    private static int sTestMethodCount;
+    private static int sExecutedTestMethodCount;
+
+    // Handle to a connection to the AccessibilityManagerService
+    private static IAccessibilityServiceConnection sConnection;
+
+    // The last received accessibility event
+    private static volatile AccessibilityEvent sLastAccessibilityEvent;
+
+    public InterrogationActivityTest() {
+        super(InterrogationActivity.class);
+        sTestMethodCount = getTestMethodCount();
+    }
+
+    @LargeTest
+    public void testFindAccessibilityNodeInfoByViewId() throws Exception {
+        beforeClassIfNeeded();
+        try {
+            // bring up the activity
+            getActivity();
+
+            AccessibilityNodeInfo button = getConnection().findAccessibilityNodeInfoByViewId(
+                    R.id.button5);
+            assertNotNull(button);
+            assertEquals(0, button.getChildCount());
+
+            // bounds
+            Rect bounds = new Rect();
+            button.getBounds(bounds);
+            assertEquals(0, bounds.left);
+            assertEquals(0, bounds.top);
+            assertEquals(73, bounds.right);
+            assertEquals(48, bounds.bottom);
+
+            // char sequence attributes
+            assertEquals("com.android.frameworks.coretests", button.getPackageName());
+            assertEquals("android.widget.Button", button.getClassName());
+            assertEquals("Button5", button.getText());
+            assertNull(button.getContentDescription());
+
+            // boolean attributes
+            assertTrue(button.isFocusable());
+            assertTrue(button.isClickable());
+            assertTrue(button.isEnabled());
+            assertFalse(button.isFocused());
+            assertTrue(button.isClickable());
+            assertFalse(button.isPassword());
+            assertFalse(button.isSelected());
+            assertFalse(button.isCheckable());
+            assertFalse(button.isChecked());
+
+            // actions
+            assertEquals(ACTION_FOCUS | ACTION_SELECT | ACTION_CLEAR_SELECTION,
+                button.getActions());
+        } finally {
+            afterClassIfNeeded();
+        }
+    }
+
+    @LargeTest
+    public void testFindAccessibilityNodeInfoByViewText() throws Exception {
+        beforeClassIfNeeded();
+        try {
+            // bring up the activity
+            getActivity();
+
+            // find a view by text
+            List<AccessibilityNodeInfo> buttons =
+                getConnection().findAccessibilityNodeInfosByViewText("butto");
+            assertEquals(9, buttons.size());
+        } finally {
+            afterClassIfNeeded();
+        }
+    }
+
+    @LargeTest
+    public void testTraverseAllViews() throws Exception {
+        beforeClassIfNeeded();
+        try {
+            // bring up the activity
+            getActivity();
+
+            // make list of expected nodes
+            List<String> classNameAndTextList = new ArrayList<String>();
+            classNameAndTextList.add("android.widget.LinearLayout");
+            classNameAndTextList.add("android.widget.LinearLayout");
+            classNameAndTextList.add("android.widget.LinearLayout");
+            classNameAndTextList.add("android.widget.LinearLayout");
+            classNameAndTextList.add("android.widget.ButtonButton1");
+            classNameAndTextList.add("android.widget.ButtonButton2");
+            classNameAndTextList.add("android.widget.ButtonButton3");
+            classNameAndTextList.add("android.widget.ButtonButton4");
+            classNameAndTextList.add("android.widget.ButtonButton5");
+            classNameAndTextList.add("android.widget.ButtonButton6");
+            classNameAndTextList.add("android.widget.ButtonButton7");
+            classNameAndTextList.add("android.widget.ButtonButton8");
+            classNameAndTextList.add("android.widget.ButtonButton9");
+
+            AccessibilityNodeInfo root =  getConnection().findAccessibilityNodeInfoByViewId(
+                    R.id.root);
+            assertNotNull("We must find the existing root.", root);
+
+            Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
+            fringe.add(root);
+
+            // do a BFS traversal and check nodes
+            while (!fringe.isEmpty()) {
+                AccessibilityNodeInfo current = fringe.poll();
+
+                CharSequence className = current.getClassName();
+                CharSequence text = current.getText();
+                String receivedClassNameAndText = className.toString()
+                   + ((text != null) ? text.toString() : "");
+                String expectedClassNameAndText = classNameAndTextList.remove(0);
+
+                assertEquals("Did not get the expected node info",
+                        expectedClassNameAndText, receivedClassNameAndText);
+
+                final int childCount = current.getChildCount();
+                for (int i = 0; i < childCount; i++) {
+                    AccessibilityNodeInfo child = current.getChild(i);
+                    fringe.add(child);
+                }
+            }
+        } finally {
+            afterClassIfNeeded();
+        }
+    }
+
+    @LargeTest
+    public void testPerformAccessibilityActionFocus() throws Exception {
+        beforeClassIfNeeded();
+        try {
+            // bring up the activity
+            getActivity();
+
+            // find a view and make sure it is not focused
+            AccessibilityNodeInfo button = getConnection().findAccessibilityNodeInfoByViewId(
+                    R.id.button5);
+            assertFalse(button.isFocused());
+
+            // focus the view
+            assertTrue(button.performAction(ACTION_FOCUS));
+
+            // find the view again and make sure it is focused
+            button = getConnection().findAccessibilityNodeInfoByViewId(R.id.button5);
+            assertTrue(button.isFocused());
+        } finally {
+            afterClassIfNeeded();
+        }
+    }
+
+    @LargeTest
+    public void testPerformAccessibilityActionClearFocus() throws Exception {
+        beforeClassIfNeeded();
+        try {
+            // bring up the activity
+            getActivity();
+
+            // find a view and make sure it is not focused
+            AccessibilityNodeInfo button =  getConnection().findAccessibilityNodeInfoByViewId(
+                    R.id.button5);
+            assertFalse(button.isFocused());
+
+            // focus the view
+            assertTrue(button.performAction(ACTION_FOCUS));
+
+            // find the view again and make sure it is focused
+            button =  getConnection().findAccessibilityNodeInfoByViewId(R.id.button5);
+            assertTrue(button.isFocused());
+
+            // unfocus the view
+            assertTrue(button.performAction(ACTION_CLEAR_FOCUS));
+
+            // find the view again and make sure it is not focused
+            button =  getConnection().findAccessibilityNodeInfoByViewId(R.id.button5);
+            assertFalse(button.isFocused());
+        } finally {
+            afterClassIfNeeded();
+        }
+    }
+
+    @LargeTest
+    public void testPerformAccessibilityActionSelect() throws Exception {
+        beforeClassIfNeeded();
+        try {
+            // bring up the activity
+            getActivity();
+
+            // find a view and make sure it is not selected
+            AccessibilityNodeInfo button = getConnection().findAccessibilityNodeInfoByViewId(
+                    R.id.button5);
+            assertFalse(button.isSelected());
+
+            // select the view
+            assertTrue(button.performAction(ACTION_SELECT));
+
+            // find the view again and make sure it is selected
+            button = getConnection().findAccessibilityNodeInfoByViewId(R.id.button5);
+            assertTrue(button.isSelected());
+        } finally {
+            afterClassIfNeeded();
+        }
+    }
+
+    @LargeTest
+    public void testPerformAccessibilityActionClearSelection() throws Exception {
+        beforeClassIfNeeded();
+        try {
+            // bring up the activity
+            getActivity();
+
+            // find a view and make sure it is not selected
+            AccessibilityNodeInfo button = getConnection().findAccessibilityNodeInfoByViewId(
+                    R.id.button5);
+            assertFalse(button.isSelected());
+
+            // select the view
+            assertTrue(button.performAction(ACTION_SELECT));
+
+            // find the view again and make sure it is selected
+            button = getConnection().findAccessibilityNodeInfoByViewId(R.id.button5);
+            assertTrue(button.isSelected());
+
+            // unselect the view
+            assertTrue(button.performAction(ACTION_CLEAR_SELECTION));
+
+            // find the view again and make sure it is not selected
+            button = getConnection().findAccessibilityNodeInfoByViewId(R.id.button5);
+            assertFalse(button.isSelected());
+        } finally {
+            afterClassIfNeeded();
+        }
+    }
+
+    @LargeTest
+    public void testAccessibilityEventGetSource() throws Exception {
+        beforeClassIfNeeded();
+        try {
+            // bring up the activity
+            getActivity();
+
+            // find a view and make sure it is not focused
+            AccessibilityNodeInfo button = getConnection().findAccessibilityNodeInfoByViewId(
+                    R.id.button5);
+            assertFalse(button.isSelected());
+
+            // focus the view
+            assertTrue(button.performAction(ACTION_FOCUS));
+            SystemClock.sleep(200);
+
+            // check that last event source
+            AccessibilityNodeInfo source = sLastAccessibilityEvent.getSource();
+            assertNotNull(source);
+
+            // bounds
+            Rect buttonBounds = new Rect();
+            button.getBounds(buttonBounds);
+            Rect sourceBounds = new Rect();
+            source.getBounds(sourceBounds);
+
+            assertEquals(buttonBounds.left, sourceBounds.left);
+            assertEquals(buttonBounds.right, sourceBounds.right);
+            assertEquals(buttonBounds.top, sourceBounds.top);
+            assertEquals(buttonBounds.bottom, sourceBounds.bottom);
+
+            // char sequence attributes
+            assertEquals(button.getPackageName(), source.getPackageName());
+            assertEquals(button.getClassName(), source.getClassName());
+            assertEquals(button.getText(), source.getText());
+            assertSame(button.getContentDescription(), source.getContentDescription());
+
+            // boolean attributes
+            assertSame(button.isFocusable(), source.isFocusable());
+            assertSame(button.isClickable(), source.isClickable());
+            assertSame(button.isEnabled(), source.isEnabled());
+            assertNotSame(button.isFocused(), source.isFocused());
+            assertSame(button.isLongClickable(), source.isLongClickable());
+            assertSame(button.isPassword(), source.isPassword());
+            assertSame(button.isSelected(), source.isSelected());
+            assertSame(button.isCheckable(), source.isCheckable());
+            assertSame(button.isChecked(), source.isChecked());
+        } finally {
+            afterClassIfNeeded();
+        }
+    }
+
+    @Override
+    protected void scrubClass(Class<?> testCaseClass) {
+        /* intentionally do not scrub */
+    }
+
+    /**
+     * Sets accessibility in a given state by writing the state to the
+     * settings and waiting until the accessibility manager service picks
+     * it up for max {@link #TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING}.
+     *
+     * @param state The accessibility state.
+     * @throws Exception If any error occurs.
+     */
+    private void ensureAccessibilityState(boolean state) throws Exception {
+        Context context = getInstrumentation().getContext();
+        // If the local manager ready => nothing to do.
+        AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(context);
+        if (accessibilityManager.isEnabled() == state) {
+            return;
+        }
+        synchronized (this) {
+            // Check if the system already knows about the desired state. 
+            final boolean currentState = Settings.Secure.getInt(context.getContentResolver(),
+                    Settings.Secure.ACCESSIBILITY_ENABLED) == 1;
+            if (currentState != state) {
+                // Make sure we wake ourselves as the desired state is propagated.
+                accessibilityManager.addAccessibilityStateChangeListener(
+                        new AccessibilityManager.AccessibilityStateChangeListener() {
+                            public void onAccessibilityStateChanged(boolean enabled) {
+                                synchronized (this) {
+                                    notifyAll();
+                                }
+                            }
+                        });
+                Settings.Secure.putInt(context.getContentResolver(),
+                        Settings.Secure.ACCESSIBILITY_ENABLED, state ? 1 : 0);
+            }
+            // No while one attempt and that is it.
+            try {
+                wait(TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING);
+            } catch (InterruptedException ie) {
+                /* ignore */
+            }
+        }
+        if (accessibilityManager.isEnabled() != state) {
+            throw new IllegalStateException("Could not set accessibility state to: " + state);
+        }
+    }
+
+    /**
+     * Execute some set up code before any test method.
+     *
+     * NOTE: I miss Junit4's @BeforeClass
+     *
+     * @throws Exception If an error occurs.
+     */
+    private void beforeClassIfNeeded() throws Exception {
+        sExecutedTestMethodCount++;
+        if (sExecutedTestMethodCount == 1) {
+            ensureAccessibilityState(true);
+        }
+    }
+
+    /**
+     * Execute some clean up code after all test methods.
+     *
+     * NOTE: I miss Junit4's @AfterClass
+     *
+     * @throws Exception If an error occurs.
+     */
+    public void afterClassIfNeeded() throws Exception {
+        if (sExecutedTestMethodCount == sTestMethodCount) {
+            sExecutedTestMethodCount = 0;
+            ensureAccessibilityState(false);
+        }
+    }
+
+    private static IAccessibilityServiceConnection getConnection() throws Exception {
+        if (sConnection == null) {
+            IEventListener listener = new IEventListener.Stub() {
+                public void setConnection(IAccessibilityServiceConnection connection)
+                        throws RemoteException {
+                    AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+                    info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
+                    info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN;
+                    info.notificationTimeout = 0;
+                    info.flags = AccessibilityServiceInfo.DEFAULT;
+                    connection.setServiceInfo(info);
+                }
+
+                public void onInterrupt() {}
+
+                public void onAccessibilityEvent(AccessibilityEvent event) {
+                    sLastAccessibilityEvent= AccessibilityEvent.obtain(event);
+                }
+            };
+            IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
+                ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
+            sConnection = manager.registerEventListener(listener);
+        }
+        return sConnection;
+    }
+
+    /**
+     * @return The number of test methods.
+     */
+    private int getTestMethodCount() {
+        int testMethodCount = 0;
+        for (Method method : getClass().getMethods()) {
+            final int modifiers = method.getModifiers();
+            if (method.getName().startsWith("test")
+                    && (modifiers & Modifier.PUBLIC) != 0
+                    && (modifiers & Modifier.STATIC) == 0) {
+                testMethodCount++;
+            }
+        }
+        return testMethodCount;
+    }
+}
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 5ed7966..b9c0d80 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -132,6 +132,7 @@
     <assign-permission name="android.permission.BATTERY_STATS" uid="shell" />
     <assign-permission name="android.permission.INTERNAL_SYSTEM_WINDOW" uid="shell" />
     <assign-permission name="android.permission.INJECT_EVENTS" uid="shell" />
+    <assign-permission name="android.permission.RETRIEVE_WINDOW_CONTENT" uid="shell" />
     <assign-permission name="android.permission.SET_ACTIVITY_WATCHER" uid="shell" />
     <assign-permission name="android.permission.READ_INPUT_STATE" uid="shell" />
     <assign-permission name="android.permission.SET_ORIENTATION" uid="shell" />
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index fba293c..86671d6 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -16,13 +16,7 @@
 
 package com.android.server.accessibility;
 
-import com.android.internal.content.PackageMonitor;
-import com.android.internal.os.HandlerCaller;
-import com.android.internal.os.HandlerCaller.SomeArgs;
-import com.android.server.wm.WindowManagerService;
-
-import org.xmlpull.v1.XmlPullParserException;
-
+import android.Manifest;
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceConnection;
@@ -37,7 +31,6 @@
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Binder;
@@ -46,15 +39,27 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemClock;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.text.TextUtils.SimpleStringSplitter;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.view.IWindow;
 import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.IAccessibilityInteractionConnection;
+import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
 import android.view.accessibility.IAccessibilityManager;
 import android.view.accessibility.IAccessibilityManagerClient;
 
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.HandlerCaller.SomeArgs;
+import com.android.server.wm.WindowManagerService;
+
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -64,6 +69,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * This class is instantiated by the system as a system level service and can be
@@ -80,12 +86,17 @@
 
     private static final String LOG_TAG = "AccessibilityManagerService";
 
+    private static final String FUNCTION_REGISTER_EVENT_LISTENER =
+        "registerEventListener";
+
     private static int sIdCounter = 0;
 
     private static final int OWN_PROCESS_ID = android.os.Process.myPid();
 
     private static final int DO_SET_SERVICE_INFO = 10;
 
+    private static int sNextWindowId;
+
     final HandlerCaller mCaller;
 
     final Context mContext;
@@ -103,6 +114,11 @@
 
     private final Set<ComponentName> mEnabledServices = new HashSet<ComponentName>();
 
+    private final SparseArray<IAccessibilityInteractionConnection> mWindowIdToInteractionConnectionMap =
+        new SparseArray<IAccessibilityInteractionConnection>();
+
+    private final SparseArray<IBinder> mWindowIdToWindowTokenMap = new SparseArray<IBinder>();
+
     private final SimpleStringSplitter mStringColonSplitter = new SimpleStringSplitter(':');
 
     private PackageManager mPackageManager;
@@ -117,6 +133,10 @@
 
     private boolean mHasInputFilter;
 
+    private final WindowManagerService mWindowManagerService;
+
+    private final SecurityPolicy mSecurityPolicy;
+
     /**
      * Handler for delayed event dispatch.
      */
@@ -145,6 +165,9 @@
         mContext = context;
         mPackageManager = mContext.getPackageManager();
         mCaller = new HandlerCaller(context, this);
+        mWindowManagerService = (WindowManagerService) ServiceManager.getService(
+                Context.WINDOW_SERVICE);
+        mSecurityPolicy = new SecurityPolicy();
 
         registerPackageChangeAndBootCompletedBroadcastReceiver();
         registerSettingsContentObservers();
@@ -208,7 +231,6 @@
                 if (intent.getAction() == Intent.ACTION_BOOT_COMPLETED) {
                     synchronized (mLock) {
                         populateAccessibilityServiceListLocked();
-
                         // get the accessibility enabled setting on boot
                         mIsEnabled = Settings.Secure.getInt(mContext.getContentResolver(),
                                 Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
@@ -223,7 +245,7 @@
                     
                     return;
                 }
-                
+
                 super.onReceive(context, intent);
             }
         };
@@ -297,6 +319,7 @@
 
     public boolean sendAccessibilityEvent(AccessibilityEvent event) {
         synchronized (mLock) {
+            mSecurityPolicy.updateRetrievalAllowingWindowAndEventSourceLocked(event);
             notifyAccessibilityServicesDelayedLocked(event, false);
             notifyAccessibilityServicesDelayedLocked(event, true);
         }
@@ -352,7 +375,7 @@
 
     public void executeMessage(Message message) {
         switch (message.what) {
-            case DO_SET_SERVICE_INFO:
+            case DO_SET_SERVICE_INFO: {
                 SomeArgs arguments = ((SomeArgs) message.obj);
 
                 AccessibilityServiceInfo info = (AccessibilityServiceInfo) arguments.arg1;
@@ -365,17 +388,73 @@
                     AccessibilityServiceInfo oldInfo = service.mAccessibilityServiceInfo;
                     if (oldInfo != null) {
                         oldInfo.updateDynamicallyConfigurableProperties(info);
-                        service.setAccessibilityServiceInfo(oldInfo);
+                        service.setDynamicallyConfigurableProperties(oldInfo);
                     } else {
-                        service.setAccessibilityServiceInfo(info);
+                        service.setDynamicallyConfigurableProperties(info);
                     }
                 }
-                return;
+            } return;
             default:
                 Slog.w(LOG_TAG, "Unknown message type: " + message.what);
         }
     }
 
+    public int addAccessibilityInteractionConnection(IWindow windowToken,
+            IAccessibilityInteractionConnection connection) throws RemoteException {
+        synchronized (mLock) {
+            final IWindow addedWindowToken = windowToken;
+            final int windowId = sNextWindowId++;
+            connection.asBinder().linkToDeath(new DeathRecipient() {
+                public void binderDied() {
+                    synchronized (mLock) {
+                        removeAccessibilityInteractionConnection(addedWindowToken);
+                    }
+                }
+            }, 0);
+            mWindowIdToWindowTokenMap.put(windowId, addedWindowToken.asBinder());
+            mWindowIdToInteractionConnectionMap.put(windowId, connection);
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "Adding interaction connection to windowId: " + windowId);
+            }
+            return windowId;
+        }
+    }
+
+    public void removeAccessibilityInteractionConnection(IWindow windowToken) {
+        synchronized (mLock) {
+            final int count = mWindowIdToWindowTokenMap.size();
+            for (int i = 0; i < count; i++) {
+                if (mWindowIdToWindowTokenMap.valueAt(i) == windowToken.asBinder()) {
+                    final int windowId = mWindowIdToWindowTokenMap.keyAt(i);
+                    mWindowIdToWindowTokenMap.remove(windowId);
+                    mWindowIdToInteractionConnectionMap.remove(windowId);
+                    if (DEBUG) {
+                        Slog.i(LOG_TAG, "Removing interaction connection to windowId: " + windowId);
+                    }
+                    return;
+                }
+            }
+        }
+    }
+
+    public IAccessibilityServiceConnection registerEventListener(IEventListener listener) {
+        mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT,
+                FUNCTION_REGISTER_EVENT_LISTENER);
+        ComponentName componentName = new ComponentName("foo.bar", "FakeAccessibilityService");
+        synchronized (mLock) {
+            Service oldService = mComponentNameToServiceMap.get(componentName);
+            if (oldService != null) {
+                tryRemoveServiceLocked(oldService);
+            }
+        }
+        AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo();
+        accessibilityServiceInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
+        accessibilityServiceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
+        Service service = new Service(componentName, accessibilityServiceInfo, true);
+        service.onServiceConnected(componentName, listener.asBinder());
+        return service;
+    }
+
     /**
      * Populates the cached list of installed {@link AccessibilityService}s.
      */
@@ -488,6 +567,12 @@
         AccessibilityEvent event = service.mPendingEvents.get(eventType);
 
         try {
+            if (mSecurityPolicy.canRetrieveWindowContent(service)) {
+                event.setConnection(service);
+            } else {
+                event.setSource(null);
+            }
+            event.setSealed(true);
             listener.onAccessibilityEvent(event);
             if (DEBUG) {
                 Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
@@ -646,7 +731,7 @@
             if (isEnabled) {
                 if (enabledServices.contains(componentName)) {
                     if (service == null) {
-                        service = new Service(installedService);
+                        service = new Service(componentName, installedService, false);
                     }
                     service.bind();
                 } else if (!enabledServices.contains(componentName)) {
@@ -683,8 +768,6 @@
      * spoken feedback.
      */
     private void updateInputFilterLocked() {
-        WindowManagerService wm = (WindowManagerService)ServiceManager.getService(
-                Context.WINDOW_SERVICE);
         if (mIsEnabled) {
             final boolean hasSpokenFeedbackServices = !getEnabledAccessibilityServiceList(
                     AccessibilityServiceInfo.FEEDBACK_SPOKEN).isEmpty();
@@ -695,19 +778,14 @@
                 if (mInputFilter == null) {
                     mInputFilter = new AccessibilityInputFilter(mContext);
                 }
-                wm.setInputFilter(mInputFilter);
+                mWindowManagerService.setInputFilter(mInputFilter);
                 mHasInputFilter = true;
-            } else {
-                if (mHasInputFilter) {
-                    wm.setInputFilter(null);
-                    mHasInputFilter = false;
-                }
+                return;
             }
-        } else {
-            if (mHasInputFilter) {
-                wm.setInputFilter(null);
-                mHasInputFilter = false;
-            }
+        }
+        if (mHasInputFilter) {
+            mWindowManagerService.setInputFilter(null);
+            mHasInputFilter = false;
         }
     }
 
@@ -743,24 +821,38 @@
 
         Intent mIntent;
 
+        boolean mCanRetrieveScreenContent;
+
+        boolean mIsFake;
+
+        final Callback mCallback = new Callback();
+
+        final AtomicInteger mInteractionIdCounter = new AtomicInteger();
+
         // the events pending events to be dispatched to this service
         final SparseArray<AccessibilityEvent> mPendingEvents =
             new SparseArray<AccessibilityEvent>();
 
-        Service(AccessibilityServiceInfo accessibilityServiceInfo) {
+        public Service(ComponentName componentName,
+                AccessibilityServiceInfo accessibilityServiceInfo, boolean isFake) {
             mId = sIdCounter++;
-            setAccessibilityServiceInfo(accessibilityServiceInfo);
-            mIntent = new Intent().setComponent(mComponentName);
-            mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
-                    com.android.internal.R.string.accessibility_binding_label);
-            mIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
-                    mContext, 0, new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS), 0));
+            mComponentName = componentName;
+            mAccessibilityServiceInfo = accessibilityServiceInfo;
+            mIsFake = isFake;
+            if (!isFake) {
+                mCanRetrieveScreenContent = accessibilityServiceInfo.getCanRetrieveWindowContent();
+                mIntent = new Intent().setComponent(mComponentName);
+                mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
+                        com.android.internal.R.string.accessibility_binding_label);
+                mIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
+                        mContext, 0, new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS), 0));
+            } else {
+                mCanRetrieveScreenContent = true;
+            }
+            setDynamicallyConfigurableProperties(accessibilityServiceInfo);
         }
 
-        public void setAccessibilityServiceInfo(AccessibilityServiceInfo info) {
-            mAccessibilityServiceInfo = info;
-            ServiceInfo serviceInfo = info.getResolveInfo().serviceInfo;
-            mComponentName = new ComponentName(serviceInfo.packageName, serviceInfo.name);
+        public void setDynamicallyConfigurableProperties(AccessibilityServiceInfo info) {
             mEventTypes = info.eventTypes;
             mFeedbackType = info.feedbackType;
             String[] packageNames = info.packageNames;
@@ -781,7 +873,7 @@
          * @return True if binding is successful.
          */
         public boolean bind() {
-            if (mService == null) {
+            if (!mIsFake && mService == null) {
                 return mContext.bindService(mIntent, this, Context.BIND_AUTO_CREATE);
             }
             return false;
@@ -798,7 +890,9 @@
                 synchronized (mLock) {
                     tryRemoveServiceLocked(this);
                 }
-                mContext.unbindService(this);
+                if (!mIsFake) {
+                    mContext.unbindService(this);
+                }
                 mService = null;
                 return true;
             }
@@ -832,6 +926,154 @@
             }
         }
 
+        public AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int viewId) {
+            IAccessibilityInteractionConnection connection = null;
+            synchronized (mLock) {
+                final boolean permissionGranted = mSecurityPolicy.canRetrieveWindowContent(this);
+                if (permissionGranted) {
+                    connection = getConnectionToRetrievalAllowingWindowLocked();
+                }
+            }
+            if (connection == null) {
+                if (DEBUG) {
+                    Slog.e(LOG_TAG, "No interaction connection to a retrieve allowing window.");
+                }
+                return null;
+            }
+            final long identityToken = Binder.clearCallingIdentity();
+            try {
+                final int interactionId = mInteractionIdCounter.getAndIncrement();
+                connection.findAccessibilityNodeInfoByViewId(viewId, interactionId, mCallback);
+                AccessibilityNodeInfo info = mCallback.getFindAccessibilityNodeInfoResultAndClear(
+                        interactionId);
+                if (info != null) {
+                    info.setConnection(this);
+                }
+                info.setSealed(true);
+                return info;
+            } catch (RemoteException re) {
+                if (DEBUG) {
+                    Slog.e(LOG_TAG, "Error finding node.");
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identityToken);
+            }
+            return null;
+        }
+
+        public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewText(String text) {
+            IAccessibilityInteractionConnection connection = null;
+            synchronized (mLock) {
+                final boolean permissionGranted = mSecurityPolicy.canRetrieveWindowContent(this);
+                if (permissionGranted) {
+                    connection = getConnectionToRetrievalAllowingWindowLocked();
+                }
+            }
+            if (connection == null) {
+                if (DEBUG) {
+                    Slog.e(LOG_TAG, "No interaction connection to focused window.");
+                }
+                return null;
+            }
+            final long identityToken = Binder.clearCallingIdentity();
+            try {
+                final int interactionId = mInteractionIdCounter.getAndIncrement();
+                connection.findAccessibilityNodeInfosByViewText(text, interactionId, mCallback);
+                List<AccessibilityNodeInfo> infos =
+                    mCallback.getFindAccessibilityNodeInfosResultAndClear(interactionId);
+                if (infos != null) {
+                    final int infoCount = infos.size();
+                    for (int i = 0; i < infoCount; i++) {
+                        AccessibilityNodeInfo info = infos.get(i);
+                        info.setConnection(this);
+                        info.setSealed(true);
+                    }
+                }
+                return infos;
+            } catch (RemoteException re) {
+                if (DEBUG) {
+                    Slog.e(LOG_TAG, "Error finding node.");
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identityToken);
+            }
+            return null;
+        }
+
+        public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(
+                int accessibilityWindowId, int accessibilityViewId) {
+            IAccessibilityInteractionConnection connection = null;
+            synchronized (mLock) {
+                final boolean permissionGranted =
+                    mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, accessibilityWindowId);
+                if (permissionGranted) {
+                    connection = mWindowIdToInteractionConnectionMap.get(accessibilityWindowId);
+                }
+            }
+            if (connection == null) {
+                if (DEBUG) {
+                    Slog.e(LOG_TAG, "No interaction connection to window: "
+                            + accessibilityWindowId);
+                }
+                return null;
+            }
+            final long identityToken = Binder.clearCallingIdentity();
+            try {
+                final int interactionId = mInteractionIdCounter.getAndIncrement();
+                connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityViewId,
+                        interactionId, mCallback);
+                AccessibilityNodeInfo info =
+                     mCallback.getFindAccessibilityNodeInfoResultAndClear(interactionId);
+                if (info != null) {
+                    info.setConnection(this);
+                    info.setSealed(true);
+                }
+                return info;
+            } catch (RemoteException re) {
+                if (DEBUG) {
+                    Slog.e(LOG_TAG, "Error requesting node with accessibilityViewId: "
+                            + accessibilityViewId);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identityToken);
+            }
+            return null;
+        }
+
+        public boolean performAccessibilityAction(int accessibilityWindowId,
+                int accessibilityViewId, int action) {
+            IAccessibilityInteractionConnection connection = null;
+            synchronized (mLock) {
+                final boolean permissionGranted = mSecurityPolicy.canPerformActionLocked(this,
+                        accessibilityWindowId, action);
+                if (permissionGranted) {
+                    connection = mWindowIdToInteractionConnectionMap.get(accessibilityWindowId);
+                }
+            }
+            if (connection == null) {
+                if (DEBUG) {
+                    Slog.e(LOG_TAG, "No interaction connection to window: "
+                            + accessibilityWindowId);
+                }
+                return false;
+            }
+            final long identityToken = Binder.clearCallingIdentity();
+            try {
+                final int interactionId = mInteractionIdCounter.getAndIncrement();
+                connection.performAccessibilityAction(accessibilityViewId, action, interactionId,
+                        mCallback);
+                return mCallback.getPerformAccessibilityActionResult(interactionId);
+            } catch (RemoteException re) {
+                if (DEBUG) {
+                    Slog.e(LOG_TAG, "Error requesting node with accessibilityViewId: "
+                            + accessibilityViewId);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identityToken);
+            }
+            return false;
+        }
+
         public void onServiceDisconnected(ComponentName componentName) {
             /* do nothing - #binderDied takes care */
         }
@@ -849,5 +1091,171 @@
                 tryRemoveServiceLocked(this);
             }
         }
+
+        private IAccessibilityInteractionConnection getConnectionToRetrievalAllowingWindowLocked() {
+            final int windowId = mSecurityPolicy.getRetrievalAllowingWindowLocked();
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId);
+            }
+            return mWindowIdToInteractionConnectionMap.get(windowId);
+        }
+    }
+
+    final class SecurityPolicy {
+        private static final int VALID_ACTIONS = AccessibilityNodeInfo.ACTION_FOCUS
+            | AccessibilityNodeInfo.ACTION_CLEAR_FOCUS | AccessibilityNodeInfo.ACTION_SELECT
+            | AccessibilityNodeInfo.ACTION_CLEAR_SELECTION;
+
+        private static final int RETRIEVAL_ALLOWING_EVENT_TYPES =
+            AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END
+            | AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START
+            | AccessibilityEvent.TYPE_VIEW_CLICKED | AccessibilityEvent.TYPE_VIEW_FOCUSED
+            | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT
+            | AccessibilityEvent.TYPE_VIEW_LONG_CLICKED | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
+            | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
+
+        private int mRetrievalAlowingWindowId;
+
+        public void updateRetrievalAllowingWindowAndEventSourceLocked(AccessibilityEvent event) {
+            final int windowId = event.getSourceAccessibilityWindowId();
+            final int eventType = event.getEventType();
+            if ((eventType & RETRIEVAL_ALLOWING_EVENT_TYPES) != 0) {
+                mRetrievalAlowingWindowId = windowId;
+            } else {
+                event.setSource(null);
+            }
+        }
+
+        public int getRetrievalAllowingWindowLocked() {
+            return mRetrievalAlowingWindowId;
+        }
+
+        public boolean canGetAccessibilityNodeInfoLocked(Service service, int windowId) {
+            return canRetrieveWindowContent(service) && isRetrievalAllowingWindow(windowId);
+        }
+
+        public boolean canPerformActionLocked(Service service, int windowId, int action) {
+            return canRetrieveWindowContent(service)
+                && isRetrievalAllowingWindow(windowId)
+                && isActionPermitted(action);
+        }
+
+        public boolean canRetrieveWindowContent(Service service) {
+            return service.mCanRetrieveScreenContent;
+        }
+
+        private boolean isRetrievalAllowingWindow(int windowId) {
+            return (mRetrievalAlowingWindowId == windowId);
+        }
+
+        private boolean isActionPermitted(int action) {
+             return (VALID_ACTIONS & action) != 0;
+        }
+
+        private void enforceCallingPermission(String permission, String function) {
+            if (OWN_PROCESS_ID == Binder.getCallingPid()) {
+                return;
+            }
+            final int permissionStatus = mContext.checkCallingPermission(permission);
+            if (permissionStatus != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("You do not have " + permission
+                        + " required to call " + function);
+            }
+        }
+    }
+
+    final class Callback extends IAccessibilityInteractionConnectionCallback.Stub {
+        private static final long TIMEOUT_INTERACTION_MILLIS = 5000;
+
+        private int mInteractionId = -1;
+        private AccessibilityNodeInfo mFindAccessibilityNodeInfoResult;
+        private List<AccessibilityNodeInfo> mFindAccessibilityNodeInfosResult;
+        private boolean mPerformAccessibilityActionResult;
+
+        public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info,
+                int interactionId) {
+            synchronized (mLock) {
+                if (interactionId > mInteractionId) {
+                    mFindAccessibilityNodeInfoResult = info;
+                    mInteractionId = interactionId;
+                }
+                mLock.notifyAll();
+            }
+        }
+
+        public AccessibilityNodeInfo getFindAccessibilityNodeInfoResultAndClear(int interactionId) {
+            synchronized (mLock) {
+                waitForResultTimedLocked(TIMEOUT_INTERACTION_MILLIS, interactionId);
+                AccessibilityNodeInfo result = mFindAccessibilityNodeInfoResult;
+                clearLocked();
+                return result;
+            }
+        }
+
+        public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos,
+                int interactionId) {
+            synchronized (mLock) {
+                if (interactionId > mInteractionId) {
+                    mFindAccessibilityNodeInfosResult = infos;
+                    mInteractionId = interactionId;
+                }
+                mLock.notifyAll();
+            }
+        }
+
+        public List<AccessibilityNodeInfo> getFindAccessibilityNodeInfosResultAndClear(
+                int interactionId) {
+            synchronized (mLock) {
+                waitForResultTimedLocked(TIMEOUT_INTERACTION_MILLIS, interactionId);
+                List<AccessibilityNodeInfo> result = mFindAccessibilityNodeInfosResult;
+                clearLocked();
+                return result;
+            }
+        }
+
+        public void setPerformAccessibilityActionResult(boolean succeeded, int interactionId) {
+            synchronized (mLock) {
+                if (interactionId > mInteractionId) {
+                    mPerformAccessibilityActionResult = succeeded;
+                    mInteractionId = interactionId;
+                }
+                mLock.notifyAll();
+            }
+        }
+
+        public boolean getPerformAccessibilityActionResult(int interactionId) {
+            synchronized (mLock) {
+                waitForResultTimedLocked(TIMEOUT_INTERACTION_MILLIS, interactionId);
+                final boolean result = mPerformAccessibilityActionResult;
+                clearLocked();
+                return result;
+            }
+        }
+
+        public void clearLocked() {
+            mInteractionId = -1;
+            mFindAccessibilityNodeInfoResult = null;
+            mFindAccessibilityNodeInfosResult = null;
+            mPerformAccessibilityActionResult = false;
+        }
+
+        private void waitForResultTimedLocked(long waitTimeMillis, int interactionId) {
+            final long startTimeMillis = SystemClock.uptimeMillis();
+            while (true) {
+                try {
+                    if (mInteractionId == interactionId) {
+                        return;
+                    }
+                    final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+                    waitTimeMillis = TIMEOUT_INTERACTION_MILLIS - elapsedTimeMillis;
+                    if (waitTimeMillis <= 0) {
+                        return;
+                    }
+                    mLock.wait(waitTimeMillis);
+                } catch (InterruptedException ie) {
+                    /* ignore */
+                }
+            }
+        }
     }
 }