Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8bd69610 authored by Svetoslav Ganov's avatar Svetoslav Ganov
Browse files

Intra-process view hierarchy interrogation does not work.

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

bug:5138933

Change-Id: I382e2d8689f5189110226613c2387f553df98bd3
parent 69314e72
Loading
Loading
Loading
Loading
+38 −42
Original line number Diff line number Diff line
@@ -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);
}
+66 −16
Original line number Diff line number Diff line
@@ -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,14 +4471,25 @@ 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;
            // 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) {
            final int accessibilityId = message.arg1;
@@ -4499,14 +4516,25 @@ 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;
            // 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) {
            final int viewId = message.arg1;
@@ -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,8 +4570,18 @@ public final class ViewRootImpl extends Handler implements ViewParent,
            args.argi2 = interactionId;
            args.arg2 = callback;
            message.obj = args;
            // If the interrogation is performed by the same thread as the main UI
            // thread in this process, set the message as a static reference so
            // after this call completes the same thread but in the interrogating
            // 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) {
            SomeArgs args = (SomeArgs) message.obj;
@@ -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,8 +4643,18 @@ public final class ViewRootImpl extends Handler implements ViewParent,
            args.argi3 = interactionId;
            args.arg1 = callback;
            message.obj = args;
            // If the interrogation is performed by the same thread as the main UI
            // thread in this process, set the message as a static reference so
            // after this call completes the same thread but in the interrogating
            // 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) {
            SomeArgs args = (SomeArgs) message.obj;
+462 −0

File added.

Preview size limit exceeded, changes collapsed.

+12 −29
Original line number Diff line number Diff line
@@ -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,
        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
        return client.performAccessibilityAction(mConnection, mAccessibilityWindowId,
                mAccessibilityViewId, action);
        } catch (RemoteException e) {
            /* ignore */
        }
        return false;
    }

    /**
@@ -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(
        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
        return client.findAccessibilityNodeInfoByAccessibilityId(mConnection,
                mAccessibilityWindowId, mParentAccessibilityViewId);
        } catch (RemoteException e) {
            /* ignore */
        }
        return null;
    }

    /**
+3 −8
Original line number Diff line number Diff line
@@ -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,
        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
        return client.findAccessibilityNodeInfoByAccessibilityId(mConnection, mSourceWindowId,
                mSourceViewId);
        } catch (RemoteException e) {
           /* ignore */
        }
        return null;
    }

    /**
Loading