am 2eecea3b: Merge "Speedup the accessibility window querying APIs and clean up."

* commit '2eecea3b48ece6f45b30fef9b41dc20075ccc94f':
  Speedup the accessibility window querying APIs and clean up.
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 211be52..a463a62 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -17,8 +17,10 @@
 package android.accessibilityservice;
 
 import android.app.Service;
+import android.content.Context;
 import android.content.Intent;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
@@ -218,10 +220,17 @@
 
     private static final String LOG_TAG = "AccessibilityService";
 
-    private AccessibilityServiceInfo mInfo;
+    interface Callbacks {
+        public void onAccessibilityEvent(AccessibilityEvent event);
+        public void onInterrupt();
+        public void onServiceConnected();
+        public void onSetConnectionId(int connectionId);
+    }
 
     private int mConnectionId;
 
+    private AccessibilityServiceInfo mInfo;
+
     /**
      * Callback for {@link android.view.accessibility.AccessibilityEvent}s.
      *
@@ -282,27 +291,49 @@
      */
     @Override
     public final IBinder onBind(Intent intent) {
-        return new IEventListenerWrapper(this);
+        return new IEventListenerWrapper(this, getMainLooper(), new Callbacks() {
+            @Override
+            public void onServiceConnected() {
+                AccessibilityService.this.onServiceConnected();
+            }
+
+            @Override
+            public void onInterrupt() {
+                AccessibilityService.this.onInterrupt();
+            }
+
+            @Override
+            public void onAccessibilityEvent(AccessibilityEvent event) {
+                AccessibilityService.this.onAccessibilityEvent(event);
+            }
+
+            @Override
+            public void onSetConnectionId( int connectionId) {
+                mConnectionId = connectionId;
+            }
+        });
     }
 
     /**
      * Implements the internal {@link IEventListener} interface to convert
      * incoming calls to it back to calls on an {@link AccessibilityService}.
      */
