OSDN Git Service

Intra-process view hierarchy interrogation does not work.
authorSvetoslav Ganov <svetoslavganov@google.com>
Tue, 23 Aug 2011 20:40:30 +0000 (13:40 -0700)
committerSvetoslav Ganov <svetoslavganov@google.com>
Wed, 24 Aug 2011 23:26:43 +0000 (16:26 -0700)
The content retrieval APIs are synchronous from a client's
perspective but internally they are asynchronous. The client thread
calls into the system requesting an action and providing a callback
to receive the result after which it waits up to a timeout for that
result. The system enforces security and then delegates the request
to a given view hierarchy where a message is posted (from a binder
thread) describing what to be performed by the main UI thread the
result of which it delivered via the mentioned callback. However,
the blocked client thread and the main UI thread of the target view
hierarchy can be the same one, for example an accessibility service
and an activity run in the same process, thus they are executed on the
same main thread. In such a case the retrieval will fail since the UI
thread that has to process the message describing the work to be done
is blocked waiting for a result is has to compute! To avoid this scenario
when making a call the client also passes its process and thread ids so
the accessed view hierarchy can detect if the client making the request
is running in its main UI thread. In such a case the view hierarchy,
specifically the binder thread performing the IPC to it, does not post a
message to be run on the UI thread but passes it to the singleton
interaction client through which all interactions occur and the latter is
responsible to execute the message before starting to wait for the
asynchronous result delivered via the callback. In this case the expected
result is already received so no waiting is performed.

bug:5138933

Change-Id: I382e2d8689f5189110226613c2387f553df98bd3

core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
core/java/android/view/ViewRootImpl.java
core/java/android/view/accessibility/AccessibilityInteractionClient.java [new file with mode: 0644]
core/java/android/view/accessibility/AccessibilityNodeInfo.java
core/java/android/view/accessibility/AccessibilityRecord.java
core/java/android/view/accessibility/IAccessibilityInteractionCallback.aidl [new file with mode: 0644]
core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
core/tests/coretests/res/layout/interrogation_activity.xml
core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java
services/java/com/android/server/accessibility/AccessibilityManagerService.java

index 8b4e7ae..7c41082 100644 (file)
@@ -18,6 +18,7 @@ package android.accessibilityservice;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
 
 /**
  * Interface given to an AccessibilitySerivce to talk to the AccessibilityManagerService.
@@ -30,84 +31,79 @@ interface IAccessibilityServiceConnection {
 
     /**
      * Finds an {@link AccessibilityNodeInfo} by accessibility id.
-     * <p>
-     *   <strong>
-     *     It is a client responsibility to recycle the received info by
-     *     calling {@link AccessibilityNodeInfo#recycle()} to avoid creating
-     *     of multiple instances.
-     *   </strong>
-     * </p>
      *
      * @param accessibilityWindowId A unique window id.
      * @param accessibilityViewId A unique View accessibility id.
-     * @return The node info.
+     * @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.
      */
-    AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
-        int accessibilityViewId);
+    float findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
+        int accessibilityViewId, int interactionId,
+        IAccessibilityInteractionConnectionCallback callback, long threadId);
 
     /**
      * 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
      * specified.
-     * <p>
-     *   <strong>
-     *     It is a client responsibility to recycle the received infos by
-     *     calling {@link AccessibilityNodeInfo#recycle()} to avoid creating
-     *     of multiple instances.
-     *   </strong>
-     * </p>
      *
      * @param text The searched text.
-     * @param accessibilityId The id of the view from which to start searching.
+     * @param accessibilityWindowId A unique window id.
+     * @param accessibilityViewId A unique View accessibility id from where to start the search.
      *        Use {@link android.view.View#NO_ID} to start from the root.
-     * @return A list of node info.
+     * @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.
      */
-    List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewText(String text,
-        int accessibilityWindowId, int accessibilityViewId);
+    float findAccessibilityNodeInfosByViewText(String text, int accessibilityWindowId,
+        int accessibilityViewId, 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.
-     * <p>
-     *   <strong>
-     *     It is a client responsibility to recycle the received infos by
-     *     calling {@link AccessibilityNodeInfo#recycle()} to avoid creating
-     *     of multiple instances.
-     *   </strong>
-     * </p>
      *
      * @param text The searched text.
      * @param accessibilityId The id of the view from which to start searching.
      *        Use {@link android.view.View#NO_ID} to start from the root.
-     * @return A list of node info.
+     * @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.
      */
-    List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewTextInActiveWindow(String text);
+    float findAccessibilityNodeInfosByViewTextInActiveWindow(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 start from the root View in the window.
-     * <p>
-     *   <strong>
-     *     It is a client responsibility to recycle the received info by
-     *     calling {@link AccessibilityNodeInfo#recycle()} to avoid creating
-     *     of multiple instances.
-     *   </strong>
-     * </p>
+     * in the currently active window and starts from the root View in the window.
      *
      * @param id The id of the node.
-     * @return The node info.
+     * @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.
      */
-    AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId);
+    float findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId, int interactionId,
+        IAccessibilityInteractionConnectionCallback callback, long threadId);
 
     /**
      * Performs an accessibility action on an {@link AccessibilityNodeInfo}.
      *
      * @param accessibilityWindowId The id of the window.
-     * @param accessibilityViewId The of a view in the .
+     * @param accessibilityViewId A unique View accessibility id.
+     * @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.
+     * @param threadId The id of the calling thread.
      * @return Whether the action was performed.
      */
     boolean performAccessibilityAction(int accessibilityWindowId, int accessibilityViewId,
-        int action);
+        int action, int interactionId, IAccessibilityInteractionConnectionCallback callback,
+        long threadId);
 }
index f23c366..845f96c 100644 (file)
@@ -61,6 +61,7 @@ import android.util.SparseArray;
 import android.util.TypedValue;
 import android.view.View.MeasureSpec;
 import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityInteractionClient;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -4359,37 +4360,42 @@ public final class ViewRootImpl extends Handler implements ViewParent,
         }
 
         public void findAccessibilityNodeInfoByAccessibilityId(int accessibilityId,
-                int interactionId, IAccessibilityInteractionConnectionCallback callback) {
+                int interactionId, IAccessibilityInteractionConnectionCallback callback,
+                int interrogatingPid, long interrogatingTid) {
             if (mViewAncestor.get() != null) {
                 getAccessibilityInteractionController()
                     .findAccessibilityNodeInfoByAccessibilityIdClientThread(accessibilityId,
-                        interactionId, callback);
+                        interactionId, callback, interrogatingPid, interrogatingTid);
             }
         }
 
         public void performAccessibilityAction(int accessibilityId, int action,
-                int interactionId, IAccessibilityInteractionConnectionCallback callback) {
+                int interactionId, IAccessibilityInteractionConnectionCallback callback,
+                int interogatingPid, long interrogatingTid) {
             if (mViewAncestor.get() != null) {
                 getAccessibilityInteractionController()
                     .performAccessibilityActionClientThread(accessibilityId, action, interactionId,
-                            callback);
+                            callback, interogatingPid, interrogatingTid);
             }
         }
 
         public void findAccessibilityNodeInfoByViewId(int viewId,
-                int interactionId, IAccessibilityInteractionConnectionCallback callback) {
+                int interactionId, IAccessibilityInteractionConnectionCallback callback,
+                int interrogatingPid, long interrogatingTid) {
             if (mViewAncestor.get() != null) {
                 getAccessibilityInteractionController()
-                    .findAccessibilityNodeInfoByViewIdClientThread(viewId, interactionId, callback);
+                    .findAccessibilityNodeInfoByViewIdClientThread(viewId, interactionId, callback,
+                            interrogatingPid, interrogatingTid);
             }
         }
 
         public void findAccessibilityNodeInfosByViewText(String text, int accessibilityId,
-                int interactionId, IAccessibilityInteractionConnectionCallback callback) {
+                int interactionId, IAccessibilityInteractionConnectionCallback callback,
+                int interrogatingPid, long interrogatingTid) {
             if (mViewAncestor.get() != null) {
                 getAccessibilityInteractionController()
                     .findAccessibilityNodeInfosByViewTextClientThread(text, accessibilityId,
-                            interactionId, callback);
+                            interactionId, callback, interrogatingPid, interrogatingTid);
             }
         }
     }
