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

Commit 749e796e authored by Svetoslav Ganov's avatar Svetoslav Ganov
Browse files

UI test automation cannot get the root node and gets null children.

1. The AccessibilityInteractionController was using an incorrect
   looper i.e. not the UI thread looper which was causing getting
   the root node to fail.

2. The AccessibilityNodeInfo was populated by a ViewGroup with the
   children for accessibility without checking whether these children
   are really displayed.

bug:6362875

Change-Id: I7906d89571eb9d57d10f971639f88632926dd077
parent caf7f3d1
Loading
Loading
Loading
Loading
+24 −43
Original line number Diff line number Diff line
@@ -58,9 +58,14 @@ final class AccessibilityInteractionController {

    private final AccessibilityNodePrefetcher mPrefetcher;

    private final long mMyLooperThreadId;

    private final int mMyProcessId;

    public AccessibilityInteractionController(ViewRootImpl viewRootImpl) {
        // mView is never null - the caller has already checked.
        Looper looper = viewRootImpl.mView.mContext.getMainLooper();
        Looper looper =  viewRootImpl.mHandler.getLooper();
        mMyLooperThreadId = looper.getThread().getId();
        mMyProcessId = Process.myPid();
        mHandler = new PrivateHandler(looper);
        mViewRootImpl = viewRootImpl;
        mPrefetcher = new AccessibilityNodePrefetcher();
@@ -137,8 +142,7 @@ final class AccessibilityInteractionController {
        // 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()) {
        if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
            AccessibilityInteractionClient.getInstanceForThread(
                    interrogatingTid).setSameThreadMessage(message);
        } else {
@@ -169,7 +173,7 @@ final class AccessibilityInteractionController {
            } else {
                root = findViewByAccessibilityId(accessibilityViewId);
            }
            if (root != null && isDisplayedOnScreen(root)) {
            if (root != null && root.isDisplayedOnScreen()) {
                mPrefetcher.prefetchAccessibilityNodeInfos(root, virtualDescendantId, flags, infos);
            }
        } finally {
@@ -199,8 +203,7 @@ final class AccessibilityInteractionController {
        // 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()) {
        if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
            AccessibilityInteractionClient.getInstanceForThread(
                    interrogatingTid).setSameThreadMessage(message);
        } else {
@@ -232,7 +235,7 @@ final class AccessibilityInteractionController {
            }
            if (root != null) {
                View target = root.findViewById(viewId);
                if (target != null && isDisplayedOnScreen(target)) {
                if (target != null && target.isDisplayedOnScreen()) {
                    info = target.createAccessibilityNodeInfo();
                }
            }
@@ -263,8 +266,7 @@ final class AccessibilityInteractionController {
        // 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()) {
        if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
            AccessibilityInteractionClient.getInstanceForThread(
                    interrogatingTid).setSameThreadMessage(message);
        } else {
@@ -295,7 +297,7 @@ final class AccessibilityInteractionController {
            } else {
                root = mViewRootImpl.mView;
            }
            if (root != null && isDisplayedOnScreen(root)) {
            if (root != null && root.isDisplayedOnScreen()) {
                AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
                if (provider != null) {
                    infos = provider.findAccessibilityNodeInfosByText(text,
@@ -312,7 +314,7 @@ final class AccessibilityInteractionController {
                        final int viewCount = foundViews.size();
                        for (int i = 0; i < viewCount; i++) {
                            View foundView = foundViews.get(i);
                            if (isDisplayedOnScreen(foundView)) {
                            if (foundView.isDisplayedOnScreen()) {
                                provider = foundView.getAccessibilityNodeProvider();
                                if (provider != null) {
                                    List<AccessibilityNodeInfo> infosFromProvider =
@@ -356,8 +358,7 @@ final class AccessibilityInteractionController {
        // 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()) {
        if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
            AccessibilityInteractionClient.getInstanceForThread(
                    interrogatingTid).setSameThreadMessage(message);
        } else {
@@ -388,7 +389,7 @@ final class AccessibilityInteractionController {
            } else {
                root = mViewRootImpl.mView;
            }
            if (root != null && isDisplayedOnScreen(root)) {
            if (root != null && root.isDisplayedOnScreen()) {
                switch (focusType) {
                    case AccessibilityNodeInfo.FOCUS_ACCESSIBILITY: {
                        View host = mViewRootImpl.mAccessibilityFocusedHost;
@@ -409,7 +410,7 @@ final class AccessibilityInteractionController {
                    case AccessibilityNodeInfo.FOCUS_INPUT: {
                        // Input focus cannot go to virtual views.
                        View target = root.findFocus();
                        if (target != null && isDisplayedOnScreen(target)) {
                        if (target != null && target.isDisplayedOnScreen()) {
                            focused = target.createAccessibilityNodeInfo();
                        }
                    } break;
@@ -444,8 +445,7 @@ final class AccessibilityInteractionController {
        // 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()) {
        if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
            AccessibilityInteractionClient.getInstanceForThread(
                    interrogatingTid).setSameThreadMessage(message);
        } else {
@@ -476,7 +476,7 @@ final class AccessibilityInteractionController {
            } else {
                root = mViewRootImpl.mView;
            }
            if (root != null && isDisplayedOnScreen(root)) {
            if (root != null && root.isDisplayedOnScreen()) {
                if ((direction & View.FOCUS_ACCESSIBILITY) ==  View.FOCUS_ACCESSIBILITY) {
                    AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
                    if (provider != null) {
@@ -530,8 +530,7 @@ final class AccessibilityInteractionController {
        // 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()) {
        if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
            AccessibilityInteractionClient.getInstanceForThread(
                    interrogatingTid).setSameThreadMessage(message);
        } else {
@@ -562,7 +561,7 @@ final class AccessibilityInteractionController {
            } else {
                target = mViewRootImpl.mView;
            }
            if (target != null && isDisplayedOnScreen(target)) {
            if (target != null && target.isDisplayedOnScreen()) {
                AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
                if (provider != null) {
                    succeeded = provider.performAccessibilityAction(action, virtualDescendantId);
@@ -586,30 +585,12 @@ final class AccessibilityInteractionController {
            return null;
        }
        View foundView = root.findViewByAccessibilityId(accessibilityId);
        if (foundView != null && !isDisplayedOnScreen(foundView)) {
        if (foundView != null && !foundView.isDisplayedOnScreen()) {
            return null;
        }
        return foundView;
    }

    /**
     * Computes whether a view is visible on the screen.
     *
     * @param view The view to check.
     * @return Whether the view is visible on the screen.
     */
    private boolean isDisplayedOnScreen(View view) {
        // The first two checks are made also made by isShown() which
        // however traverses the tree up to the parent to catch that.
        // Therefore, we do some fail fast check to minimize the up
        // tree traversal.
        return (view.mAttachInfo != null
                && view.mAttachInfo.mWindowVisibility == View.VISIBLE
                && view.getAlpha() > 0
                && view.isShown()
                && view.getGlobalVisibleRect(mViewRootImpl.mTempRect));
    }

    /**
     * This class encapsulates a prefetching strategy for the accessibility APIs for
     * querying window content. It is responsible to prefetch a batch of
@@ -684,7 +665,7 @@ final class AccessibilityInteractionController {
                    }
                    View child = children.getChildAt(i);
                    if (child.getAccessibilityViewId() != current.getAccessibilityViewId()
                            &&  isDisplayedOnScreen(child)) {
                            &&  child.isDisplayedOnScreen()) {
                        AccessibilityNodeInfo info = null;
                        AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
                        if (provider == null) {
@@ -718,7 +699,7 @@ final class AccessibilityInteractionController {
                    return;
                }
                View child = children.getChildAt(i);
                if ( isDisplayedOnScreen(child)) {
                if (child.isDisplayedOnScreen()) {
                    AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
                    if (provider == null) {
                        AccessibilityNodeInfo info = child.createAccessibilityNodeInfo();
+19 −2
Original line number Diff line number Diff line
@@ -4679,6 +4679,23 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
        }
    }
    /**
     * Computes whether this view is visible on the screen.
     *
     * @return Whether the view is visible on the screen.
     */
    boolean isDisplayedOnScreen() {
        // The first two checks are made also made by isShown() which
        // however traverses the tree up to the parent to catch that.
        // Therefore, we do some fail fast check to minimize the up
        // tree traversal.
        return (mAttachInfo != null
                && mAttachInfo.mWindowVisibility == View.VISIBLE
                && getAlpha() > 0
                && isShown()
                && getGlobalVisibleRect(mAttachInfo.mTmpInvalRect));
    }
    /**
     * Sets a delegate for implementing accessibility support via compositon as
     * opposed to inheritance. The delegate's primary use is for implementing
@@ -6301,9 +6318,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
    boolean includeForAccessibility() {
        if (mAttachInfo != null) {
            if (!mAttachInfo.mIncludeNotImportantViews) {
                return isImportantForAccessibility();
                return isImportantForAccessibility() && isDisplayedOnScreen();
            } else {
                return true;
                return isDisplayedOnScreen();
            }
        }
        return false;