-    class IEventListenerWrapper extends IEventListener.Stub
+    static class IEventListenerWrapper extends IEventListener.Stub
             implements HandlerCaller.Callback {
 
+        static final int NO_ID = -1;
+
         private static final int DO_SET_SET_CONNECTION = 10;
         private static final int DO_ON_INTERRUPT = 20;
         private static final int DO_ON_ACCESSIBILITY_EVENT = 30;
 
         private final HandlerCaller mCaller;
 
-        private final AccessibilityService mTarget;
+        private final Callbacks mCallback;
 
-        public IEventListenerWrapper(AccessibilityService context) {
-            mTarget = context;
-            mCaller = new HandlerCaller(context, this);
+        public IEventListenerWrapper(Context context, Looper looper, Callbacks callback) {
+            mCallback = callback;
+            mCaller = new HandlerCaller(context, looper, this);
         }
 
         public void setConnection(IAccessibilityServiceConnection connection, int connectionId) {
@@ -326,12 +357,13 @@
                 case DO_ON_ACCESSIBILITY_EVENT :
                     AccessibilityEvent event = (AccessibilityEvent) message.obj;
                     if (event != null) {
-                        mTarget.onAccessibilityEvent(event);
+                        AccessibilityInteractionClient.getInstance().onAccessibilityEvent(event);
+                        mCallback.onAccessibilityEvent(event);
                         event.recycle();
                     }
                     return;
                 case DO_ON_INTERRUPT :
-                    mTarget.onInterrupt();
+                    mCallback.onInterrupt();
                     return;
                 case DO_SET_SET_CONNECTION :
                     final int connectionId = message.arg1;
@@ -340,12 +372,11 @@
                     if (connection != null) {
                         AccessibilityInteractionClient.getInstance().addConnection(connectionId,
                                 connection);
-                        mConnectionId = connectionId;
-                        mTarget.onServiceConnected();
+                        mCallback.onSetConnectionId(connectionId);
+                        mCallback.onServiceConnected();
                     } else {
                         AccessibilityInteractionClient.getInstance().removeConnection(connectionId);
-                        mConnectionId = AccessibilityInteractionClient.NO_ID;
-                        // TODO: Do we need a onServiceDisconnected callback?
+                        mCallback.onSetConnectionId(AccessibilityInteractionClient.NO_ID);
                     }
                     return;
                 default :
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index e53b313..c9468eb 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -32,8 +32,13 @@
     /**
      * Finds an {@link AccessibilityNodeInfo} by accessibility id.
      *
-     * @param accessibilityWindowId A unique window id.
-     * @param accessibilityNodeId A unique view id or virtual descendant id.
+     * @param accessibilityWindowId A unique window id. Use
+     *     {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
+     *     to query the currently active window.
+     * @param accessibilityNodeId A unique view id or virtual descendant id from
+     *     where to start the search. Use
+     *     {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
+     *     to start from the root.
      * @param interactionId The id of the interaction for matching with the callback result.
      * @param callback Callback which to receive the result.
      * @param threadId The id of the calling thread.
@@ -46,57 +51,58 @@
     /**
      * Finds {@link AccessibilityNodeInfo}s by View text. The match is case
      * insensitive containment. The search is performed in the window whose
-     * id is specified and starts from the View whose accessibility id is
+     * id is specified and starts from the node whose accessibility id is
      * specified.
      *
-     * @param text The searched text.
-     * @param accessibilityWindowId A unique window id.
+     * @param accessibilityWindowId A unique window id. Use
+     *     {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
+     *     to query the currently active window.
      * @param accessibilityNodeId A unique view id or virtual descendant id from
-     *        where to start the search. Use {@link android.view.View#NO_ID} to start from the root.
-     * @param interactionId The id of the interaction for matching with the callback result.
-     * @param callback Callback which to receive the result.
-     * @param threadId The id of the calling thread.
-     * @return The current window scale, where zero means a failure.
-     */
-    float findAccessibilityNodeInfosByText(String text, int accessibilityWindowId,
-        long accessibilityNodeId, int interractionId,
-        IAccessibilityInteractionConnectionCallback callback, long threadId);
-
-    /**
-     * Finds {@link AccessibilityNodeInfo}s by View text. The match is case
-     * insensitive containment. The search is performed in the currently
-     * active window and start from the root View in the window.
-     *
+     *     where to start the search. Use
+     *     {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
+     *     to start from the root.
      * @param text The searched text.
-     * @param accessibilityId The id of the view from which to start searching.
-     *        Use {@link android.view.View#NO_ID} to start from the root.
      * @param interactionId The id of the interaction for matching with the callback result.
      * @param callback Callback which to receive the result.
      * @param threadId The id of the calling thread.
      * @return The current window scale, where zero means a failure.
      */
-    float findAccessibilityNodeInfosByTextInActiveWindow(String text,
-        int interactionId, IAccessibilityInteractionConnectionCallback callback,
+    float findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId,
+        String text, int interactionId, IAccessibilityInteractionConnectionCallback callback,
         long threadId);
 
     /**
-     * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed
-     * in the currently active window and starts from the root View in the window.
+     * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed in
+     * the window whose id is specified and starts from the node whose accessibility
+     * id is specified.
      *
+     * @param accessibilityWindowId A unique window id. Use
+     *     {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
+     *     to query the currently active window.
+     * @param accessibilityNodeId A unique view id or virtual descendant id from
+     *     where to start the search. Use
+     *     {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
+     *     to start from the root.
      * @param id The id of the node.
      * @param interactionId The id of the interaction for matching with the callback result.
      * @param callback Callback which to receive the result.
      * @param threadId The id of the calling thread.
      * @return The current window scale, where zero means a failure.
      */
-    float findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId, int interactionId,
-        IAccessibilityInteractionConnectionCallback callback, long threadId);
+    float findAccessibilityNodeInfoByViewId(int accessibilityWindowId, long accessibilityNodeId,
+        int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
+        long threadId);
 
     /**
      * Performs an accessibility action on an {@link AccessibilityNodeInfo}.
      *
-     * @param accessibilityWindowId The id of the window.
-     * @param accessibilityNodeId A unique view id or virtual descendant id.
+     * @param accessibilityWindowId A unique window id. Use
+     *     {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
+     *     to query the currently active window.
+     * @param accessibilityNodeId A unique view id or virtual descendant id from
+     *     where to start the search. Use
+     *     {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
+     *     to start from the root.
      * @param action The action to perform.
      * @param interactionId The id of the interaction for matching with the callback result.
      * @param callback Callback which to receive the result.
diff --git a/core/java/android/accessibilityservice/UiTestAutomationBridge.java b/core/java/android/accessibilityservice/UiTestAutomationBridge.java
new file mode 100644
index 0000000..9d48efc
--- /dev/null
+++ b/core/java/android/accessibilityservice/UiTestAutomationBridge.java
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice;
+
+import android.accessibilityservice.AccessibilityService.Callbacks;
+import android.accessibilityservice.AccessibilityService.IEventListenerWrapper;
+import android.content.Context;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityInteractionClient;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.IAccessibilityManager;
+
+import com.android.internal.util.Predicate;
+
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * This class represents a bridge that can be used for UI test
+ * automation. It is responsible for connecting to the system,
+ * keeping track of the last accessibility event, and exposing
+ * window content querying APIs. This class is designed to be
+ * used from both an Android application and a Java program
+ * run from the shell.
+ *
+ * @hide
+ */
+public class UiTestAutomationBridge {
+
+    private static final String LOG_TAG = UiTestAutomationBridge.class.getSimpleName();
+
+    public static final int ACTIVE_WINDOW_ID = -1;
+
+    public static final long ROOT_NODE_ID = -1;
+
+    private static final int TIMEOUT_REGISTER_SERVICE = 5000;
+
+    private final Object mLock = new Object();
+
+    private volatile int mConnectionId = AccessibilityInteractionClient.NO_ID;
+
+    private IEventListenerWrapper mListener;
+
+    private AccessibilityEvent mLastEvent;
+
+    private volatile boolean mWaitingForEventDelivery;
+
+    private volatile boolean mUnprocessedEventAvailable;
+
+    /**
+     * Gets the last received {@link AccessibilityEvent}.
+     *
+     * @return The event.
+     */
+    public AccessibilityEvent getLastAccessibilityEvent() {
+        return mLastEvent; 
+    }
+
+    /**
+     * Callback for receiving an {@link AccessibilityEvent}.
+     *
+     * <strong>Note:</strong> This method is <strong>NOT</strong>
+     * executed on the application main thread. The client are
+     * responsible for proper synchronization.
+     *
+     * @param event The received event.
+     */
+    public void onAccessibilityEvent(AccessibilityEvent event) {
+        /* hook - do nothing */
+    }
+
+    /**
+     * Callback for requests to stop feedback.
+     *
+     * <strong>Note:</strong> This method is <strong>NOT</strong>
+     * executed on the application main thread. The client are
+     * responsible for proper synchronization.
+     */
+    public void onInterrupt() {
+        /* hook - do nothing */
+    }
+
+    /**
+     * Connects this service.
+     *
+     * @throws IllegalStateException If already connected.
+     */
+    public void connect() {
+        if (isConnected()) {
+            throw new IllegalStateException("Already connected.");
+        }
+
+        // Serialize binder calls to a handler on a dedicated thread
+        // different from the main since we expose APIs that block
+        // the main thread waiting for a result the deliver of which
+        // on the main thread will prevent that thread from waking up.
+        // The serialization is needed also to ensure that events are
+        // examined in delivery order. Otherwise, a fair locking
+        // is needed for making sure the binder calls are interleaved
+        // with check for the expected event and also to make sure the
+        // binder threads are allowed to proceed in the received order.
+        HandlerThread handlerThread = new HandlerThread("UiTestAutomationBridge");
+        handlerThread.start();
+        Looper looper = handlerThread.getLooper();
+
+        mListener = new IEventListenerWrapper(null, looper, new Callbacks() {
+            @Override
+            public void onServiceConnected() {
+                /* do nothing */
+            }
+
+            @Override
+            public void onInterrupt() {
+                UiTestAutomationBridge.this.onInterrupt();  
+            }
+
+            @Override
+            public void onAccessibilityEvent(AccessibilityEvent event) {
+                synchronized (mLock) {
+                    while (true) {
+                        if (!mWaitingForEventDelivery) {
+                            break;
+                        }
+                        if (!mUnprocessedEventAvailable) {
+                            mUnprocessedEventAvailable = true;
+                            mLastEvent = AccessibilityEvent.obtain(event);
+                            mLock.notifyAll();
+                            break;
+                        }
+                        try {
+                            mLock.wait();
+                        } catch (InterruptedException ie) {
+                            /* ignore */
+                        }
+                    }
+                }
+                UiTestAutomationBridge.this.onAccessibilityEvent(event);
+            }
+
+            @Override
+            public void onSetConnectionId(int connectionId) {
+                synchronized (mLock) {
+                    mConnectionId = connectionId;
+                    mLock.notifyAll();
+                }
+            }
+        });
+
+        final IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
+                ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
+
+        final AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+        info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
+        info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
+
+        try {
+            manager.registerUiTestAutomationService(mListener, info);
+        } catch (RemoteException re) {
+            throw new IllegalStateException("Cound not register UiAutomationService.", re);
+        }
+
+        synchronized (mLock) {
+            final long startTimeMillis = SystemClock.uptimeMillis();
+            while (true) {
+                if (isConnected()) {
+                    return;
+                }
+                final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+                final long remainingTimeMillis = TIMEOUT_REGISTER_SERVICE - elapsedTimeMillis;
+                if (remainingTimeMillis <= 0) {
+                    throw new IllegalStateException("Cound not register UiAutomationService.");
+                }
+                try {
+                    mLock.wait(remainingTimeMillis);
+                } catch (InterruptedException ie) {
+                    /* ignore */
+                }
+            }
+        }
+    }
+
+    /**
+     * Disconnects this service.
+     *
+     * @throws IllegalStateException If already disconnected.
+     */
+    public void disconnect() {
+        if (!isConnected()) {
+            throw new IllegalStateException("Already disconnected.");
+        }
+
+        IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
+              ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
+
+        try {
+            manager.unregisterUiTestAutomationService(mListener);
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while unregistering UiTestAutomationService", re);
+        }
+    }
+
+    /**
+     * Gets whether this service is connected.
+     *
+     * @return True if connected.
+     */
+    public boolean isConnected() {
+        return (mConnectionId != AccessibilityInteractionClient.NO_ID);
+    }
+
+    /**
+     * Executes a command and waits for a specific accessibility event type up
+     * to a given timeout.
+     *
+     * @param command The command to execute before starting to wait for the event.
+     * @param predicate Predicate for recognizing the awaited event.
+     * @param timeoutMillis The max wait time in milliseconds.
+     */
+    public AccessibilityEvent executeCommandAndWaitForAccessibilityEvent(Runnable command,
+            Predicate<AccessibilityEvent> predicate, long timeoutMillis)
+            throws TimeoutException, Exception {
+        synchronized (mLock) {
+            // Prepare to wait for an event.
+            mWaitingForEventDelivery = true;
+            mUnprocessedEventAvailable = false;
+            if (mLastEvent != null) {
+                mLastEvent.recycle();
+                mLastEvent = null;
+            }
+            // Execute the command.
+            command.run();
+            // Wait for the event.
+            final long startTimeMillis = SystemClock.uptimeMillis();
+            while (true) {
+                // If the expected event is received, that's it.
+                if ((mUnprocessedEventAvailable && predicate.apply(mLastEvent))) {
+                    mWaitingForEventDelivery = false;
+                    mUnprocessedEventAvailable = false;
+                    mLock.notifyAll();
+                    return mLastEvent;
+                }
+                // Ask for another event.
+                mWaitingForEventDelivery = true;
+                mUnprocessedEventAvailable = false;
+                mLock.notifyAll();
+                // Check if timed out and if not wait.
+                final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+                final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
+                if (remainingTimeMillis <= 0) {
+                    mWaitingForEventDelivery = false;
+                    mUnprocessedEventAvailable = false;
+                    mLock.notifyAll();
+                    throw new TimeoutException("Expacted event not received within: "
+                            + timeoutMillis + " ms.");
+                }
+                try {
+                    mLock.wait(remainingTimeMillis);
+                } catch (InterruptedException ie) {
+                    /* ignore */
+                }
+            }
+        }
+    }
+
+    /**
+     * Finds an {@link AccessibilityNodeInfo} by accessibility id in the active
+     * window. The search is performed from the root node.
+     *
+     * @param accessibilityNodeId A unique view id or virtual descendant id for
+     *     which to search.
+     * @return The current window scale, where zero means a failure.
+     */
+    public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityIdInActiveWindow(
+            long accessibilityNodeId) {
+        return findAccessibilityNodeInfoByAccessibilityId(ACTIVE_WINDOW_ID, accessibilityNodeId);
+    }
+
+    /**
+     * Finds an {@link AccessibilityNodeInfo} by accessibility id.
+     *
+     * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID}
+     *     to query the currently active window.
+     * @param accessibilityNodeId A unique view id or virtual descendant id for
+     *     which to search.
+     * @return The current window scale, where zero means a failure.
+     */
+    public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(
+            int accessibilityWindowId, long accessibilityNodeId) {
+        // Cache the id to avoid locking
+        final int connectionId = mConnectionId;
+        ensureValidConnection(connectionId);
+        return AccessibilityInteractionClient.getInstance()
+                .findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
+                        accessibilityWindowId, accessibilityNodeId);
+    }
+
+    /**
+     * Finds an {@link AccessibilityNodeInfo} by View id in the active
+     * window. The search is performed from the root node.
+     *
+     * @return The current window scale, where zero means a failure.
+     */
+    public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId) {
+        return findAccessibilityNodeInfoByViewId(ACTIVE_WINDOW_ID, ROOT_NODE_ID, viewId);
+    }
+
+    /**
+     * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed in
+     * the window whose id is specified and starts from the node whose accessibility
+     * id is specified.
+     *
+     * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID}
+     *     to query the currently active window.
+     * @param accessibilityNodeId A unique view id or virtual descendant id from
+     *     where to start the search. Use {@link #ROOT_NODE_ID} to start from the root.
+     * @return The current window scale, where zero means a failure.
+     */
+    public AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int accessibilityWindowId,
+            long accessibilityNodeId, int viewId) {
+        // Cache the id to avoid locking
+        final int connectionId = mConnectionId;
+        ensureValidConnection(connectionId);
+        return AccessibilityInteractionClient.getInstance()
+                .findAccessibilityNodeInfoByViewId(connectionId, accessibilityWindowId,
+                        accessibilityNodeId, viewId);
+    }
+
+    /**
+     * Finds {@link AccessibilityNodeInfo}s by View text in the active
+     * window. The search is performed from the root node.
+     *
+     * @param text The searched text.
+     * @return The current window scale, where zero means a failure.
+     */
+    public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByTextInActiveWindow(String text) {
+        return findAccessibilityNodeInfosByText(ACTIVE_WINDOW_ID, ROOT_NODE_ID, text);
+    }
+
+    /**
+     * Finds {@link AccessibilityNodeInfo}s by View text. The match is case
+     * insensitive containment. The search is performed in the window whose
+     * id is specified and starts from the node whose accessibility id is
+     * specified.
+     *
+     * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID}
+     *     to query the currently active window.
+     * @param accessibilityNodeId A unique view id or virtual descendant id from
+     *     where to start the search. Use {@link #ROOT_NODE_ID} to start from the root.
+     * @param text The searched text.
+     * @return The current window scale, where zero means a failure.
+     */
+    public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(int accessibilityWindowId,
+            long accessibilityNodeId, String text) {
+        // Cache the id to avoid locking
+        final int connectionId = mConnectionId;
+        ensureValidConnection(connectionId);
+        return AccessibilityInteractionClient.getInstance()
+                .findAccessibilityNodeInfosByText(connectionId, accessibilityWindowId,
+                        accessibilityNodeId, text);
+    }
+
+    /**
+     * Performs an accessibility action on an {@link AccessibilityNodeInfo}
+     * in the active window.
+     *
+     * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id).
+     * @param action The action to perform.
+     * @return Whether the action was performed.
+     */
+    public boolean performAccessibilityActionInActiveWindow(long accessibilityNodeId, int action) {
+        return performAccessibilityAction(ACTIVE_WINDOW_ID, accessibilityNodeId, action);
+    }
+
+    /**
+     * Performs an accessibility action on an {@link AccessibilityNodeInfo}.
+     *
+     * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID}
+     *     to query the currently active window.
+     * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id).
+     * @param action The action to perform.
+     * @return Whether the action was performed.
+     */
+    public boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId,
+            int action) {
+        // Cache the id to avoid locking
+        final int connectionId = mConnectionId;
+        ensureValidConnection(connectionId);
+        return AccessibilityInteractionClient.getInstance().performAccessibilityAction(connectionId,
+                accessibilityWindowId, accessibilityNodeId, action);
+    }
+
+    private void ensureValidConnection(int connectionId) {
+        if (connectionId == AccessibilityInteractionClient.NO_ID) {
+            throw new IllegalStateException("UiAutomationService not connected."
+                    + " Did you call #register()?");
+        }
+    }
+}
diff --git a/core/java/android/view/AccessibilityNodeInfoCache.java b/core/java/android/view/AccessibilityNodeInfoCache.java
new file mode 100644
index 0000000..244a491
--- /dev/null
+++ b/core/java/android/view/AccessibilityNodeInfoCache.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.util.LongSparseArray;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+/**
+ * Simple cache for AccessibilityNodeInfos. The cache is mapping an
+ * accessibility id to an info. The cache allows storing of
+ * <code>null</code> values. It also tracks accessibility events
+ * and invalidates accordingly.
+ *
+ * @hide
+ */
+public class AccessibilityNodeInfoCache {
+
+    private final boolean ENABLED = true;
+
+    /**
+     * @return A new <strong>not synchronized</strong> AccessibilityNodeInfoCache.
+     */
+    public static AccessibilityNodeInfoCache newAccessibilityNodeInfoCache() {
+        return new AccessibilityNodeInfoCache();
+    }
+
+    /**
+     * @return A new <strong>synchronized</strong> AccessibilityNodeInfoCache.
+     */
+    public static AccessibilityNodeInfoCache newSynchronizedAccessibilityNodeInfoCache() {
+        return new AccessibilityNodeInfoCache() {
+            private final Object mLock = new Object();
+
+            @Override
+            public void clear() {
+                synchronized(mLock) {
+                    super.clear();
+                }
+            }
+
+            @Override
+            public AccessibilityNodeInfo get(long accessibilityNodeId) {
+                synchronized(mLock) {
+                    return super.get(accessibilityNodeId);
+                }
+            }
+
+            @Override
+            public void put(long accessibilityNodeId, AccessibilityNodeInfo info) {
+                synchronized(mLock) {
+                   super.put(accessibilityNodeId, info);
+                }
+            }
+
+            @Override
+            public void remove(long accessibilityNodeId) {
+                synchronized(mLock) {
+                   super.remove(accessibilityNodeId);
+                }
+            }
+        };
+    }
+
+    private final LongSparseArray<AccessibilityNodeInfo> mCacheImpl;
+
+    private AccessibilityNodeInfoCache() {
+        if (ENABLED) {
+            mCacheImpl = new LongSparseArray<AccessibilityNodeInfo>();
+        } else {
+            mCacheImpl = null;
+        }
+    }
+
+    /**
+     * The cache keeps track of {@link AccessibilityEvent}s and invalidates
+     * cached nodes as appropriate.
+     *
+     * @param event An event.
+     */
+    public void onAccessibilityEvent(AccessibilityEvent event) {
+        final int eventType = event.getEventType();
+        switch (eventType) {
+            case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
+            case AccessibilityEvent.TYPE_VIEW_SCROLLED:
+                clear();
+                break;
+            case AccessibilityEvent.TYPE_VIEW_FOCUSED:
+            case AccessibilityEvent.TYPE_VIEW_SELECTED:
+            case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
+            case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED:
+                final long accessibilityNodeId = event.getSourceNodeId();
+                remove(accessibilityNodeId);
+                break;
+        }
+    }
+
+    /**
+     * Gets a cached {@link AccessibilityNodeInfo} given its accessibility node id.
+     *
+     * @param accessibilityNodeId The info accessibility node id.
+     * @return The cached {@link AccessibilityNodeInfo} or null if such not found.
+     */
+    public AccessibilityNodeInfo get(long accessibilityNodeId) {
+        if (ENABLED) {
+            return mCacheImpl.get(accessibilityNodeId);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Caches an {@link AccessibilityNodeInfo} given its accessibility node id.
+     *
+     * @param accessibilityNodeId The info accessibility node id.
+     * @param info The {@link AccessibilityNodeInfo} to cache.
+     */
+    public void put(long accessibilityNodeId, AccessibilityNodeInfo info) {
+        if (ENABLED) {
+            mCacheImpl.put(accessibilityNodeId, info);
+        }
+    }
+
+    /**
+     * Returns whether the cache contains an accessibility node id key.
+     *
+     * @param accessibilityNodeId The key for which to check.
+     * @return True if the key is in the cache.
+     */
+    public boolean containsKey(long accessibilityNodeId) {
+        if (ENABLED) {
+            return (mCacheImpl.indexOfKey(accessibilityNodeId) >= 0);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Removes a cached {@link AccessibilityNodeInfo}.
+     *
+     * @param accessibilityNodeId The info accessibility node id.
+     */
+    public void remove(long accessibilityNodeId) {
+        if (ENABLED) {
+            mCacheImpl.remove(accessibilityNodeId);
+        }
+    }
+
+    /**
+     * Clears the cache.
+     */
+    public void clear() {
+        if (ENABLED) {
+            mCacheImpl.clear();
+        }
+    }
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 8597017..d80d080 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -12679,6 +12679,11 @@
             ViewDebug.trace(this, ViewDebug.HierarchyTraceType.REQUEST_LAYOUT);
         }
 
+        if (getAccessibilityNodeProvider() != null) {
+            throw new IllegalStateException("Views with AccessibilityNodeProvider"
+                    + " can't have children.");
+        }
+
         mPrivateFlags |= FORCE_LAYOUT;
         mPrivateFlags |= INVALIDATED;
 
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 7559862..d3af618 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3356,6 +3356,11 @@
     private void addViewInner(View child, int index, LayoutParams params,
             boolean preventRequestLayout) {
 
+        if (getAccessibilityNodeProvider() != null) {
+            throw new IllegalStateException("Views with AccessibilityNodeProvider"
+                    + " can't have children.");
+        }
+
         if (mTransition != null) {
             // Don't prevent other add transitions from completing, but cancel remove
             // transitions to let them complete the process before we add to the container
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2ef843b..3f61e6b 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -52,6 +52,7 @@
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Log;
+import android.util.LongSparseArray;
 import android.util.Pool;
 import android.util.Poolable;
 import android.util.PoolableManager;
@@ -302,6 +303,8 @@
 
     SendWindowContentChangedAccessibilityEvent mSendWindowContentChangedAccessibilityEvent;
 
+    AccessibilityPrefetchStrategy mAccessibilityPrefetchStrategy;
+
     private final int mDensity;
 
     /**
@@ -3483,6 +3486,17 @@
         return mAccessibilityInteractionController;
     }
 
+    public AccessibilityPrefetchStrategy getAccessibilityPrefetchStrategy() {
+        if (mView == null) {
+            throw new IllegalStateException("getAccessibilityPrefetchStrategy"
+                    + " called when there is no mView");
+        }
+        if (mAccessibilityPrefetchStrategy == null) {
+            mAccessibilityPrefetchStrategy = new AccessibilityPrefetchStrategy();
+        }
+        return mAccessibilityPrefetchStrategy;
+    }
+
     private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
             boolean insetsPending) throws RemoteException {
 
@@ -3983,6 +3997,7 @@
         if (mView == null) {
             return false;
         }
+        getAccessibilityPrefetchStrategy().onAccessibilityEvent(event);
         mAccessibilityManager.sendAccessibilityEvent(event);
         return true;
     }
@@ -4542,6 +4557,13 @@
                 viewRootImpl.getAccessibilityInteractionController()
                     .findAccessibilityNodeInfoByAccessibilityIdClientThread(accessibilityNodeId,
                         interactionId, callback, interrogatingPid, interrogatingTid);
+            } else {
+                // We cannot make the call and notify the caller so it does not wait.
+                try {
+                    callback.setFindAccessibilityNodeInfosResult(null, interactionId);
+                } catch (RemoteException re) {
+                    /* best effort - ignore */
+                }
             }
         }
 
@@ -4553,28 +4575,49 @@
                 viewRootImpl.getAccessibilityInteractionController()
                     .performAccessibilityActionClientThread(accessibilityNodeId, action,
                             interactionId, callback, interogatingPid, interrogatingTid);
+            } else {
+                // We cannot make the call and notify the caller so it does not
+                try {
+                    callback.setPerformAccessibilityActionResult(false, interactionId);
+                } catch (RemoteException re) {
+                    /* best effort - ignore */
+                }
             }
         }
 
-        public void findAccessibilityNodeInfoByViewId(int viewId,
+        public void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int viewId,
                 int interactionId, IAccessibilityInteractionConnectionCallback callback,
                 int interrogatingPid, long interrogatingTid) {
             ViewRootImpl viewRootImpl = mViewRootImpl.get();
             if (viewRootImpl != null && viewRootImpl.mView != null) {
                 viewRootImpl.getAccessibilityInteractionController()
-                    .findAccessibilityNodeInfoByViewIdClientThread(viewId, interactionId, callback,
-                            interrogatingPid, interrogatingTid);
-            }
-        }
-
-        public void findAccessibilityNodeInfosByText(String text, long accessibilityNodeId,
-                int interactionId, IAccessibilityInteractionConnectionCallback callback,
-                int interrogatingPid, long interrogatingTid) {
-            ViewRootImpl viewRootImpl = mViewRootImpl.get();
-            if (viewRootImpl != null && viewRootImpl.mView != null) {
-                viewRootImpl.getAccessibilityInteractionController()
-                    .findAccessibilityNodeInfosByTextClientThread(text, accessibilityNodeId,
+                    .findAccessibilityNodeInfoByViewIdClientThread(accessibilityNodeId, viewId,
                             interactionId, callback, interrogatingPid, interrogatingTid);
+            } else {
+                // We cannot make the call and notify the caller so it does not
+                try {
+                    callback.setFindAccessibilityNodeInfoResult(null, interactionId);
+                } catch (RemoteException re) {
+                    /* best effort - ignore */
+                }
+            }
+        }
+
+        public void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text,
+                int interactionId, IAccessibilityInteractionConnectionCallback callback,
+                int interrogatingPid, long interrogatingTid) {
+            ViewRootImpl viewRootImpl = mViewRootImpl.get();
+            if (viewRootImpl != null && viewRootImpl.mView != null) {
+                viewRootImpl.getAccessibilityInteractionController()
+                    .findAccessibilityNodeInfosByTextClientThread(accessibilityNodeId, text,
+                            interactionId, callback, interrogatingPid, interrogatingTid);
+            } else {
+                // We cannot make the call and notify the caller so it does not
+                try {
+                    callback.setFindAccessibilityNodeInfosResult(null, interactionId);
+                } catch (RemoteException re) {
+                    /* best effort - ignore */
+                }
             }
         }
     }