@@ -4465,13 +4471,24 @@ public final class ViewRootImpl extends Handler implements ViewParent,
         }
 
         public void findAccessibilityNodeInfoByAccessibilityIdClientThread(int accessibilityId,
-                int interactionId, IAccessibilityInteractionConnectionCallback callback) {
+                int interactionId, IAccessibilityInteractionConnectionCallback callback,
+                int interrogatingPid, long interrogatingTid) {
             Message message = Message.obtain();
             message.what = DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID;
             message.arg1 = accessibilityId;
             message.arg2 = interactionId;
             message.obj = callback;
-            sendMessage(message);
+            // If the interrogation is performed by the same thread as the main UI
+            // thread in this process, set the message as a static reference so
+            // after this call completes the same thread but in the interrogating
+            // client can handle the message to generate the result.
+            if (interrogatingPid == Process.myPid()
+                    && interrogatingTid == Looper.getMainLooper().getThread().getId()) {
+                message.setTarget(ViewRootImpl.this);
+                AccessibilityInteractionClient.getInstance().setSameThreadMessage(message);
+            } else {
+                sendMessage(message);
+            }
         }
 
         public void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
@@ -4499,13 +4516,24 @@ public final class ViewRootImpl extends Handler implements ViewParent,
         }
 
         public void findAccessibilityNodeInfoByViewIdClientThread(int viewId, int interactionId,
-                IAccessibilityInteractionConnectionCallback callback) {
+                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;
-            sendMessage(message);
+            // If the interrogation is performed by the same thread as the main UI
+            // thread in this process, set the message as a static reference so
+            // after this call completes the same thread but in the interrogating
+            // client can handle the message to generate the result.
+            if (interrogatingPid == Process.myPid()
+                    && interrogatingTid == Looper.getMainLooper().getThread().getId()) {
+                message.setTarget(ViewRootImpl.this);
+                AccessibilityInteractionClient.getInstance().setSameThreadMessage(message);
+            } else {
+                sendMessage(message);
+            }
         }
 
         public void findAccessibilityNodeInfoByViewIdUiThread(Message message) {
@@ -4532,7 +4560,8 @@ public final class ViewRootImpl extends Handler implements ViewParent,
 
         public void findAccessibilityNodeInfosByViewTextClientThread(String text,
                 int accessibilityViewId, int interactionId,
-                IAccessibilityInteractionConnectionCallback callback) {
+                IAccessibilityInteractionConnectionCallback callback, int interrogatingPid,
+                long interrogatingTid) {
             Message message = Message.obtain();
             message.what = DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT;
             SomeArgs args = mPool.acquire();
@@ -4541,7 +4570,17 @@ public final class ViewRootImpl extends Handler implements ViewParent,
             args.argi2 = interactionId;
             args.arg2 = callback;
             message.obj = args;
-            sendMessage(message);
+            // If the interrogation is performed by the same thread as the main UI
+            // thread in this process, set the message as a static reference so
+            // after this call completes the same thread but in the interrogating
+            // client can handle the message to generate the result.
+            if (interrogatingPid == Process.myPid()
+                    && interrogatingTid == Looper.getMainLooper().getThread().getId()) {
+                message.setTarget(ViewRootImpl.this);
+                AccessibilityInteractionClient.getInstance().setSameThreadMessage(message);
+            } else {
+                sendMessage(message);
+            }
         }
 
         public void findAccessibilityNodeInfosByViewTextUiThread(Message message) {
@@ -4594,7 +4633,8 @@ public final class ViewRootImpl extends Handler implements ViewParent,
         }
 
         public void performAccessibilityActionClientThread(int accessibilityId, int action,
-                int interactionId, IAccessibilityInteractionConnectionCallback callback) {
+                int interactionId, IAccessibilityInteractionConnectionCallback callback,
+                int interogatingPid, long interrogatingTid) {
             Message message = Message.obtain();
             message.what = DO_PERFORM_ACCESSIBILITY_ACTION;
             SomeArgs args = mPool.acquire();
@@ -4603,7 +4643,17 @@ public final class ViewRootImpl extends Handler implements ViewParent,
             args.argi3 = interactionId;
             args.arg1 = callback;
             message.obj = args;
-            sendMessage(message);
+            // If the interrogation is performed by the same thread as the main UI
+            // thread in this process, set the message as a static reference so
+            // after this call completes the same thread but in the interrogating
+            // client can handle the message to generate the result.
+            if (interogatingPid == Process.myPid()
+                    && interrogatingTid == Looper.getMainLooper().getThread().getId()) {
+                message.setTarget(ViewRootImpl.this);
+                AccessibilityInteractionClient.getInstance().setSameThreadMessage(message);
+            } else {
+                sendMessage(message);
+            }
         }
 
         public void perfromAccessibilityActionUiThread(Message message) {
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
new file mode 100644 (file)
index 0000000..071701e
--- /dev/null
@@ -0,0 +1,462 @@
+/*
+ ** Copyright 2011, The Android Open Source Project
+ **
+ ** Licensed under the Apache License, Version 2.0 (the "License");
+ ** you may not use this file except in compliance with the License.
+ ** You may obtain a copy of the License at
+ **
+ **     http://www.apache.org/licenses/LICENSE-2.0
+ **
+ ** Unless required by applicable law or agreed to in writing, software
+ ** distributed under the License is distributed on an "AS IS" BASIS,
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ** See the License for the specific language governing permissions and
+ ** limitations under the License.
+ */
+
+package android.view.accessibility;
+
+import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.graphics.Rect;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemClock;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * This class is a singleton that performs accessibility interaction
+ * which is it queries remote view hierarchies about snapshots of their
+ * views as well requests from these hierarchies to perform certain
+ * actions on their views.
+ *
+ * Rationale: The content retrieval APIs are synchronous from a client's
+ *     perspective but internally they are asynchronous. The client thread
+ *     calls into the system requesting an action and providing a callback
+ *     to receive the result after which it waits up to a timeout for that
+ *     result. The system enforces security and the delegates the request
+ *     to a given view hierarchy where a message is posted (from a binder
+ *     thread) describing what to be performed by the main UI thread the
+ *     result of which it delivered via the mentioned callback. However,
+ *     the blocked client thread and the main UI thread of the target view
+ *     hierarchy can be the same thread, for example an accessibility service
+ *     and an activity run in the same process, thus they are executed on the
+ *     same main thread. In such a case the retrieval will fail since the UI
+ *     thread that has to process the message describing the work to be done
+ *     is blocked waiting for a result is has to compute! To avoid this scenario
+ *     when making a call the client also passes its process and thread ids so
+ *     the accessed view hierarchy can detect if the client making the request
+ *     is running in its main UI thread. In such a case the view hierarchy,
+ *     specifically the binder thread performing the IPC to it, does not post a
+ *     message to be run on the UI thread but passes it to the singleton
+ *     interaction client through which all interactions occur and the latter is
+ *     responsible to execute the message before starting to wait for the
+ *     asynchronous result delivered via the callback. In this case the expected
+ *     result is already received so no waiting is performed.
+ *
+ * @hide
+ */
+public final class AccessibilityInteractionClient
+        extends IAccessibilityInteractionConnectionCallback.Stub {
+
+    private static final long TIMEOUT_INTERACTION_MILLIS = 5000;
+
+    private static final Object sStaticLock = new Object();
+
+    private static AccessibilityInteractionClient sInstance;
+
+    private final AtomicInteger mInteractionIdCounter = new AtomicInteger();
+
+    private final Object mInstanceLock = new Object();
+
+    private int mInteractionId = -1;
+
+    private AccessibilityNodeInfo mFindAccessibilityNodeInfoResult;
+
+    private List<AccessibilityNodeInfo> mFindAccessibilityNodeInfosResult;
+
+    private boolean mPerformAccessibilityActionResult;
+
+    private Message mSameThreadMessage;
+
+    private final Rect mTempBounds = new Rect();
+
+    /**
+     * @return The singleton of this class.
+     */
+    public static AccessibilityInteractionClient getInstance() {
+        synchronized (sStaticLock) {
+            if (sInstance == null) {
+                sInstance = new AccessibilityInteractionClient();
+            }
+            return sInstance;
+        }
+    }
+
+    /**
+     * Sets the message to be processed if the interacted view hierarchy
+     * and the interacting client are running in the same thread.
+     *
+     * @param message The message.
+     */
+    public void setSameThreadMessage(Message message) {
+        synchronized (mInstanceLock) {
+            mSameThreadMessage = message;
+        }
+    }
+
+    /**
+     * Finds an {@link AccessibilityNodeInfo} by accessibility id.
+     *
+     * @param connection A connection for interacting with the system.
+     * @param accessibilityWindowId A unique window id.
+     * @param accessibilityViewId A unique View accessibility id.
+     * @return An {@link AccessibilityNodeInfo} if found, null otherwise.
+     */
+    public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(
+            IAccessibilityServiceConnection connection, int accessibilityWindowId,
+            int accessibilityViewId) {
+        try {
+            final int interactionId = mInteractionIdCounter.getAndIncrement();
+            final float windowScale = connection.findAccessibilityNodeInfoByAccessibilityId(
+                    accessibilityWindowId, accessibilityViewId, interactionId, this,
+                    Thread.currentThread().getId());
+            // If the scale is zero the call has failed.
+            if (windowScale > 0) {
+                handleSameThreadMessageIfNeeded();
+                AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
+                        interactionId);
+                finalizeAccessibilityNodeInfo(info, connection, windowScale);
+                return info;
+            }
+        } catch (RemoteException re) {
+            /* ignore */
+        }
+        return null;
+    }
+
+    /**
+     * 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.
+     *
+     * @param connection A connection for interacting with the system.
+     * @param id The id of the node.
+     * @return An {@link AccessibilityNodeInfo} if found, null otherwise.
+     */
+    public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(
+            IAccessibilityServiceConnection connection, int viewId) {
+        try {
+            final int interactionId = mInteractionIdCounter.getAndIncrement();
+            final float windowScale = connection.findAccessibilityNodeInfoByViewIdInActiveWindow(
+                    viewId, interactionId, this, Thread.currentThread().getId());
+            // If the scale is zero the call has failed.
+            if (windowScale > 0) {
+                handleSameThreadMessageIfNeeded();
+                AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
+                        interactionId);
+                finalizeAccessibilityNodeInfo(info, connection, windowScale);
+                return info;
+            }
+        } catch (RemoteException re) {
+            /* ignore */
+        }
+        return null;
+    }
+
+    /**
+     * 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 connection A connection for interacting with the system.
+     * @param text The searched text.
+     * @return A list of found {@link AccessibilityNodeInfo}s.
+     */
+    public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewTextInActiveWindow(
+            IAccessibilityServiceConnection connection, String text) {
+        try {
+            final int interactionId = mInteractionIdCounter.getAndIncrement();
+            final float windowScale = connection.findAccessibilityNodeInfosByViewTextInActiveWindow(
+                    text, interactionId, this, Thread.currentThread().getId());
+            // If the scale is zero the call has failed.
+            if (windowScale > 0) {
+                handleSameThreadMessageIfNeeded();
+                List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
+                        interactionId);
+                finalizeAccessibilityNodeInfos(infos, connection, windowScale);
+                return infos;
+            }
+        } catch (RemoteException re) {
+            /* ignore */
+        }
+        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
+     * specified.
+     *
+     * @param connection A connection for interacting with the system.
+     * @param text The searched text.
+     * @param accessibilityWindowId A unique window id.
+     * @param accessibilityViewId A unique View accessibility 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> findAccessibilityNodeInfosByViewText(
+            IAccessibilityServiceConnection connection, String text, int accessibilityWindowId,
+            int accessibilityViewId) {
+        try {
+            final int interactionId = mInteractionIdCounter.getAndIncrement();
+            final float windowScale = connection.findAccessibilityNodeInfosByViewText(text,
+                    accessibilityWindowId, accessibilityViewId, interactionId, this,
+                    Thread.currentThread().getId());
+            // If the scale is zero the call has failed.
+            if (windowScale > 0) {
+                handleSameThreadMessageIfNeeded();
+                List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
+                        interactionId);
+                finalizeAccessibilityNodeInfos(infos, connection, windowScale);
+                return infos;
+            }
+        } catch (RemoteException re) {
+            /* ignore */
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * Performs an accessibility action on an {@link AccessibilityNodeInfo}.
+     *
+     * @param connection A connection for interacting with the system.
+     * @param accessibilityWindowId The id of the window.
+     * @param accessibilityViewId A unique View accessibility id.
+     * @param action The action to perform.
+     * @return Whether the action was performed.
+     */
+    public boolean performAccessibilityAction(IAccessibilityServiceConnection connection,
+            int accessibilityWindowId, int accessibilityViewId, int action) {
+        try {
+            final int interactionId = mInteractionIdCounter.getAndIncrement();
+            final boolean success = connection.performAccessibilityAction(
+                    accessibilityWindowId, accessibilityViewId, action, interactionId, this,
+                    Thread.currentThread().getId());
+            if (success) {
+                handleSameThreadMessageIfNeeded();
+                return getPerformAccessibilityActionResult(interactionId);
+            }
+        } catch (RemoteException re) {
+            /* ignore */
+        }
+        return false;
+    }
+
+    /**
+     * Gets the the result of an async request that returns an {@link AccessibilityNodeInfo}.
+     *
+     * @param interactionId The interaction id to match the result with the request.
+     * @return The result {@link AccessibilityNodeInfo}.
+     */
+    private AccessibilityNodeInfo getFindAccessibilityNodeInfoResultAndClear(int interactionId) {
+        synchronized (mInstanceLock) {
+            final boolean success = waitForResultTimedLocked(interactionId);
+            AccessibilityNodeInfo result = success ? mFindAccessibilityNodeInfoResult : null;
+            clearResultLocked();
+            return result;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info,
+                int interactionId) {
+        synchronized (mInstanceLock) {
+            if (interactionId > mInteractionId) {
+                mFindAccessibilityNodeInfoResult = info;
+                mInteractionId = interactionId;
+            }
+            mInstanceLock.notifyAll();
+        }
+    }
+
+    /**
+     * Gets the the result of an async request that returns {@link AccessibilityNodeInfo}s.
+     *
+     * @param interactionId The interaction id to match the result with the request.
+     * @return The result {@link AccessibilityNodeInfo}s.
+     */
+    private List<AccessibilityNodeInfo> getFindAccessibilityNodeInfosResultAndClear(
+                int interactionId) {
+        synchronized (mInstanceLock) {
+            final boolean success = waitForResultTimedLocked(interactionId);
+            List<AccessibilityNodeInfo> result = success ? mFindAccessibilityNodeInfosResult : null;
+            clearResultLocked();
+            return result;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos,
+                int interactionId) {
+        synchronized (mInstanceLock) {
+            if (interactionId > mInteractionId) {
+                mFindAccessibilityNodeInfosResult = infos;
+                mInteractionId = interactionId;
+            }
+            mInstanceLock.notifyAll();
+        }
+    }
+
+    /**
+     * Gets the result of a request to perform an accessibility action.
+     *
+     * @param interactionId The interaction id to match the result with the request.
+     * @return Whether the action was performed.
+     */
+    private boolean getPerformAccessibilityActionResult(int interactionId) {
+        synchronized (mInstanceLock) {
+            final boolean success = waitForResultTimedLocked(interactionId);
+            final boolean result = success ? mPerformAccessibilityActionResult : false;
+            clearResultLocked();
+            return result;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setPerformAccessibilityActionResult(boolean succeeded, int interactionId) {
+        synchronized (mInstanceLock) {
+            if (interactionId > mInteractionId) {
+                mPerformAccessibilityActionResult = succeeded;
+                mInteractionId = interactionId;
+            }
+            mInstanceLock.notifyAll();
+        }
+    }
+
+    /**
+     * Clears the result state.
+     */
+    private void clearResultLocked() {
+        mInteractionId = -1;
+        mFindAccessibilityNodeInfoResult = null;
+        mFindAccessibilityNodeInfosResult = null;
+        mPerformAccessibilityActionResult = false;
+    }
+
+    /**
+     * Waits up to a given bound for a result of a request and returns it.
+     *
+     * @param interactionId The interaction id to match the result with the request.
+     * @return Whether the result was received.
+     */
+    private boolean waitForResultTimedLocked(int interactionId) {
+        long waitTimeMillis = TIMEOUT_INTERACTION_MILLIS;
+        final long startTimeMillis = SystemClock.uptimeMillis();
+        while (true) {
+            try {
+                if (mInteractionId == interactionId) {
+                    return true;
+                }
+                if (mInteractionId > interactionId) {
+                    return false;
+                }
+                final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+                waitTimeMillis = TIMEOUT_INTERACTION_MILLIS - elapsedTimeMillis;
+                if (waitTimeMillis <= 0) {
+                    return false;
+                }
+                mInstanceLock.wait(waitTimeMillis);
+            } catch (InterruptedException ie) {
+                /* ignore */
+            }
+        }
+    }
+
+    /**
+     * Applies compatibility scale to the info bounds if it is not equal to one.
+     *
+     * @param info The info whose bounds to scale.
+     * @param scale The scale to apply.
+     */
+    private void applyCompatibilityScaleIfNeeded(AccessibilityNodeInfo info, float scale) {
+        if (scale == 1.0f) {
+            return;
+        }
+        Rect bounds = mTempBounds;
+        info.getBoundsInParent(bounds);
+        bounds.scale(scale);
+        info.setBoundsInParent(bounds);
+
+        info.getBoundsInScreen(bounds);
+        bounds.scale(scale);
+        info.setBoundsInScreen(bounds);
+    }
+
+    /**
+     * Handles the message stored if the interacted and interacting
+     * threads are the same otherwise this is a NOP.
+     */
+    private void handleSameThreadMessageIfNeeded() {
+        Message sameProcessMessage = getSameProcessMessageAndClear();
+        if (sameProcessMessage != null) {
+            sameProcessMessage.getTarget().handleMessage(sameProcessMessage);
+        }
+    }
+
+    /**
+     * Finalize an {@link AccessibilityNodeInfo} before passing it to the client.
+     *
+     * @param info The info.
+     * @param connection The current connection to the system.
+     * @param windowScale The source window compatibility scale.
+     */
+    private void finalizeAccessibilityNodeInfo(AccessibilityNodeInfo info,
+            IAccessibilityServiceConnection connection, float windowScale) {
+        if (info != null) {
+            applyCompatibilityScaleIfNeeded(info, windowScale);
+            info.setConnection(connection);
+            info.setSealed(true);
+        }
+    }
+
+    /**
+     * Finalize {@link AccessibilityNodeInfo}s before passing them to the client.
+     *
+     * @param infos The {@link AccessibilityNodeInfo}s.
+     * @param connection The current connection to the system.
+     * @param windowScale The source window compatibility scale.
+     */
+    private void finalizeAccessibilityNodeInfos(List<AccessibilityNodeInfo> infos,
+            IAccessibilityServiceConnection connection, float windowScale) {
+        if (infos != null) {
+            final int infosCount = infos.size();
+            for (int i = 0; i < infosCount; i++) {
+                AccessibilityNodeInfo info = infos.get(i);
+                finalizeAccessibilityNodeInfo(info, connection, windowScale);
+            }
+        }
+    }
+
+    /**
+     * Gets the message stored if the interacted and interacting
+     * threads are the same.
+     *
+     * @return The message.
+     */
+    private Message getSameProcessMessageAndClear() {
+        synchronized (mInstanceLock) {
+            Message result = mSameThreadMessage;
+            mSameThreadMessage = null;
+            return result;
+        }
+    }
+}
index 6469b35..f0e8005 100644 (file)
@@ -20,7 +20,6 @@ import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.graphics.Rect;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.os.RemoteException;
 import android.text.TextUtils;
 import android.util.SparseIntArray;
 import android.view.View;
@@ -181,13 +180,9 @@ public class AccessibilityNodeInfo implements Parcelable {
         if (!canPerformRequestOverConnection(childAccessibilityViewId)) {
             return null;
         }
-        try {
-            return mConnection.findAccessibilityNodeInfoByAccessibilityId(mAccessibilityWindowId,
-                    childAccessibilityViewId);
-        } catch (RemoteException re) {
-             /* ignore*/
-        }
-        return null;
+        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        return client.findAccessibilityNodeInfoByAccessibilityId(mConnection,
+                mAccessibilityWindowId, childAccessibilityViewId);
     }
 
     /**
@@ -257,13 +252,9 @@ public class AccessibilityNodeInfo implements Parcelable {
         if (!canPerformRequestOverConnection(mAccessibilityViewId)) {
             return false;
         }
-        try {
-            return mConnection.performAccessibilityAction(mAccessibilityWindowId,
-                    mAccessibilityViewId, action);
-        } catch (RemoteException e) {
-            /* ignore */
-        }
-        return false;
+        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        return client.performAccessibilityAction(mConnection, mAccessibilityWindowId,
+                mAccessibilityViewId, action);
     }
 
     /**
@@ -284,13 +275,9 @@ public class AccessibilityNodeInfo implements Parcelable {
         if (!canPerformRequestOverConnection(mAccessibilityViewId)) {
             return Collections.emptyList();
         }
-        try {
-            return mConnection.findAccessibilityNodeInfosByViewText(text, mAccessibilityWindowId,
-                    mAccessibilityViewId);
-        } catch (RemoteException e) {
-            /* ignore */
-        }
-        return Collections.emptyList();
+        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        return client.findAccessibilityNodeInfosByViewText(mConnection, text,
+                mAccessibilityWindowId, mAccessibilityViewId);
     }
 
     /**
@@ -308,13 +295,9 @@ public class AccessibilityNodeInfo implements Parcelable {
         if (!canPerformRequestOverConnection(mAccessibilityViewId)) {
             return null;
         }
-        try {
-            return mConnection.findAccessibilityNodeInfoByAccessibilityId(
-                    mAccessibilityWindowId, mParentAccessibilityViewId);
-        } catch (RemoteException e) {
-            /* ignore */
-        }
-        return null;
+        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        return client.findAccessibilityNodeInfoByAccessibilityId(mConnection,
+                mAccessibilityWindowId, mParentAccessibilityViewId);
     }
 
     /**
index 210106f..afd7473 100644 (file)
@@ -18,7 +18,6 @@ package android.view.accessibility;
 
 import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.os.Parcelable;
-import android.os.RemoteException;
 import android.view.View;
 
 import java.util.ArrayList;
@@ -127,13 +126,9 @@ public class AccessibilityRecord {
         if (mSourceWindowId == View.NO_ID || mSourceViewId == View.NO_ID || mConnection == null) {
             return null;
         }
-        try {
-            return mConnection.findAccessibilityNodeInfoByAccessibilityId(mSourceWindowId,
-                    mSourceViewId);
-        } catch (RemoteException e) {
-           /* ignore */
-        }
-        return null;
+        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        return client.findAccessibilityNodeInfoByAccessibilityId(mConnection, mSourceWindowId,
+                mSourceViewId);
     }
 
     /**
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionCallback.aidl
new file mode 100644 (file)
index 0000000..eeab4f2
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+import android.view.accessibility.AccessibilityNodeInfo;
+import java.util.List;
+
+/**
+ * Callback for specifying the result for an asynchronous request made
+ * via calling a method on IAccessibilityInteractionCallback.
+ *
+ * @hide
+ */
+oneway interface IAccessibilityInteractionCallback {
+
+    /**
+     * Sets the result of an async request that returns an {@link AccessibilityNodeInfo}.
+     *
+     * @param infos The result {@link AccessibilityNodeInfo}.
+     * @param interactionId The interaction id to match the result with the request.
+     */
+    void setFindAccessibilityNodeInfoResult(in AccessibilityNodeInfo info, int interactionId);
+
+    /**
+     * Sets the result of an async request that returns {@link AccessibilityNodeInfo}s.
+     *
+     * @param infos The result {@link AccessibilityNodeInfo}s.
+     * @param interactionId The interaction id to match the result with the request.
+     */
+    void setFindAccessibilityNodeInfosResult(in List<AccessibilityNodeInfo> infos,
+        int interactionId);
+
+    /**
+     * Sets the result of a request to perform an accessibility action.
+     *
+     * @param Whether the action was performed.
+     * @param interactionId The interaction id to match the result with the request.
+     */
+    void setPerformAccessibilityActionResult(boolean succeeded, int interactionId);
+}
index d35186b..535d594 100644 (file)
@@ -28,14 +28,18 @@ import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
 oneway interface IAccessibilityInteractionConnection {
 
     void findAccessibilityNodeInfoByAccessibilityId(int accessibilityViewId, int interactionId,
-        IAccessibilityInteractionConnectionCallback callback);
+        IAccessibilityInteractionConnectionCallback callback,
+        int interrogatingPid, long interrogatingTid);
 
     void findAccessibilityNodeInfoByViewId(int id, int interactionId,
-        IAccessibilityInteractionConnectionCallback callback);
+        IAccessibilityInteractionConnectionCallback callback,
+        int interrogatingPid, long interrogatingTid);
 
     void findAccessibilityNodeInfosByViewText(String text, int accessibilityViewId,
-        int interactionId, IAccessibilityInteractionConnectionCallback callback);
+        int interactionId, IAccessibilityInteractionConnectionCallback callback,
+        int interrogatingPid, long interrogatingTid);
 
     void performAccessibilityAction(int accessibilityId, int action, int interactionId,
-        IAccessibilityInteractionConnectionCallback callback);
+        IAccessibilityInteractionConnectionCallback callback, int interrogatingPid,
+        long interrogatingTid);
 }
