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

Commit a6cdf865 authored by Svetoslav Ganov's avatar Svetoslav Ganov Committed by Android (Google) Code Review
Browse files

Merge "Improving accessibility APIs used for UI automation."

parents edabdcf5 0d04e245
Loading
Loading
Loading
Loading
+17 −17
Original line number Diff line number Diff line
@@ -30,14 +30,14 @@ interface IAccessibilityServiceConnection {
    void setServiceInfo(in AccessibilityServiceInfo info);

    /**
     * Finds an {@link AccessibilityNodeInfo} by accessibility id.
     * Finds an {@link android.view.accessibility.AccessibilityNodeInfo} by accessibility id.
     *
     * @param accessibilityWindowId A unique window id. Use
     *     {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
     *     {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
     *     to query the currently active window.
     * @param accessibilityNodeId A unique view id or virtual descendant id from
     *     where to start the search. Use
     *     {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
     *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
     *     to start from the root.
     * @param interactionId The id of the interaction for matching with the callback result.
     * @param callback Callback which to receive the result.
@@ -49,17 +49,16 @@ interface IAccessibilityServiceConnection {
        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 node whose accessibility id is
     * specified.
     * Finds {@link android.view.accessibility.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 node whose accessibility id is specified.
     *
     * @param accessibilityWindowId A unique window id. Use
     *     {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
     *     {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
     *     to query the currently active window.
     * @param accessibilityNodeId A unique view id or virtual descendant id from
     *     where to start the search. Use
     *     {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
     *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
     *     to start from the root.
     * @param text The searched text.
     * @param interactionId The id of the interaction for matching with the callback result.
@@ -72,16 +71,16 @@ interface IAccessibilityServiceConnection {
        long threadId);

    /**
     * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed in
     * the window whose id is specified and starts from the node whose accessibility
     * id is specified.
     * Finds an {@link android.view.accessibility.AccessibilityNodeInfo} by View id. The search
     * is performed in the window whose id is specified and starts from the node whose
     * accessibility id is specified.
     *
     * @param accessibilityWindowId A unique window id. Use
     *     {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
     *     {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
     *     to query the currently active window.
     * @param accessibilityNodeId A unique view id or virtual descendant id from
     *     where to start the search. Use
     *     {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
     *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
     *     to start from the root.
     * @param id The id of the node.
     * @param interactionId The id of the interaction for matching with the callback result.
@@ -94,14 +93,15 @@ interface IAccessibilityServiceConnection {
        long threadId);

    /**
     * Performs an accessibility action on an {@link AccessibilityNodeInfo}.
     * Performs an accessibility action on an
     * {@link android.view.accessibility.AccessibilityNodeInfo}.
     *
     * @param accessibilityWindowId A unique window id. Use
     *     {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
     *     {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
     *     to query the currently active window.
     * @param accessibilityNodeId A unique view id or virtual descendant id from
     *     where to start the search. Use
     *     {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
     *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
     *     to start from the root.
     * @param action The action to perform.
     * @param interactionId The id of the interaction for matching with the callback result.
+59 −31
Original line number Diff line number Diff line
@@ -49,11 +49,13 @@ public class UiTestAutomationBridge {

    private static final String LOG_TAG = UiTestAutomationBridge.class.getSimpleName();

    public static final int ACTIVE_WINDOW_ID = -1;
    private static final int TIMEOUT_REGISTER_SERVICE = 5000;

    public static final long ROOT_NODE_ID = -1;
    public static final int ACTIVE_WINDOW_ID = AccessibilityNodeInfo.ACTIVE_WINDOW_ID;

    private static final int TIMEOUT_REGISTER_SERVICE = 5000;
    public static final long ROOT_NODE_ID = AccessibilityNodeInfo.ROOT_NODE_ID;

    public static final int UNDEFINED = -1;

    private final Object mLock = new Object();

@@ -63,8 +65,6 @@ public class UiTestAutomationBridge {

    private AccessibilityEvent mLastEvent;

    private AccessibilityEvent mLastWindowStateChangeEvent;

    private volatile boolean mWaitingForEventDelivery;

    private volatile boolean mUnprocessedEventAvailable;
@@ -141,17 +141,8 @@ public class UiTestAutomationBridge {
                synchronized (mLock) {
                    while (true) {
                        mLastEvent = AccessibilityEvent.obtain(event);

                        final int eventType = event.getEventType();
                        if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
                                || eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) {
                            if (mLastWindowStateChangeEvent != null) {
                                mLastWindowStateChangeEvent.recycle();
                            }
                            mLastWindowStateChangeEvent = mLastEvent;
                        }

                        if (!mWaitingForEventDelivery) {
                            mLock.notifyAll();
                            break;
                        }
                        if (!mUnprocessedEventAvailable) {
@@ -294,6 +285,43 @@ public class UiTestAutomationBridge {
        }
    }

    /**
     * Waits for the accessibility event stream to become idle, which is not to
     * have received a new accessibility event within <code>idleTimeout</code>,
     * and do so within a maximal global timeout as specified by
     * <code>globalTimeout</code>.
     *
     * @param idleTimeout The timeout between two event to consider the device idle.
     * @param globalTimeout The maximal global timeout in which to wait for idle.
     */
    public void waitForIdle(long idleTimeout, long globalTimeout) {
        final long startTimeMillis = SystemClock.uptimeMillis();
        long lastEventTime = (mLastEvent != null)
                ? mLastEvent.getEventTime() : SystemClock.uptimeMillis();
        synchronized (mLock) {
            while (true) {
                final long currentTimeMillis = SystemClock.uptimeMillis();
                final long sinceLastEventTimeMillis = currentTimeMillis - lastEventTime;
                if (sinceLastEventTimeMillis > idleTimeout) {
                    return;
                }
                if (mLastEvent != null) {
                    lastEventTime = mLastEvent.getEventTime();
                }
                final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
                final long remainingTimeMillis = globalTimeout - elapsedTimeMillis;
                if (remainingTimeMillis <= 0) {
                    return;
                }
                try {
                     mLock.wait(idleTimeout);
                } catch (InterruptedException e) {
                     /* ignore */
                }
            }
        }
    }

    /**
     * Finds an {@link AccessibilityNodeInfo} by accessibility id in the active
     * window. The search is performed from the root node.
@@ -310,8 +338,8 @@ public class UiTestAutomationBridge {
    /**
     * Finds an {@link AccessibilityNodeInfo} by accessibility id.
     *
     * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID}
     *     to query the currently active window.
     * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID} to query
     *     the currently active window.
     * @param accessibilityNodeId A unique view id or virtual descendant id for
     *     which to search.
     * @return The current window scale, where zero means a failure.
@@ -341,8 +369,8 @@ public class UiTestAutomationBridge {
     * the window whose id is specified and starts from the node whose accessibility
     * id is specified.
     *
     * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID}
     *     to query the currently active window.
     * @param accessibilityWindowId A unique window id. Use
     *     {@link  #ACTIVE_WINDOW_ID} to query the currently active window.
     * @param accessibilityNodeId A unique view id or virtual descendant id from
     *     where to start the search. Use {@link  #ROOT_NODE_ID} to start from the root.
     * @return The current window scale, where zero means a failure.
@@ -374,8 +402,8 @@ public class UiTestAutomationBridge {
     * id is specified and starts from the node whose accessibility id is
     * specified.
     *
     * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID}
     *     to query the currently active window.
     * @param accessibilityWindowId A unique window id. Use
     *     {@link #ACTIVE_WINDOW_ID} to query the currently active window.
     * @param accessibilityNodeId A unique view id or virtual descendant id from
     *     where to start the search. Use {@link #ROOT_NODE_ID} to start from the root.
     * @param text The searched text.
@@ -406,8 +434,8 @@ public class UiTestAutomationBridge {
    /**
     * Performs an accessibility action on an {@link AccessibilityNodeInfo}.
     *
     * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID}
     *     to query the currently active window.
     * @param accessibilityWindowId A unique window id. Use
     *     {@link #ACTIVE_WINDOW_ID} to query the currently active window.
     * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id).
     * @param action The action to perform.
     * @return Whether the action was performed.
@@ -427,16 +455,16 @@ public class UiTestAutomationBridge {
     * @return The root info.
     */
    public AccessibilityNodeInfo getRootAccessibilityNodeInfoInActiveWindow() {
        synchronized (mLock) {
            if (mLastWindowStateChangeEvent != null) {
                return mLastWindowStateChangeEvent.getSource();
            }
        }
        return null;
        // Cache the id to avoid locking
        final int connectionId = mConnectionId;
        ensureValidConnection(connectionId);
        return AccessibilityInteractionClient.getInstance()
                .findAccessibilityNodeInfoByAccessibilityId(connectionId, ACTIVE_WINDOW_ID,
                        ROOT_NODE_ID);
    }

    private void ensureValidConnection(int connectionId) {
        if (connectionId == AccessibilityInteractionClient.NO_ID) {
        if (connectionId == UNDEFINED) {
            throw new IllegalStateException("UiAutomationService not connected."
                    + " Did you call #register()?");
        }
+27 −2
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.view;

import android.os.Process;
import android.util.Log;
import android.util.LongSparseArray;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -30,7 +32,11 @@ import android.view.accessibility.AccessibilityNodeInfo;
 */
public class AccessibilityNodeInfoCache {

    private final boolean ENABLED = true;
    private static final String LOG_TAG = AccessibilityNodeInfoCache.class.getSimpleName();

    private static final boolean ENABLED = true;

    private static final boolean DEBUG = false;

    /**
     * @return A new <strong>not synchronized</strong> AccessibilityNodeInfoCache.
@@ -95,6 +101,7 @@ public class AccessibilityNodeInfoCache {
    public void onAccessibilityEvent(AccessibilityEvent event) {
        final int eventType = event.getEventType();
        switch (eventType) {
            case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
            case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
            case AccessibilityEvent.TYPE_VIEW_SCROLLED:
                clear();
@@ -117,7 +124,14 @@ public class AccessibilityNodeInfoCache {
     */
    public AccessibilityNodeInfo get(long accessibilityNodeId) {
        if (ENABLED) {
            if (DEBUG) {
                AccessibilityNodeInfo info = mCacheImpl.get(accessibilityNodeId);
                Log.i(LOG_TAG, "Process: " + Process.myPid() +
                        " get(" + accessibilityNodeId + ") = " + info);
                return info;
            } else {
                return mCacheImpl.get(accessibilityNodeId);
            }
        } else {
            return null;
        }
@@ -131,6 +145,10 @@ public class AccessibilityNodeInfoCache {
     */
    public void put(long accessibilityNodeId, AccessibilityNodeInfo info) {
        if (ENABLED) {
            if (DEBUG) {
                Log.i(LOG_TAG, "Process: " + Process.myPid()
                        + " put(" + accessibilityNodeId + ", " + info + ")");
            }
            mCacheImpl.put(accessibilityNodeId, info);
        }
    }
@@ -156,6 +174,10 @@ public class AccessibilityNodeInfoCache {
     */
    public void remove(long accessibilityNodeId) {
        if (ENABLED) {
            if (DEBUG) {
                Log.i(LOG_TAG,  "Process: " + Process.myPid()
                        + " remove(" + accessibilityNodeId + ")");
            }
            mCacheImpl.remove(accessibilityNodeId);
        }
    }
@@ -165,6 +187,9 @@ public class AccessibilityNodeInfoCache {
     */
    public void clear() {
        if (ENABLED) {
            if (DEBUG) {
                Log.i(LOG_TAG,  "Process: " + Process.myPid() + "clear()");
            }
            mCacheImpl.clear();
        }
    }
+40 −37
Original line number Diff line number Diff line
@@ -2647,8 +2647,8 @@ public final class ViewRootImpl implements ViewParent,
                        mHasHadWindowFocus = true;
                    }

                    if (hasWindowFocus && mView != null) {
                        sendAccessibilityEvents();
                    if (hasWindowFocus && mView != null && mAccessibilityManager.isEnabled()) {
                        mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
                    }
                }
            } break;
@@ -4062,21 +4062,6 @@ public final class ViewRootImpl implements ViewParent,
        }
    }

    /**
     * The window is getting focus so if there is anything focused/selected
     * send an {@link AccessibilityEvent} to announce that.
     */
    private void sendAccessibilityEvents() {
        if (!mAccessibilityManager.isEnabled()) {
            return;
        }
        mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
        View focusedView = mView.findFocus();
        if (focusedView != null && focusedView != mView) {
            focusedView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
        }
    }

    /**
     * Post a callback to send a
     * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event.
@@ -4646,24 +4631,35 @@ public final class ViewRootImpl implements ViewParent,
        public void onAccessibilityStateChanged(boolean enabled) {
            if (enabled) {
                ensureConnection();
                if (mAttachInfo != null && mAttachInfo.mHasWindowFocus) {
                    mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
                    View focusedView = mView.findFocus();
                    if (focusedView != null && focusedView != mView) {
                        focusedView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
                    }
                }
            } else {
                ensureNoConnection();
            }
        }

        public void ensureConnection() {
            final boolean registered = mAttachInfo.mAccessibilityWindowId != View.NO_ID;
            if (mAttachInfo != null) {
                final boolean registered =
                    mAttachInfo.mAccessibilityWindowId != AccessibilityNodeInfo.UNDEFINED;
                if (!registered) {
                    mAttachInfo.mAccessibilityWindowId =
                        mAccessibilityManager.addAccessibilityInteractionConnection(mWindow,
                                new AccessibilityInteractionConnection(ViewRootImpl.this));
                }
            }
        }

        public void ensureNoConnection() {
            final boolean registered = mAttachInfo.mAccessibilityWindowId != View.NO_ID;
            final boolean registered =
                mAttachInfo.mAccessibilityWindowId != AccessibilityNodeInfo.UNDEFINED;
            if (registered) {
                mAttachInfo.mAccessibilityWindowId = View.NO_ID;
                mAttachInfo.mAccessibilityWindowId = AccessibilityNodeInfo.UNDEFINED;
                mAccessibilityManager.removeAccessibilityInteractionConnection(mWindow);
            }
        }
@@ -4860,16 +4856,23 @@ public final class ViewRootImpl implements ViewParent,
            List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
            infos.clear();
            try {
                if (accessibilityViewId == AccessibilityNodeInfo.UNDEFINED) {
                    View target = ViewRootImpl.this.mView;
                    if (target != null && target.getVisibility() == View.VISIBLE) {
                        infos.add(target.createAccessibilityNodeInfo());
                    }
                } else {
                    View target = findViewByAccessibilityId(accessibilityViewId);
                    if (target != null && target.getVisibility() == View.VISIBLE) {
                        AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
                        if (provider != null) {
                            infos.add(provider.createAccessibilityNodeInfo(virtualDescendantId));
                    } else if (virtualDescendantId == View.NO_ID) {
                        } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED) {
                            getAccessibilityPrefetchStrategy().prefetchAccessibilityNodeInfos(
                                    interrogatingPid, target, infos);
                        }
                    }
                }
            } finally {
                try {
                    callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
@@ -4915,7 +4918,7 @@ public final class ViewRootImpl implements ViewParent,
            AccessibilityNodeInfo info = null;
            try {
                View root = null;
                if (accessibilityViewId != View.NO_ID) {
                if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
                    root = findViewByAccessibilityId(accessibilityViewId);
                } else {
                    root = ViewRootImpl.this.mView;
@@ -4973,7 +4976,7 @@ public final class ViewRootImpl implements ViewParent,
            List<AccessibilityNodeInfo> infos = null;
            try {
                View target;
                if (accessibilityViewId != View.NO_ID) {
                if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
                    target = findViewByAccessibilityId(accessibilityViewId);
                } else {
                    target = ViewRootImpl.this.mView;
@@ -4983,7 +4986,7 @@ public final class ViewRootImpl implements ViewParent,
                    if (provider != null) {
                        infos = provider.findAccessibilityNodeInfosByText(text,
                                virtualDescendantId);
                    } else if (virtualDescendantId == View.NO_ID) {
                    } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED) {
                        ArrayList<View> foundViews = mAttachInfo.mFocusablesTempList;
                        foundViews.clear();
                        target.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT
@@ -5063,7 +5066,7 @@ public final class ViewRootImpl implements ViewParent,
                    if (provider != null) {
                        succeeded = provider.performAccessibilityAction(action,
                                virtualDescendantId);
                    } else if (virtualDescendantId == View.NO_ID) {
                    } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED) {
                        switch (action) {
                            case AccessibilityNodeInfo.ACTION_FOCUS: {
                                if (!target.hasFocus()) {
@@ -5171,7 +5174,7 @@ public final class ViewRootImpl implements ViewParent,
        private void addAndCacheNotCachedNodeInfo(long interrogatingPid,
                View view, List<AccessibilityNodeInfo> outInfos) {
            final long accessibilityNodeId = AccessibilityNodeInfo.makeNodeId(
                    view.getAccessibilityViewId(), View.NO_ID);
                    view.getAccessibilityViewId(), AccessibilityNodeInfo.UNDEFINED);
            AccessibilityNodeInfoCache cache = getCacheForInterrogatingPid(interrogatingPid);
            if (!cache.containsKey(accessibilityNodeId)) {
                // Account for the ids of the fetched infos. The infos will be
+28 −32

File changed.

Preview size limit exceeded, changes collapsed.

Loading