diff options
author | Svetoslav Ganov <svetoslavganov@google.com> | 2011-08-25 08:54:29 -0700 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2011-08-25 08:54:29 -0700 |
commit | 12bde60b39affbfdcb7ef6317e0a5f99c3f41b10 (patch) | |
tree | ebcb48da50b2271f67cd75917d3573c72d8ef2bc /core/java/android | |
parent | 8799b4bf673a90afca4e83fa6cfd528ad27076c9 (diff) | |
parent | 8bd69610aafc6995126965d1d23b771fe02a9084 (diff) | |
download | frameworks_base-12bde60b39affbfdcb7ef6317e0a5f99c3f41b10.zip frameworks_base-12bde60b39affbfdcb7ef6317e0a5f99c3f41b10.tar.gz frameworks_base-12bde60b39affbfdcb7ef6317e0a5f99c3f41b10.tar.bz2 |
Merge "Intra-process view hierarchy interrogation does not work."
Diffstat (limited to 'core/java/android')
8 files changed, 661 insertions, 99 deletions
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 8b4e7aee..7c41082 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -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); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index fdf6ad7..6ea863f 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -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; @@ -4362,37 +4363,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); } } } @@ -4468,13 +4474,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) { @@ -4502,13 +4519,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) { @@ -4535,7 +4563,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(); @@ -4544,7 +4573,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) { @@ -4597,7 +4636,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(); @@ -4606,7 +4646,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 index 0000000..071701e --- /dev/null +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -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; + } + } +} diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 6469b35..f0e8005 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -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); } /** diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java index 210106f..afd7473 100644 --- a/core/java/android/view/accessibility/AccessibilityRecord.java +++ b/core/java/android/view/accessibility/AccessibilityRecord.java @@ -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 index 0000000..eeab4f2 --- /dev/null +++ b/core/java/android/view/accessibility/IAccessibilityInteractionCallback.aidl @@ -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); +} diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl index d35186b..535d594 100644 --- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl +++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl @@ -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); } diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl index 9c5e8dc..c1a3ab7 100644 --- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl +++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl @@ -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); } |