index 9c5e8dc..c1a3ab7 100644 (file)
@@ -27,10 +27,28 @@ import java.util.List;
  */
 oneway interface IAccessibilityInteractionConnectionCallback {
 
+    /**
+     * Sets the result of an async request that returns an {@link AccessibilityNodeInfo}.
+     *
+     * @param infos The result {@link AccessibilityNodeInfo}.
+     * @param interactionId The interaction id to match the result with the request.
+     */
     void setFindAccessibilityNodeInfoResult(in AccessibilityNodeInfo info, int interactionId);
 
+    /**
+     * Sets the result of an async request that returns {@link AccessibilityNodeInfo}s.
+     *
+     * @param infos The result {@link AccessibilityNodeInfo}s.
+     * @param interactionId The interaction id to match the result with the request.
+     */
     void setFindAccessibilityNodeInfosResult(in List<AccessibilityNodeInfo> infos,
         int interactionId);
 
+    /**
+     * Sets the result of a request to perform an accessibility action.
+     *
+     * @param Whether the action was performed.
+     * @param interactionId The interaction id to match the result with the request.
+     */
     void setPerformAccessibilityActionResult(boolean succeeded, int interactionId);
 }
index 28d965b..44ed75c 100644 (file)
         >
         <Button
             android:id="@+id/button1"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
