import android.accessibilityservice.AccessibilityServiceInfo;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
/**
* Interface given to an AccessibilitySerivce to talk to the AccessibilityManagerService.
/**
* 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);
}
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;
}
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);
}
}
}
}
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) {
}
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) {
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();
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) {
}
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();
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) {
--- /dev/null
+/*
+ ** 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;
+ }
+ }
+}
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;
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);
}
/**
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);
}
/**
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);
}
/**
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);
}
/**
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.os.Parcelable;
-import android.os.RemoteException;
import android.view.View;
import java.util.ArrayList;
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);
}
/**
--- /dev/null
+/*
+ * 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);
+}
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);
}
*/
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);
}
>
<Button
android:id="@+id/button1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="160px"
+ android:layout_height="100px"
android:text="@string/button1"
/>
<Button
android:id="@+id/button2"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="160px"
+ android:layout_height="100px"
android:text="@string/button2"
/>
<Button
android:id="@+id/button3"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="160px"
+ android:layout_height="100px"
android:text="@string/button3"
/>
</LinearLayout>
>
<Button
android:id="@+id/button4"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="160px"
+ android:layout_height="100px"
android:text="@string/button4"
/>
<Button
android:id="@+id/button5"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="160px"
+ android:layout_height="100px"
android:text="@string/button5"
/>
<Button
android:id="@+id/button6"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="160px"
+ android:layout_height="100px"
android:text="@string/button6"
/>
</LinearLayout>
>
<Button
android:id="@+id/button7"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="160px"
+ android:layout_height="100px"
android:text="@string/button7"
/>
<Button
android:id="@+id/button8"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="160px"
+ android:layout_height="100px"
android:text="@string/button8"
/>
<Button
android:id="@+id/button9"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="160px"
+ android:layout_height="100px"
android:text="@string/button9"
/>
</LinearLayout>
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;
// 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());
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());
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();
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>();
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();
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();
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();
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();
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
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++) {
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.SystemClock;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.TextUtils.SimpleStringSplitter;
import 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
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
}
}
- 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) {
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.");
} 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.");
} 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) {
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: "
} 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,
}
}
}
+ 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: "
} finally {
Binder.restoreCallingIdentity(identityToken);
}
- return false;
+ return true;
}
public void onServiceDisconnected(ComponentName componentName) {
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);
}
}
}
}
}
-
- 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 */
- }
- }
- }
- }
}