diff options
11 files changed, 753 insertions, 316 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 f23c366..845f96c 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; @@ -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 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); } diff --git a/core/tests/coretests/res/layout/interrogation_activity.xml b/core/tests/coretests/res/layout/interrogation_activity.xml index 28d965b..44ed75c 100644 --- a/core/tests/coretests/res/layout/interrogation_activity.xml +++ b/core/tests/coretests/res/layout/interrogation_activity.xml @@ -30,20 +30,20 @@ > <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> @@ -55,20 +55,20 @@ > <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> @@ -80,20 +80,20 @@ > <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> diff --git a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java index 99d534c..a542a1b 100644 --- a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java +++ b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java @@ -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++) { diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index af01c5b..09ddc2f 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -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 */ - } - } - } - } } |