+            android:layout_width="160px"
+            android:layout_height="100px"
             android:text="@string/button1"
         />
         <Button
             android:id="@+id/button2"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
+            android:layout_width="160px"
+            android:layout_height="100px"
             android:text="@string/button2"
         />
         <Button
             android:id="@+id/button3"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
+            android:layout_width="160px"
+            android:layout_height="100px"
             android:text="@string/button3"
         />
     </LinearLayout>
         >
         <Button
             android:id="@+id/button4"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
+            android:layout_width="160px"
+            android:layout_height="100px"
             android:text="@string/button4"
         />
         <Button
             android:id="@+id/button5"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
+            android:layout_width="160px"
+            android:layout_height="100px"
             android:text="@string/button5"
         />
         <Button
             android:id="@+id/button6"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
+            android:layout_width="160px"
+            android:layout_height="100px"
             android:text="@string/button6"
         />
     </LinearLayout>
         >
         <Button
             android:id="@+id/button7"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
+            android:layout_width="160px"
+            android:layout_height="100px"
             android:text="@string/button7"
         />
         <Button
             android:id="@+id/button8"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
+            android:layout_width="160px"
+            android:layout_height="100px"
             android:text="@string/button8"
         />
         <Button
             android:id="@+id/button9"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
+            android:layout_width="160px"
+            android:layout_height="100px"
             android:text="@string/button9"
         />
     </LinearLayout>