@@ -4652,6 +4695,7 @@
                 long interrogatingTid) {
             Message message = Message.obtain();
             message.what = DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID;
+            message.arg1 = interrogatingPid;
             SomeArgs args = mPool.acquire();
             args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
             args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
@@ -4674,40 +4718,47 @@
 
         public void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
             SomeArgs args = (SomeArgs) message.obj;
+            final int interrogatingPid = message.arg1;
             final int accessibilityViewId = args.argi1;
             final int virtualDescendantId = args.argi2;
             final int interactionId = args.argi3;
             final IAccessibilityInteractionConnectionCallback callback =
                 (IAccessibilityInteractionConnectionCallback) args.arg1;
             mPool.release(args);
-            AccessibilityNodeInfo info = null;
+            List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
+            infos.clear();
             try {
                 View target = findViewByAccessibilityId(accessibilityViewId);
                 if (target != null && target.getVisibility() == View.VISIBLE) {
                     AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
                     if (provider != null) {
-                        info = provider.createAccessibilityNodeInfo(virtualDescendantId);
+                        infos.add(provider.createAccessibilityNodeInfo(virtualDescendantId));
                     } else if (virtualDescendantId == View.NO_ID) {
-                        info = target.createAccessibilityNodeInfo();
+                        getAccessibilityPrefetchStrategy().prefetchAccessibilityNodeInfos(
+                                interrogatingPid, target, infos);
                     }
                 }
             } finally {
                 try {
-                    callback.setFindAccessibilityNodeInfoResult(info, interactionId);
+                    callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
+                    infos.clear();
                 } catch (RemoteException re) {
                     /* ignore - the other side will time out */
                 }
             }
         }
 