index 99d534c..a542a1b 100644 (file)
@@ -31,6 +31,7 @@ import android.test.ActivityInstrumentationTestCase2;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 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;
@@ -81,8 +82,8 @@ public class InterrogationActivityTest
             // bring up the activity
             getActivity();
 
-            AccessibilityNodeInfo button =
-                getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
+            AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
             assertNotNull(button);
             assertEquals(0, button.getChildCount());
 
@@ -91,8 +92,8 @@ public class InterrogationActivityTest
             button.getBoundsInParent(bounds);
             assertEquals(0, bounds.left);
             assertEquals(0, bounds.top);
-            assertEquals(73, bounds.right);
-            assertEquals(48, bounds.bottom);
+            assertEquals(160, bounds.right);
+            assertEquals(100, bounds.bottom);
 
             // char sequence attributes
             assertEquals("com.android.frameworks.coretests", button.getPackageName());
@@ -133,8 +134,8 @@ public class InterrogationActivityTest
             getActivity();
 
             // find a view by text
-            List<AccessibilityNodeInfo> buttons =
-                getConnection().findAccessibilityNodeInfosByViewTextInActiveWindow("butto");
+            List<AccessibilityNodeInfo> buttons =  AccessibilityInteractionClient.getInstance()
+                .findAccessibilityNodeInfosByViewTextInActiveWindow(getConnection(), "butto");
             assertEquals(9, buttons.size());
         } finally {
             afterClassIfNeeded();
@@ -170,8 +171,8 @@ public class InterrogationActivityTest
             classNameAndTextList.add("android.widget.ButtonButton8");
             classNameAndTextList.add("android.widget.ButtonButton9");
 
-            AccessibilityNodeInfo root =
-                getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.root);
+            AccessibilityNodeInfo root = AccessibilityInteractionClient.getInstance()
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.root);
             assertNotNull("We must find the existing root.", root);
 
             Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
@@ -214,15 +215,16 @@ public class InterrogationActivityTest
             getActivity();
 
             // find a view and make sure it is not focused
-            AccessibilityNodeInfo button =
-                getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
+            AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
             assertFalse(button.isFocused());
 
             // focus the view
             assertTrue(button.performAction(ACTION_FOCUS));
 
             // find the view again and make sure it is focused
-            button = getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
+            button =  AccessibilityInteractionClient.getInstance()
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
             assertTrue(button.isFocused());
         } finally {
             afterClassIfNeeded();
@@ -242,22 +244,24 @@ public class InterrogationActivityTest
             getActivity();
 
             // find a view and make sure it is not focused
-            AccessibilityNodeInfo button =
-                getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
+            AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
             assertFalse(button.isFocused());
 
             // focus the view
             assertTrue(button.performAction(ACTION_FOCUS));
 
             // find the view again and make sure it is focused
-            button =  getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
+            button = AccessibilityInteractionClient.getInstance()
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
             assertTrue(button.isFocused());
 
             // unfocus the view
             assertTrue(button.performAction(ACTION_CLEAR_FOCUS));
 
             // find the view again and make sure it is not focused
-            button =  getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
+            button = AccessibilityInteractionClient.getInstance()
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
             assertFalse(button.isFocused());
         } finally {
             afterClassIfNeeded();
@@ -278,15 +282,16 @@ public class InterrogationActivityTest
             getActivity();
 
             // find a view and make sure it is not selected
-            AccessibilityNodeInfo button =
-                getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
+            AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
             assertFalse(button.isSelected());
 
             // select the view
             assertTrue(button.performAction(ACTION_SELECT));
 
             // find the view again and make sure it is selected
-            button = getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
+            button = AccessibilityInteractionClient.getInstance()
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
             assertTrue(button.isSelected());
         } finally {
             afterClassIfNeeded();
@@ -306,22 +311,24 @@ public class InterrogationActivityTest
             getActivity();
 
             // find a view and make sure it is not selected
-            AccessibilityNodeInfo button =
-                getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
+            AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
             assertFalse(button.isSelected());
 
             // select the view
             assertTrue(button.performAction(ACTION_SELECT));
 
             // find the view again and make sure it is selected
-            button = getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
+            button = AccessibilityInteractionClient.getInstance()
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
             assertTrue(button.isSelected());
 
             // unselect the view
             assertTrue(button.performAction(ACTION_CLEAR_SELECTION));
 
             // find the view again and make sure it is not selected
-            button = getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
+            button =  AccessibilityInteractionClient.getInstance()
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
             assertFalse(button.isSelected());
         } finally {
             afterClassIfNeeded();
@@ -342,8 +349,8 @@ public class InterrogationActivityTest
             getActivity();
 
             // find a view and make sure it is not focused
-            AccessibilityNodeInfo button =
-                getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
+            AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
             assertFalse(button.isSelected());
 
             // focus the view
@@ -406,8 +413,8 @@ public class InterrogationActivityTest
             getActivity();
 
             // find a view and make sure it is not focused
-            AccessibilityNodeInfo button =
-                getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
+            AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
+                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
             AccessibilityNodeInfo parent = button.getParent();
             final int childCount = parent.getChildCount();
             for (int i = 0; i < childCount; i++) {
index af01c5b..09ddc2f 100644 (file)
@@ -40,7 +40,6 @@ import android.os.IBinder;
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.SystemClock;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.text.TextUtils.SimpleStringSplitter;
@@ -72,7 +71,6 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * This class is instantiated by the system as a system level service and can be
@@ -871,10 +869,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
 
         boolean mIsAutomation;
 
-        final Callback mCallback = new Callback();
-
-        final AtomicInteger mInteractionIdCounter = new AtomicInteger();
-
         final Rect mTempBounds = new Rect();
 
         // the events pending events to be dispatched to this service
@@ -974,14 +968,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
             }
         }
 