-        public void findAccessibilityNodeInfoByViewIdClientThread(int viewId, int interactionId,
-                IAccessibilityInteractionConnectionCallback callback, int interrogatingPid,
-                long interrogatingTid) {
+        public void findAccessibilityNodeInfoByViewIdClientThread(long accessibilityNodeId,
+                int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
+                int interrogatingPid, long interrogatingTid) {
             Message message = Message.obtain();
             message.what = DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID;
-            message.arg1 = viewId;
-            message.arg2 = interactionId;
-            message.obj = callback;
+            message.arg1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+            SomeArgs args = mPool.acquire();
+            args.argi1 = viewId;
+            args.argi2 = interactionId;
+            args.arg1 = callback;
+            message.obj = args;
             // If the interrogation is performed by the same thread as the main UI
             // thread in this process, set the message as a static reference so
             // after this call completes the same thread but in the interrogating
@@ -4723,17 +4774,26 @@
         }
 
         public void findAccessibilityNodeInfoByViewIdUiThread(Message message) {
-            final int viewId = message.arg1;
-            final int interactionId = message.arg2;
+            final int accessibilityViewId = message.arg1;
+            SomeArgs args = (SomeArgs) message.obj;
+            final int viewId = args.argi1;
+            final int interactionId = args.argi2;
             final IAccessibilityInteractionConnectionCallback callback =
-                (IAccessibilityInteractionConnectionCallback) message.obj;
-
+                (IAccessibilityInteractionConnectionCallback) args.arg1;
+            mPool.release(args);
             AccessibilityNodeInfo info = null;
             try {
-                View root = ViewRootImpl.this.mView;
-                View target = root.findViewById(viewId);
-                if (target != null && target.getVisibility() == View.VISIBLE) {
-                    info = target.createAccessibilityNodeInfo();
+                View root = null;
+                if (accessibilityViewId != View.NO_ID) {
+                    root = findViewByAccessibilityId(accessibilityViewId);
+                } else {
+                    root = ViewRootImpl.this.mView;
+                }
+                if (root != null) {
+                    View target = root.findViewById(viewId);
+                    if (target != null && target.getVisibility() == View.VISIBLE) {
+                        info = target.createAccessibilityNodeInfo();
+                    }
                 }
             } finally {
                 try {
@@ -4744,8 +4804,8 @@
             }
         }
 
-        public void findAccessibilityNodeInfosByTextClientThread(String text,
-                long accessibilityNodeId, int interactionId,
+        public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId,
+                String text, int interactionId,
                 IAccessibilityInteractionConnectionCallback callback, int interrogatingPid,
                 long interrogatingTid) {
             Message message = Message.obtain();
@@ -4937,4 +4997,88 @@
             }
         }
     }
+
+    /**
+     * This class encapsulates a prefetching strategy for the accessibility APIs for
+     * querying window content.It is responsible to prefetch a batch of
+     * AccessibilityNodeInfos in addition to the one for a requested node. It caches
+     * the ids of the prefeteched nodes such that they are fetched only once.
+     */
+    class AccessibilityPrefetchStrategy {
+        private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 100;
+
+        // We need to keep track of what we have sent for each interrogating
+        // process. Usually there will be only one such process but we
+        // should support the general case. Note that the accessibility event
+        // stream will take care of clearing caches of querying processes that
+        // are not longer alive, so we do not waste memory.
+        private final LongSparseArray<AccessibilityNodeInfoCache> mAccessibilityNodeInfoCaches =
+            new LongSparseArray<AccessibilityNodeInfoCache>();
+
+        private AccessibilityNodeInfoCache getCacheForInterrogatingPid(long interrogatingPid) {
+            AccessibilityNodeInfoCache cache = mAccessibilityNodeInfoCaches.get(interrogatingPid);
+            if (cache == null) {
+                cache = AccessibilityNodeInfoCache.newAccessibilityNodeInfoCache();
+                mAccessibilityNodeInfoCaches.put(interrogatingPid, cache);
+            }
+            return cache;
+        }
+
+        public void onAccessibilityEvent(AccessibilityEvent event) {
+            final int cacheCount = mAccessibilityNodeInfoCaches.size();
+            for (int i = 0; i < cacheCount; i++) {
+                AccessibilityNodeInfoCache cache = mAccessibilityNodeInfoCaches.valueAt(i);
+                cache.onAccessibilityEvent(event);
+            }
+        }
+
+        public void prefetchAccessibilityNodeInfos(long interrogatingPid, View root,
+                List<AccessibilityNodeInfo> outInfos) {
+            addAndCacheNotCachedNodeInfo(interrogatingPid, root, outInfos);
+            addAndCacheNotCachedPredecessorInfos(interrogatingPid, root, outInfos);
+            addAndCacheNotCachedDescendantInfos(interrogatingPid, root, outInfos);
+        }
+
+        private void addAndCacheNotCachedNodeInfo(long interrogatingPid,
+                View view, List<AccessibilityNodeInfo> outInfos) {
+            final long accessibilityNodeId = AccessibilityNodeInfo.makeNodeId(
+                    view.getAccessibilityViewId(), View.NO_ID);
+            AccessibilityNodeInfoCache cache = getCacheForInterrogatingPid(interrogatingPid);
+            if (!cache.containsKey(accessibilityNodeId)) {
+                // Account for the ids of the fetched infos. The infos will be
+                // cached in the window querying process. We just need to know
+                // which infos are cached to avoid fetching a cached one again.
+                cache.put(accessibilityNodeId, null);
+                outInfos.add(view.createAccessibilityNodeInfo());
+            }
+        }
+
+        private void addAndCacheNotCachedPredecessorInfos(long interrogatingPid, View view,
+                List<AccessibilityNodeInfo> outInfos) {
+            ViewParent predecessor = view.getParent();
+            while (predecessor instanceof View
+                    && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+                View predecessorView = (View) predecessor;
+                addAndCacheNotCachedNodeInfo(interrogatingPid, predecessorView, outInfos);
+                predecessor = predecessor.getParent();
+            }
+        }
+
+        private void addAndCacheNotCachedDescendantInfos(long interrogatingPid, View view,
+                List<AccessibilityNodeInfo> outInfos) {
+            if (outInfos.size() > MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE
+                    || view.getAccessibilityNodeProvider() != null) {
+                return;
+            }
+            addAndCacheNotCachedNodeInfo(interrogatingPid, view, outInfos);
+            if (view instanceof ViewGroup) {
+                ViewGroup rootGroup = (ViewGroup) view;
+                final int childCount = rootGroup.getChildCount();
+                for (int i = 0; i < childCount; i++) {
+                View child = rootGroup.getChildAt(i);
+                    addAndCacheNotCachedDescendantInfos(interrogatingPid, child, outInfos);
+                }
+            }
+        }
+    }
 }
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 95c070c..072fdd8 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -24,7 +24,9 @@
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.SparseArray;
+import android.view.AccessibilityNodeInfoCache;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -97,6 +99,11 @@
     private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache =
         new SparseArray<IAccessibilityServiceConnection>();
 