-        public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId)
+        public float findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId,
+                int interactionId, IAccessibilityInteractionConnectionCallback callback,
+                long interrogatingTid)
                 throws RemoteException {
             IAccessibilityInteractionConnection connection = null;
             synchronized (mLock) {
                 mSecurityPolicy.enforceCanRetrieveWindowContent(this);
                 final boolean permissionGranted = mSecurityPolicy.canRetrieveWindowContent(this);
                 if (!permissionGranted) {
-                    return null;
+                    return 0;
                 } else {
                     connection = getConnectionToRetrievalAllowingWindowLocked();
                     if (connection == null) {
@@ -989,22 +985,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                             Slog.e(LOG_TAG, "No interaction connection to a retrieve "
                                     + "allowing window.");
                         }
-                        return null;
+                        return 0;
                     }
                 }
             }
+            final int interrogatingPid = Binder.getCallingPid();
             final long identityToken = Binder.clearCallingIdentity();
             try {
-                final int interactionId = mInteractionIdCounter.getAndIncrement();
-                connection.findAccessibilityNodeInfoByViewId(viewId, interactionId, mCallback);
-                AccessibilityNodeInfo info = mCallback.getFindAccessibilityNodeInfoResultAndClear(
-                        interactionId);
-                if (info != null) {
-                    applyCompatibilityScaleIfNeeded(info);
-                    info.setConnection(this);
-                    info.setSealed(true);
-                }
-                return info;
+                connection.findAccessibilityNodeInfoByViewId(viewId, interactionId, callback,
+                        interrogatingPid, interrogatingTid);
             } catch (RemoteException re) {
                 if (DEBUG) {
                     Slog.e(LOG_TAG, "Error finding node.");
@@ -1012,51 +1001,44 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
             } finally {
                 Binder.restoreCallingIdentity(identityToken);
             }
-            return null;
+            return getCompatibilityScale(mSecurityPolicy.getRetrievalAllowingWindowLocked());
         }
 
-        public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewTextInActiveWindow(
-                String text) throws RemoteException {
+        public float findAccessibilityNodeInfosByViewTextInActiveWindow(
+                String text, int interactionId,
+                IAccessibilityInteractionConnectionCallback callback, long threadId)
+                throws RemoteException {
             return findAccessibilityNodeInfosByViewText(text,
-                    mSecurityPolicy.mRetrievalAlowingWindowId, View.NO_ID);
+                    mSecurityPolicy.mRetrievalAlowingWindowId, View.NO_ID, interactionId, callback,
+                    threadId);
         }
 
-        public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewText(String text,
-                int accessibilityWindowId, int accessibilityViewId) throws RemoteException {
+        public float findAccessibilityNodeInfosByViewText(String text,
+                int accessibilityWindowId, int accessibilityViewId, int interactionId,
+                IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
+                throws RemoteException {
             IAccessibilityInteractionConnection connection = null;
             synchronized (mLock) {
                 mSecurityPolicy.enforceCanRetrieveWindowContent(this);
                 final boolean permissionGranted =
                     mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, accessibilityWindowId);
                 if (!permissionGranted) {
-                    return null;
+                    return 0;
                 } else {
                     connection = getConnectionToRetrievalAllowingWindowLocked();
                     if (connection == null) {
                         if (DEBUG) {
                             Slog.e(LOG_TAG, "No interaction connection to focused window.");
                         }
-                        return null;
+                        return 0;
                     }
                 }
             }
+            final int interrogatingPid = Binder.getCallingPid();
             final long identityToken = Binder.clearCallingIdentity();
             try {
-                final int interactionId = mInteractionIdCounter.getAndIncrement();
                 connection.findAccessibilityNodeInfosByViewText(text, accessibilityViewId,
-                        interactionId, mCallback);
-                List<AccessibilityNodeInfo> infos =
-                    mCallback.getFindAccessibilityNodeInfosResultAndClear(interactionId);
-                if (infos != null) {
-                    final int infoCount = infos.size();
-                    for (int i = 0; i < infoCount; i++) {
-                        AccessibilityNodeInfo info = infos.get(i);
-                        applyCompatibilityScaleIfNeeded(info);
-                        info.setConnection(this);
-                        info.setSealed(true);
-                    }
-                }
-                return infos;
+                        interactionId, callback, interrogatingPid, interrogatingTid);
             } catch (RemoteException re) {
                 if (DEBUG) {
                     Slog.e(LOG_TAG, "Error finding node.");
@@ -1064,18 +1046,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
             } finally {
                 Binder.restoreCallingIdentity(identityToken);
             }
-            return null;
+            return getCompatibilityScale(accessibilityWindowId);
         }
 
-        public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(
-                int accessibilityWindowId, int accessibilityViewId) throws RemoteException {
+        public float findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
+                int accessibilityViewId, int interactionId,
+                IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
+                throws RemoteException {
             IAccessibilityInteractionConnection connection = null;
             synchronized (mLock) {
                 mSecurityPolicy.enforceCanRetrieveWindowContent(this);
                 final boolean permissionGranted =
                     mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, accessibilityWindowId);
                 if (!permissionGranted) {
-                    return null;
+                    return 0;
                 } else {
                     connection = mWindowIdToInteractionConnectionMap.get(accessibilityWindowId);
                     if (connection == null) {
@@ -1083,23 +1067,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                             Slog.e(LOG_TAG, "No interaction connection to window: "
                                     + accessibilityWindowId);
                         }
-                        return null;
+                        return 0;
                     }
                 }
             }
+            final int interrogatingPid = Binder.getCallingPid();
             final long identityToken = Binder.clearCallingIdentity();
             try {
-                final int interactionId = mInteractionIdCounter.getAndIncrement();
                 connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityViewId,
-                        interactionId, mCallback);
-                AccessibilityNodeInfo info =
-                     mCallback.getFindAccessibilityNodeInfoResultAndClear(interactionId);
-                if (info != null) {
-                    applyCompatibilityScaleIfNeeded(info);
-                    info.setConnection(this);
-                    info.setSealed(true);
-                }
-                return info;
+                        interactionId, callback, interrogatingPid, interrogatingTid);
             } catch (RemoteException re) {
                 if (DEBUG) {
                     Slog.e(LOG_TAG, "Error requesting node with accessibilityViewId: "
@@ -1108,11 +1084,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
             } finally {
                 Binder.restoreCallingIdentity(identityToken);
             }
-            return null;
+            return getCompatibilityScale(accessibilityWindowId);
         }
 
         public boolean performAccessibilityAction(int accessibilityWindowId,
-                int accessibilityViewId, int action) {
+                int accessibilityViewId, int action, int interactionId,
+                IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) {
             IAccessibilityInteractionConnection connection = null;
             synchronized (mLock) {
                 final boolean permissionGranted = mSecurityPolicy.canPerformActionLocked(this,
@@ -1130,12 +1107,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                     }
                 }
             }
+            final int interrogatingPid = Binder.getCallingPid();
             final long identityToken = Binder.clearCallingIdentity();
             try {
-                final int interactionId = mInteractionIdCounter.getAndIncrement();
                 connection.performAccessibilityAction(accessibilityViewId, action, interactionId,
-                        mCallback);
-                return mCallback.getPerformAccessibilityActionResult(interactionId);
+                        callback, interrogatingPid, interrogatingTid);
             } catch (RemoteException re) {
                 if (DEBUG) {
                     Slog.e(LOG_TAG, "Error requesting node with accessibilityViewId: "
@@ -1144,7 +1120,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
             } finally {
                 Binder.restoreCallingIdentity(identityToken);
             }
-            return false;
+            return true;
         }
 
         public void onServiceDisconnected(ComponentName componentName) {
@@ -1173,22 +1149,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
             return mWindowIdToInteractionConnectionMap.get(windowId);
         }
 
-        private void applyCompatibilityScaleIfNeeded(AccessibilityNodeInfo info) {
-            IBinder windowToken = mWindowIdToWindowTokenMap.get(info.getWindowId());
-            final float scale = mWindowManagerService.getWindowCompatibilityScale(windowToken);
-
-            if (scale == 1.0f) {
-                return;
-            }
-
-            Rect bounds = mTempBounds;
-            info.getBoundsInParent(bounds);
-            bounds.scale(scale);
-            info.setBoundsInParent(bounds);
-
-            info.getBoundsInScreen(bounds);
-            bounds.scale(scale);
-            info.setBoundsInScreen(bounds);
+        private float getCompatibilityScale(int windowId) {
+            IBinder windowToken = mWindowIdToWindowTokenMap.get(windowId);
+            return mWindowManagerService.getWindowCompatibilityScale(windowToken);
         }
     }
 
@@ -1275,99 +1238,4 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
             }
         }
     }
-
-    final class Callback extends IAccessibilityInteractionConnectionCallback.Stub {
-        private static final long TIMEOUT_INTERACTION_MILLIS = 5000;
-
-        private int mInteractionId = -1;
-        private AccessibilityNodeInfo mFindAccessibilityNodeInfoResult;
-        private List<AccessibilityNodeInfo> mFindAccessibilityNodeInfosResult;
-        private boolean mPerformAccessibilityActionResult;
-
-        public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info,
-                int interactionId) {
-            synchronized (mLock) {
-                if (interactionId > mInteractionId) {
-                    mFindAccessibilityNodeInfoResult = info;
-                    mInteractionId = interactionId;
-                }
-                mLock.notifyAll();
-            }
-        }
-
-        public AccessibilityNodeInfo getFindAccessibilityNodeInfoResultAndClear(int interactionId) {
-            synchronized (mLock) {
-                waitForResultTimedLocked(TIMEOUT_INTERACTION_MILLIS, interactionId);
-                AccessibilityNodeInfo result = mFindAccessibilityNodeInfoResult;
-                clearLocked();
-                return result;
-            }
-        }
-
-        public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos,
-                int interactionId) {
-            synchronized (mLock) {
-                if (interactionId > mInteractionId) {
-                    mFindAccessibilityNodeInfosResult = infos;
-                    mInteractionId = interactionId;
-                }
-                mLock.notifyAll();
-            }
-        }
-
-        public List<AccessibilityNodeInfo> getFindAccessibilityNodeInfosResultAndClear(
-                int interactionId) {
-            synchronized (mLock) {
-                waitForResultTimedLocked(TIMEOUT_INTERACTION_MILLIS, interactionId);
-                List<AccessibilityNodeInfo> result = mFindAccessibilityNodeInfosResult;
-                clearLocked();
-                return result;
-            }
-        }
-
-        public void setPerformAccessibilityActionResult(boolean succeeded, int interactionId) {
-            synchronized (mLock) {
-                if (interactionId > mInteractionId) {
-                    mPerformAccessibilityActionResult = succeeded;
-                    mInteractionId = interactionId;
-                }
-                mLock.notifyAll();
-            }
-        }
-
-        public boolean getPerformAccessibilityActionResult(int interactionId) {
-            synchronized (mLock) {
-                waitForResultTimedLocked(TIMEOUT_INTERACTION_MILLIS, interactionId);
-                final boolean result = mPerformAccessibilityActionResult;
-                clearLocked();
-                return result;
-            }
-        }
-
-        public void clearLocked() {
-            mInteractionId = -1;
-            mFindAccessibilityNodeInfoResult = null;
-            mFindAccessibilityNodeInfosResult = null;
-            mPerformAccessibilityActionResult = false;
-        }
-
-        private void waitForResultTimedLocked(long waitTimeMillis, int interactionId) {
-            final long startTimeMillis = SystemClock.uptimeMillis();
-            while (true) {
-                try {
-                    if (mInteractionId == interactionId) {
-                        return;
-                    }
-                    final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
-                    waitTimeMillis = TIMEOUT_INTERACTION_MILLIS - elapsedTimeMillis;
-                    if (waitTimeMillis <= 0) {
-                        return;
-                    }
-                    mLock.wait(waitTimeMillis);
-                } catch (InterruptedException ie) {
-                    /* ignore */
-                }
-            }
-        }
-    }
 }