+    // The connection cache is shared between all interrogating threads since
+    // at any given time there is only one window allowing querying.
+    private static final AccessibilityNodeInfoCache sAccessibilityNodeInfoCache =
+        AccessibilityNodeInfoCache.newSynchronizedAccessibilityNodeInfoCache();
+
     /**
      * @return The client for the current thread.
      */
@@ -145,7 +152,9 @@
      * Finds an {@link AccessibilityNodeInfo} by accessibility id.
      *
      * @param connectionId The id of a connection for interacting with the system.
-     * @param accessibilityWindowId A unique window id.
+     * @param accessibilityWindowId A unique window id. Use
+     *     {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
+     *     to query the currently active window.
      * @param accessibilityNodeId A unique node accessibility id
      *     (accessibility view and virtual descendant id).
      * @return An {@link AccessibilityNodeInfo} if found, null otherwise.
@@ -155,16 +164,22 @@
         try {
             IAccessibilityServiceConnection connection = getConnection(connectionId);
             if (connection != null) {
+                AccessibilityNodeInfo cachedInfo = sAccessibilityNodeInfoCache.get(accessibilityNodeId); 
+                if (cachedInfo != null) {
+                    return cachedInfo;
+                }
                 final int interactionId = mInteractionIdCounter.getAndIncrement();
                 final float windowScale = connection.findAccessibilityNodeInfoByAccessibilityId(
                         accessibilityWindowId, accessibilityNodeId, interactionId, this,
                         Thread.currentThread().getId());
                 // If the scale is zero the call has failed.
                 if (windowScale > 0) {
-                    AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
+                    List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
                             interactionId);
-                    finalizeAccessibilityNodeInfo(info, connectionId, windowScale);
-                    return info;
+                    finalizeAccessibilityNodeInfos(infos, connectionId, windowScale);
+                    if (infos != null && !infos.isEmpty()) {
+                        return infos.get(0);
+                    }
                 }
             } else {
                 if (DEBUG) {
@@ -181,22 +196,30 @@
     }
 
     /**
-     * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed
-     * in the currently active window and starts from the root View in the window.
+     * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed in
+     * the window whose id is specified and starts from the node whose accessibility
+     * id is specified.
      *
      * @param connectionId The id of a connection for interacting with the system.
+     * @param accessibilityWindowId A unique window id. Use
+     *     {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
+     *     to query the currently active window.
+     * @param accessibilityNodeId A unique view id from where to start the search. Use
+     *     {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
+     *     to start from the root.
      * @param viewId The id of the view.
      * @return An {@link AccessibilityNodeInfo} if found, null otherwise.
      */
-    public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(int connectionId,
-            int viewId) {
+    public AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int connectionId,
+            int accessibilityWindowId, long accessibilityNodeId, int viewId) {
         try {
             IAccessibilityServiceConnection connection = getConnection(connectionId);
             if (connection != null) {
                 final int interactionId = mInteractionIdCounter.getAndIncrement();
                 final float windowScale =
-                    connection.findAccessibilityNodeInfoByViewIdInActiveWindow(viewId,
-                            interactionId, this, Thread.currentThread().getId());
+                    connection.findAccessibilityNodeInfoByViewId(accessibilityWindowId,
+                            accessibilityNodeId, viewId, interactionId, this,
+                            Thread.currentThread().getId());
                 // If the scale is zero the call has failed.
                 if (windowScale > 0) {
                     AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
@@ -220,64 +243,27 @@
 
     /**
      * Finds {@link AccessibilityNodeInfo}s by View text. The match is case
-     * insensitive containment. The search is performed in the currently
-     * active window and starts from the root View in the window.
-     *
-     * @param connectionId The id of a connection for interacting with the system.
-     * @param text The searched text.
-     * @return A list of found {@link AccessibilityNodeInfo}s.
-     */
-    public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByTextInActiveWindow(
-            int connectionId, String text) {
-        try {
-            IAccessibilityServiceConnection connection = getConnection(connectionId);
-            if (connection != null) {
-                final int interactionId = mInteractionIdCounter.getAndIncrement();
-                final float windowScale =
-                    connection.findAccessibilityNodeInfosByTextInActiveWindow(text,
-                            interactionId, this, Thread.currentThread().getId());
-                // If the scale is zero the call has failed.
-                if (windowScale > 0) {
-                    List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
-                            interactionId);
-                    finalizeAccessibilityNodeInfos(infos, connectionId, windowScale);
-                    return infos;
-                }
-            } else {
-                if (DEBUG) {
-                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
-                }
-            }
-        } catch (RemoteException re) {
-            if (DEBUG) {
-                Log.w(LOG_TAG, "Error while calling remote"
-                        + " findAccessibilityNodeInfosByViewTextInActiveWindow", re);
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Finds {@link AccessibilityNodeInfo}s by View text. The match is case
      * insensitive containment. The search is performed in the window whose
-     * id is specified and starts from the View whose accessibility id is
+     * id is specified and starts from the node whose accessibility id is
      * specified.
      *
      * @param connectionId The id of a connection for interacting with the system.
+     * @param accessibilityWindowId A unique window id. Use
+     *     {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
+     *     to query the currently active window.
+     * @param accessibilityNodeId A unique view id from where to start the search. Use
+     *     {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
      * @param text The searched text.
-     * @param accessibilityWindowId A unique window id.
-     * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id) from
-     *        where to start the search. Use {@link android.view.View#NO_ID} to start from the root.
      * @return A list of found {@link AccessibilityNodeInfo}s.
      */
     public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(int connectionId,
-            String text, int accessibilityWindowId, long accessibilityNodeId) {
+            int accessibilityWindowId, long accessibilityNodeId, String text) {
         try {
             IAccessibilityServiceConnection connection = getConnection(connectionId);
             if (connection != null) {
                 final int interactionId = mInteractionIdCounter.getAndIncrement();
-                final float windowScale = connection.findAccessibilityNodeInfosByText(text,
-                        accessibilityWindowId, accessibilityNodeId, interactionId, this,
+                final float windowScale = connection.findAccessibilityNodeInfosByText(
+                        accessibilityWindowId, accessibilityNodeId, text, interactionId, this,
                         Thread.currentThread().getId());
                 // If the scale is zero the call has failed.
                 if (windowScale > 0) {
@@ -304,7 +290,9 @@
      * Performs an accessibility action on an {@link AccessibilityNodeInfo}.
      *
      * @param connectionId The id of a connection for interacting with the system.
-     * @param accessibilityWindowId The id of the window.
+     * @param accessibilityWindowId A unique window id. Use
+     *     {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
+     *     to query the currently active window.
      * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id).
      * @param action The action to perform.
      * @return Whether the action was performed.
@@ -319,7 +307,7 @@
                         accessibilityWindowId, accessibilityNodeId, action, interactionId, this,
                         Thread.currentThread().getId());
                 if (success) {
-                    return getPerformAccessibilityActionResult(interactionId);
+                    return getPerformAccessibilityActionResultAndClear(interactionId);
                 }
             } else {
                 if (DEBUG) {
@@ -334,6 +322,24 @@
         return false;
     }
 
+    public void clearCache() {
+        if (DEBUG) {
+            Log.w(LOG_TAG, "clearCache()");
+        }
+        sAccessibilityNodeInfoCache.clear();
+    }
+
+    public void removeCachedNode(long accessibilityNodeId) {
+        if (DEBUG) {
+            Log.w(LOG_TAG, "removeCachedNode(" + accessibilityNodeId +")");
+        }
+        sAccessibilityNodeInfoCache.remove(accessibilityNodeId);
+    }
+
+    public void onAccessibilityEvent(AccessibilityEvent event) {
+        sAccessibilityNodeInfoCache.onAccessibilityEvent(event);
+    }
+
     /**
      * Gets the the result of an async request that returns an {@link AccessibilityNodeInfo}.
      *
@@ -358,6 +364,9 @@
             if (interactionId > mInteractionId) {
                 mFindAccessibilityNodeInfoResult = info;
                 mInteractionId = interactionId;
+                if (info != null) {
+                    sAccessibilityNodeInfoCache.put(info.getSourceNodeId(), info);
+                }
             }
             mInstanceLock.notifyAll();
         }
@@ -386,8 +395,20 @@
                 int interactionId) {
         synchronized (mInstanceLock) {
             if (interactionId > mInteractionId) {
-                mFindAccessibilityNodeInfosResult = infos;
+                // If the call is not an IPC, i.e. it is made from the same process, we need to
+                // instantiate new result list to avoid passing internal instances to clients.
+                final boolean isIpcCall = (queryLocalInterface(getInterfaceDescriptor()) == null);
+                if (!isIpcCall) {
+                    mFindAccessibilityNodeInfosResult = new ArrayList<AccessibilityNodeInfo>(infos);
+                } else {
+                    mFindAccessibilityNodeInfosResult = infos;
+                }
                 mInteractionId = interactionId;
+                final int infoCount = infos.size();
+                for (int i = 0; i < infoCount; i ++) {
+                    AccessibilityNodeInfo info = infos.get(i);
+                    sAccessibilityNodeInfoCache.put(info.getSourceNodeId(), info);
+                }
             }
             mInstanceLock.notifyAll();
         }
@@ -399,7 +420,7 @@
      * @param interactionId The interaction id to match the result with the request.
      * @return Whether the action was performed.
      */
-    private boolean getPerformAccessibilityActionResult(int interactionId) {
+    private boolean getPerformAccessibilityActionResultAndClear(int interactionId) {
         synchronized (mInstanceLock) {
             final boolean success = waitForResultTimedLocked(interactionId);
             final boolean result = success ? mPerformAccessibilityActionResult : false;
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 6939c2c..d7d6792 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -380,8 +380,8 @@
             return Collections.emptyList();
         }
         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
-        return client.findAccessibilityNodeInfosByText(mConnectionId, text, mWindowId,
-                mSourceNodeId);
+        return client.findAccessibilityNodeInfosByText(mConnectionId, mWindowId, mSourceNodeId,
+                text);
     }
 
     /**
@@ -903,6 +903,17 @@
     }
 
     /**
+     * Gets the id of the source node.
+     *
+     * @return The id.
+     *
+     * @hide
+     */
+    public long getSourceNodeId() {
+        return mSourceNodeId;
+    }
+
+    /**
      * Sets if this instance is sealed.
      *
      * @param sealed Whether is sealed.
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index 07aeb9a..b60f50e 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -564,6 +564,17 @@
     }
 
     /**
+     * Gets the id of the source node.
+     *
+     * @return The id.
+     *
+     * @hide
+     */
+    public long getSourceNodeId() {
+        return mSourceNodeId;
+    }
+
+    /**
      * Sets the unique id of the IAccessibilityServiceConnection over which
      * this instance can send requests to the system.
      *
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
index a90c427..ae6869c 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
@@ -31,13 +31,13 @@
         IAccessibilityInteractionConnectionCallback callback,
         int interrogatingPid, long interrogatingTid);
 
-    void findAccessibilityNodeInfoByViewId(int id, int interactionId,
+    void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int id, int interactionId,
         IAccessibilityInteractionConnectionCallback callback,
         int interrogatingPid, long interrogatingTid);
 
-    void findAccessibilityNodeInfosByText(String text, long accessibilityNodeId,
-        int interactionId, IAccessibilityInteractionConnectionCallback callback,
-        int interrogatingPid, long interrogatingTid);
+    void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text, int interactionId,
+        IAccessibilityInteractionConnectionCallback callback, int interrogatingPid,
+        long interrogatingTid);
 
     void performAccessibilityAction(long accessibilityNodeId, int action, int interactionId,
         IAccessibilityInteractionConnectionCallback callback, int interrogatingPid,
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index c3794be..320c75d 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -49,5 +49,7 @@
 
     void removeAccessibilityInteractionConnection(IWindow windowToken);
 
-    void registerEventListener(IEventListener client);
+    void registerUiTestAutomationService(IEventListener listener, in AccessibilityServiceInfo info);
+
+    void unregisterUiTestAutomationService(IEventListener listener);
 }
diff --git a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivity.java b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivity.java
index b4a0581..a9f144b 100644
--- a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivity.java
+++ b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivity.java
@@ -14,12 +14,12 @@
 
 package android.accessibilityservice;
 
-import com.android.frameworks.coretests.R;
-
 import android.app.Activity;
 import android.os.Bundle;
 import android.view.View;
 
+import com.android.frameworks.coretests.R;
+
 /**
  * Activity for testing the accessibility APIs for "interrogation" of
  * the screen content. These APIs allow exploring the screen and
diff --git a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java
index 259a094..fa48093 100644
--- a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java
+++ b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java
@@ -14,26 +14,21 @@
 
 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_CLEAR_FOCUS;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SELECT;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_SELECTION;
 
-import android.content.Context;
 import android.graphics.Rect;
-import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.test.ActivityInstrumentationTestCase2;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
-import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityInteractionClient;
-import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.IAccessibilityManager;
 
 import com.android.frameworks.coretests.R;
+import com.android.internal.util.Predicate;
 
 import java.util.ArrayList;
 import java.util.LinkedList;
@@ -48,21 +43,15 @@
  */
 public class InterrogationActivityTest
         extends ActivityInstrumentationTestCase2<InterrogationActivity> {
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = false;
 
     private static String LOG_TAG = "InterrogationActivityTest";
 
-    // Timeout before give up wait for the system to process an accessibility setting change.       
-    private static final int TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING = 2000;
-
     // Timeout for the accessibility state of an Activity to be fully initialized.
-    private static final int TIMEOUT_ACCESSIBLITY_STATE_INITIALIZED_MILLIS = 100;
+    private static final int TIMEOUT_PROPAGATE_ACCESSIBILITY_EVENT_MILLIS = 5000;
 
     // Handle to a connection to the AccessibilityManagerService
-    private static int sConnectionId = View.NO_ID;
-
-    // The last received accessibility event
-    private volatile AccessibilityEvent mLastAccessibilityEvent;
+    private UiTestAutomationBridge mUiTestAutomationBridge;
 
     public InterrogationActivityTest() {
         super(InterrogationActivity.class);
@@ -70,16 +59,39 @@
 
     @Override
     public void setUp() throws Exception {
-        ensureConnection();
-        bringUpActivityWithInitalizedAccessbility();
+        super.setUp();
+        mUiTestAutomationBridge = new UiTestAutomationBridge();
+        mUiTestAutomationBridge.connect();
+        mUiTestAutomationBridge.executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+                // wait for the first accessibility event
+                @Override
+                public void run() {
+                    // bring up the activity
+                    getActivity();
+                }
+            },
+            new Predicate<AccessibilityEvent>() {
+                @Override
+                public boolean apply(AccessibilityEvent event) {
+                    return (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+                            && event.getPackageName().equals(getActivity().getPackageName()));
+                }
+            },
+            TIMEOUT_PROPAGATE_ACCESSIBILITY_EVENT_MILLIS);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mUiTestAutomationBridge.disconnect();
+        super.tearDown();
     }
 
     @LargeTest
     public void testFindAccessibilityNodeInfoByViewId() throws Exception {
         final long startTimeMillis = SystemClock.uptimeMillis();
         try {
-            AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
+            AccessibilityNodeInfo button = mUiTestAutomationBridge
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
             assertNotNull(button);
             assertEquals(0, button.getChildCount());
 
@@ -125,8 +137,8 @@
         final long startTimeMillis = SystemClock.uptimeMillis();
         try {
             // find a view by text
-            List<AccessibilityNodeInfo> buttons =  AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfosByTextInActiveWindow(sConnectionId, "butto");
+            List<AccessibilityNodeInfo> buttons = mUiTestAutomationBridge
+                .findAccessibilityNodeInfosByTextInActiveWindow("butto");
             assertEquals(9, buttons.size());
         } finally {
             if (DEBUG) {
@@ -141,12 +153,9 @@
     public void testFindAccessibilityNodeInfoByViewTextContentDescription() throws Exception {
         final long startTimeMillis = SystemClock.uptimeMillis();
         try {
-            bringUpActivityWithInitalizedAccessbility();
-
             // find a view by text
-            List<AccessibilityNodeInfo> buttons =  AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfosByTextInActiveWindow(sConnectionId,
-                        "contentDescription");
+            List<AccessibilityNodeInfo> buttons = mUiTestAutomationBridge
+                .findAccessibilityNodeInfosByTextInActiveWindow("contentDescription");
             assertEquals(1, buttons.size());
         } finally {
             if (DEBUG) {
@@ -177,8 +186,8 @@
             classNameAndTextList.add("android.widget.ButtonButton8");
             classNameAndTextList.add("android.widget.ButtonButton9");
 
-            AccessibilityNodeInfo root = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.root);
+            AccessibilityNodeInfo root = mUiTestAutomationBridge
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.root);
             assertNotNull("We must find the existing root.", root);
 
             Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
@@ -216,16 +225,16 @@
         final long startTimeMillis = SystemClock.uptimeMillis();
         try {
             // find a view and make sure it is not focused
-            AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
+            AccessibilityNodeInfo button = mUiTestAutomationBridge
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(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 = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
+            button = mUiTestAutomationBridge
+                    .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
             assertTrue(button.isFocused());
         } finally {
             if (DEBUG) {
@@ -240,24 +249,24 @@
         final long startTimeMillis = SystemClock.uptimeMillis();
         try {
             // find a view and make sure it is not focused
-            AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
+            AccessibilityNodeInfo button = mUiTestAutomationBridge
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(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 = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
+            button = mUiTestAutomationBridge
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(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 = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
+            button = mUiTestAutomationBridge
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
             assertFalse(button.isFocused());
         } finally {
             if (DEBUG) {
@@ -273,16 +282,16 @@
         final long startTimeMillis = SystemClock.uptimeMillis();
         try {
             // find a view and make sure it is not selected
-            AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
+            AccessibilityNodeInfo button = mUiTestAutomationBridge
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(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 = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
+            button = mUiTestAutomationBridge
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
             assertTrue(button.isSelected());
         } finally {
             if (DEBUG) {
@@ -297,24 +306,24 @@
         final long startTimeMillis = SystemClock.uptimeMillis();
         try {
             // find a view and make sure it is not selected
-            AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
+            AccessibilityNodeInfo button = mUiTestAutomationBridge
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(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 = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
+            button = mUiTestAutomationBridge
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(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 = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
+            button = mUiTestAutomationBridge
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
             assertFalse(button.isSelected());
         } finally {
             if (DEBUG) {
@@ -330,23 +339,33 @@
         final long startTimeMillis = SystemClock.uptimeMillis();
         try {
             // find a view and make sure it is not focused
-            AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
-            assertFalse(button.isSelected());
+            final AccessibilityNodeInfo button = mUiTestAutomationBridge
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
+            assertFalse(button.isFocused());
 
-            // focus the view
-            assertTrue(button.performAction(ACTION_FOCUS));
-
-            synchronized (this) {
-                try {
-                    wait(TIMEOUT_ACCESSIBLITY_STATE_INITIALIZED_MILLIS);
-                } catch (InterruptedException ie) {
-                    /* ignore */
+            AccessibilityEvent event = mUiTestAutomationBridge
+                    .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+                @Override
+                public void run() {
+                    // focus the view
+                    assertTrue(button.performAction(ACTION_FOCUS));
                 }
-            }
+            },
+            new Predicate<AccessibilityEvent>() {
+                @Override
+                public boolean apply(AccessibilityEvent event) {
+                    return (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED
+                            && event.getPackageName().equals(getActivity().getPackageName())
+                            && event.getText().get(0).equals(button.getText()));
+                }
+            },
+            TIMEOUT_PROPAGATE_ACCESSIBILITY_EVENT_MILLIS);
+
+            // check the last event
+            assertNotNull(event);
 
             // check that last event source
-            AccessibilityNodeInfo source = mLastAccessibilityEvent.getSource();
+            AccessibilityNodeInfo source = event.getSource();
             assertNotNull(source);
 
             // bounds
@@ -389,8 +408,9 @@
         final long startTimeMillis = SystemClock.uptimeMillis();
         try {
             // find a view and make sure it is not focused
-            AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
-                .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
+            AccessibilityNodeInfo button = mUiTestAutomationBridge
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
+            assertNotNull(button);
             AccessibilityNodeInfo parent = button.getParent();
             final int childCount = parent.getChildCount();
             for (int i = 0; i < childCount; i++) {
@@ -410,71 +430,4 @@
             }
         }
     }
-
-    private void bringUpActivityWithInitalizedAccessbility() {
-        mLastAccessibilityEvent = null;
-        // bring up the activity
-        getActivity();
-
-        final long startTimeMillis = SystemClock.uptimeMillis();
-        while (true) {
-            if (mLastAccessibilityEvent != null) {
-                final int eventType = mLastAccessibilityEvent.getEventType();
-                if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
-                    return;
-                }
-            }
-            final long remainingTimeMillis = TIMEOUT_ACCESSIBLITY_STATE_INITIALIZED_MILLIS
-                    - (SystemClock.uptimeMillis() - startTimeMillis);
-            if (remainingTimeMillis <= 0) {
-                return;
-            }
-            synchronized (this) {
-                try {
-                    wait(remainingTimeMillis);
-                } catch (InterruptedException e) {
-                    /* ignore */
-                }
-            }
-        }
-    }
-
-    private void ensureConnection() throws Exception {
-        if (sConnectionId == View.NO_ID) {
-            IEventListener listener = new IEventListener.Stub() {
-                public void setConnection(IAccessibilityServiceConnection connection,
-                        int connectionId) {
-                    sConnectionId = connectionId;
-                    if (connection != null) {
-                        AccessibilityInteractionClient.getInstance().addConnection(connectionId,
-                                connection);
-                    } else {
-                        AccessibilityInteractionClient.getInstance().removeConnection(connectionId);
-                    }
-                    synchronized (this) {
-                        notifyAll();
-                    }
-                }
-
-                public void onInterrupt() {}
-
-                public void onAccessibilityEvent(AccessibilityEvent event) {
-                    mLastAccessibilityEvent = AccessibilityEvent.obtain(event);
-                    synchronized (this) {
-                        notifyAll();
-                    }
-                }
-            };
-
-            AccessibilityManager accessibilityManager =
-                AccessibilityManager.getInstance(getInstrumentation().getContext());
-
-            synchronized (this) {
-                IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
-                        ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
-                manager.registerEventListener(listener);
-                wait(TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING);
-            }
-        }
-    }
 }
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index 23fa94a..8bda755 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -46,7 +46,6 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.IWindow;
-import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -96,6 +95,10 @@
 
     private static final int DO_SET_SERVICE_INFO = 10;
 
+    public static final int ACTIVE_WINDOW_ID = -1;
+
+    public static final long ROOT_NODE_ID = -1;
+
     private static int sNextWindowId;
 
     final HandlerCaller mCaller;
@@ -467,7 +470,8 @@
         }
     }
 
-    public void registerEventListener(IEventListener listener) {
+    public void registerUiTestAutomationService(IEventListener listener,
+            AccessibilityServiceInfo accessibilityServiceInfo) {
         mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT,
                 FUNCTION_REGISTER_EVENT_LISTENER);
         ComponentName componentName = new ComponentName("foo.bar",
@@ -490,13 +494,23 @@
             }
         }
         // Hook the automation service up.
-        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());
     }
 
+    public void unregisterUiTestAutomationService(IEventListener listener) {
+        synchronized (mLock) {
+            final int serviceCount = mServices.size();
+            for (int i = 0; i < serviceCount; i++) {
+                Service service = mServices.get(i);
+                if (service.mServiceInterface == listener && service.mIsAutomation) {
+                    // Automation service is not bound, so pretend it died to perform clean up.
+                    service.binderDied();
+                }
+            }
+        }
+    }
+
     /**
      * Removes an AccessibilityInteractionConnection.
      *
@@ -1070,10 +1084,11 @@
             }
         }
 
-        public float findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId,
-                int interactionId, IAccessibilityInteractionConnectionCallback callback,
-                long interrogatingTid)
+        public float findAccessibilityNodeInfoByViewId(int accessibilityWindowId,
+                long accessibilityNodeId, int viewId, int interactionId,
+                IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
                 throws RemoteException {
+            final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId);
             IAccessibilityInteractionConnection connection = null;
             synchronized (mLock) {
                 mSecurityPolicy.enforceCanRetrieveWindowContent(this);
@@ -1081,12 +1096,8 @@
                 if (!permissionGranted) {
                     return 0;
                 } else {
-                    connection = getConnectionToRetrievalAllowingWindowLocked();
+                    connection = getConnectionLocked(resolvedWindowId);
                     if (connection == null) {
-                        if (DEBUG) {
-                            Slog.e(LOG_TAG, "No interaction connection to a retrieve "
-                                    + "allowing window.");
-                        }
                         return 0;
                     }
                 }
@@ -1094,44 +1105,33 @@
             final int interrogatingPid = Binder.getCallingPid();
             final long identityToken = Binder.clearCallingIdentity();
             try {
-                connection.findAccessibilityNodeInfoByViewId(viewId, interactionId, callback,
-                        interrogatingPid, interrogatingTid);
+                connection.findAccessibilityNodeInfoByViewId(accessibilityNodeId, viewId,
+                        interactionId, callback, interrogatingPid, interrogatingTid);
             } catch (RemoteException re) {
                 if (DEBUG) {
-                    Slog.e(LOG_TAG, "Error finding node.");
+                    Slog.e(LOG_TAG, "Error findAccessibilityNodeInfoByViewId().");
                 }
             } finally {
                 Binder.restoreCallingIdentity(identityToken);
             }
-            return getCompatibilityScale(mSecurityPolicy.getRetrievalAllowingWindowLocked());
+            return getCompatibilityScale(resolvedWindowId);
         }
 
-        public float findAccessibilityNodeInfosByTextInActiveWindow(
-                String text, int interactionId,
-                IAccessibilityInteractionConnectionCallback callback, long threadId)
-                throws RemoteException {
-            return findAccessibilityNodeInfosByText(text,
-                    mSecurityPolicy.mRetrievalAlowingWindowId, View.NO_ID, interactionId, callback,
-                    threadId);
-        }
-
-        public float findAccessibilityNodeInfosByText(String text,
-                int accessibilityWindowId, long accessibilityNodeId, int interactionId,
+        public float findAccessibilityNodeInfosByText(int accessibilityWindowId,
+                long accessibilityNodeId, String text, int interactionId,
                 IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
                 throws RemoteException {
+            final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId);
             IAccessibilityInteractionConnection connection = null;
             synchronized (mLock) {
                 mSecurityPolicy.enforceCanRetrieveWindowContent(this);
                 final boolean permissionGranted =
-                    mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, accessibilityWindowId);
+                    mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
                 if (!permissionGranted) {
                     return 0;
                 } else {
-                    connection = getConnectionToRetrievalAllowingWindowLocked();
+                    connection = getConnectionLocked(resolvedWindowId);
                     if (connection == null) {
-                        if (DEBUG) {
-                            Slog.e(LOG_TAG, "No interaction connection to focused window.");
-                        }
                         return 0;
                     }
                 }
@@ -1139,40 +1139,35 @@
             final int interrogatingPid = Binder.getCallingPid();
             final long identityToken = Binder.clearCallingIdentity();
             try {
-                connection.findAccessibilityNodeInfosByText(text, accessibilityNodeId,
+                connection.findAccessibilityNodeInfosByText(accessibilityNodeId, text,
                         interactionId, callback, interrogatingPid, interrogatingTid);
             } catch (RemoteException re) {
                 if (DEBUG) {
-                    Slog.e(LOG_TAG, "Error finding node.");
+                    Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfosByText()");
                 }
             } finally {
                 Binder.restoreCallingIdentity(identityToken);
             }
-            return getCompatibilityScale(accessibilityWindowId);
+            return getCompatibilityScale(resolvedWindowId);
         }
 
         public float findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
                 long accessibilityNodeId, int interactionId,
                 IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
                 throws RemoteException {
+            final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId);
             IAccessibilityInteractionConnection connection = null;
             synchronized (mLock) {
                 mSecurityPolicy.enforceCanRetrieveWindowContent(this);
                 final boolean permissionGranted =
-                    mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, accessibilityWindowId);
+                    mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
                 if (!permissionGranted) {
                     return 0;
                 } else {
-                    AccessibilityConnectionWrapper wrapper =
-                        mWindowIdToInteractionConnectionWrapperMap.get(accessibilityWindowId);
-                    if (wrapper == null) {
-                        if (DEBUG) {
-                            Slog.e(LOG_TAG, "No interaction connection to window: "
-                                    + accessibilityWindowId);
-                        }
+                    connection = getConnectionLocked(resolvedWindowId);
+                    if (connection == null) {
                         return 0;
                     }
-                    connection = wrapper.mConnection;
                 }
             }
             final int interrogatingPid = Binder.getCallingPid();
@@ -1182,35 +1177,29 @@
                         interactionId, callback, interrogatingPid, interrogatingTid);
             } catch (RemoteException re) {
                 if (DEBUG) {
-                    Slog.e(LOG_TAG, "Error requesting node with accessibilityNodeId: "
-                            + accessibilityNodeId);
+                    Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()");
                 }
             } finally {
                 Binder.restoreCallingIdentity(identityToken);
             }
-            return getCompatibilityScale(accessibilityWindowId);
+            return getCompatibilityScale(resolvedWindowId);
         }
 
         public boolean performAccessibilityAction(int accessibilityWindowId,
                 long accessibilityNodeId, int action, int interactionId,
                 IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) {
+            final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId);
             IAccessibilityInteractionConnection connection = null;
             synchronized (mLock) {
                 final boolean permissionGranted = mSecurityPolicy.canPerformActionLocked(this,
-                        accessibilityWindowId, action);
+                        resolvedWindowId, action);
                 if (!permissionGranted) {
                     return false;
                 } else {
-                    AccessibilityConnectionWrapper wrapper =
-                        mWindowIdToInteractionConnectionWrapperMap.get(accessibilityWindowId);
-                    if (wrapper == null) {
-                        if (DEBUG) {
-                            Slog.e(LOG_TAG, "No interaction connection to window: "
-                                    + accessibilityWindowId);
-                        }
+                    connection = getConnectionLocked(resolvedWindowId);
+                    if (connection == null) {
                         return false;
                     }
-                    connection = wrapper.mConnection;
                 }
             }
             final int interrogatingPid = Binder.getCallingPid();
@@ -1220,8 +1209,7 @@
                         callback, interrogatingPid, interrogatingTid);
             } catch (RemoteException re) {
                 if (DEBUG) {
-                    Slog.e(LOG_TAG, "Error requesting node with accessibilityNodeId: "
-                            + accessibilityNodeId);
+                    Slog.e(LOG_TAG, "Error calling performAccessibilityAction()");
                 }
             } finally {
                 Binder.restoreCallingIdentity(identityToken);
@@ -1265,14 +1253,26 @@
             }
         }
 
-        private IAccessibilityInteractionConnection getConnectionToRetrievalAllowingWindowLocked() {
-            final int windowId = mSecurityPolicy.getRetrievalAllowingWindowLocked();
+        private IAccessibilityInteractionConnection getConnectionLocked(int windowId) {
             if (DEBUG) {
                 Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId);
             }
-            AccessibilityConnectionWrapper wrapper =
-                mWindowIdToInteractionConnectionWrapperMap.get(windowId);
-            return (wrapper != null) ? wrapper.mConnection : null;
+            AccessibilityConnectionWrapper wrapper = mWindowIdToInteractionConnectionWrapperMap.get(
+                    windowId);
+            if (wrapper != null && wrapper.mConnection != null) {
+                return wrapper.mConnection;
+            }
+            if (DEBUG) {
+                Slog.e(LOG_TAG, "No interaction connection to window: " + windowId);
+            }
+            return null;
+        }
+
+        private int resolveAccessibilityWindowId(int accessibilityWindowId) {
+            if (accessibilityWindowId == ACTIVE_WINDOW_ID) {
+                return mSecurityPolicy.mRetrievalAlowingWindowId;
+            }
+            return accessibilityWindowId;
         }
 
         private float getCompatibilityScale(int windowId) {