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

Commit 834787af authored by Qasid Ahmad Sadiq's avatar Qasid Ahmad Sadiq Committed by Qasid Sadiq
Browse files

Use map to traverse accessibilityIds instead of the view tree

This is the first step to a few things including allowing for virtual nodes to have view backed descendants and siblings.
The main change in findViewByAccessibilityId SPI (its changed name and location, but it still is effectively the same thing) is that if something has a nodeProvider it will now still return view children.
I sifted through each use of the SPI and determined if they were an issue, and cleaned up any code in the process (Autofill cleanup is coming in a different CL).

Test: added a few simple unit tests, used talkback on system in a few places including virtual views, atest accessibility*, cts accessibility* (There were some failures here but they don't seem to be caused by this change, those are captured in these bugs b/120628276 b/120890822), CtsAutoFillServiceTestCases
Bug: 37714287

Change-Id: Id5807114fbb4c932eaea275dc2d94d0dbe25a8ae
parent da5f5c85
Loading
Loading
Loading
Loading
+21 −58
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import android.util.Slog;
import android.view.View.AttachInfo;
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeIdManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.AccessibilityNodeProvider;
@@ -154,13 +155,7 @@ public final class AccessibilityInteractionController {
    }

    private boolean isShown(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.isShown());
        return (view != null) && (view.getWindowVisibility() == View.VISIBLE && view.isShown());
    }

    public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
@@ -340,13 +335,8 @@ public final class AccessibilityInteractionController {
                return;
            }
            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
            View root = null;
            if (accessibilityViewId == AccessibilityNodeInfo.ROOT_ITEM_ID) {
                root = mViewRootImpl.mView;
            } else {
                root = findViewByAccessibilityId(accessibilityViewId);
            }
            if (root != null && isShown(root)) {
            final View root = findViewByAccessibilityId(accessibilityViewId);
            if (root != null) {
                mPrefetcher.prefetchAccessibilityNodeInfos(
                        root, virtualDescendantId, flags, infos, arguments);
            }
@@ -396,12 +386,7 @@ public final class AccessibilityInteractionController {
                return;
            }
            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
            View root = null;
            if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {
                root = findViewByAccessibilityId(accessibilityViewId);
            } else {
                root = mViewRootImpl.mView;
            }
            final View root = findViewByAccessibilityId(accessibilityViewId);
            if (root != null) {
                final int resolvedViewId = root.getContext().getResources()
                        .getIdentifier(viewId, null, null);
@@ -462,13 +447,8 @@ public final class AccessibilityInteractionController {
                return;
            }
            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
            View root = null;
            if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {
                root = findViewByAccessibilityId(accessibilityViewId);
            } else {
                root = mViewRootImpl.mView;
            }
            if (root != null && isShown(root)) {
            final View root = findViewByAccessibilityId(accessibilityViewId);
            if (root != null) {
                AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
                if (provider != null) {
                    infos = provider.findAccessibilityNodeInfosByText(text,
@@ -550,13 +530,8 @@ public final class AccessibilityInteractionController {
                return;
            }
            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
            View root = null;
            if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {
                root = findViewByAccessibilityId(accessibilityViewId);
            } else {
                root = mViewRootImpl.mView;
            }
            if (root != null && isShown(root)) {
            final View root = findViewByAccessibilityId(accessibilityViewId);
            if (root != null) {
                switch (focusType) {
                    case AccessibilityNodeInfo.FOCUS_ACCESSIBILITY: {
                        View host = mViewRootImpl.mAccessibilityFocusedHost;
@@ -583,7 +558,7 @@ public final class AccessibilityInteractionController {
                    } break;
                    case AccessibilityNodeInfo.FOCUS_INPUT: {
                        View target = root.findFocus();
                        if (target == null || !isShown(target)) {
                        if (!isShown(target)) {
                            break;
                        }
                        AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
@@ -645,13 +620,8 @@ public final class AccessibilityInteractionController {
                return;
            }
            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
            View root = null;
            if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {
                root = findViewByAccessibilityId(accessibilityViewId);
            } else {
                root = mViewRootImpl.mView;
            }
            if (root != null && isShown(root)) {
            final View root = findViewByAccessibilityId(accessibilityViewId);
            if (root != null) {
                View nextView = root.focusSearch(direction);
                if (nextView != null) {
                    next = nextView.createAccessibilityNodeInfo();
@@ -705,13 +675,8 @@ public final class AccessibilityInteractionController {
                return;
            }
            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
            View target = null;
            if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {
                target = findViewByAccessibilityId(accessibilityViewId);
            } else {
                target = mViewRootImpl.mView;
            }
            if (target != null && isShown(target)) {
            final View target = findViewByAccessibilityId(accessibilityViewId);
            if (target != null) {
                if (action == R.id.accessibilityActionClickOnClickableSpan) {
                    // Handle this hidden action separately
                    succeeded = handleClickableSpanActionUiThread(
@@ -791,15 +756,13 @@ public final class AccessibilityInteractionController {
    }

    private View findViewByAccessibilityId(int accessibilityId) {
        View root = mViewRootImpl.mView;
        if (root == null) {
            return null;
        }
        View foundView = root.findViewByAccessibilityId(accessibilityId);
        if (foundView != null && !isShown(foundView)) {
            return null;
        if (accessibilityId == AccessibilityNodeInfo.ROOT_ITEM_ID) {
            return mViewRootImpl.mView;
        } else {
            final View foundView =
                    AccessibilityNodeIdManager.getInstance().findView(accessibilityId);
            return isShown(foundView) ? foundView : null;
        }
        return foundView;
    }

    private void applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos,
+4 −18
Original line number Diff line number Diff line
@@ -102,6 +102,7 @@ import android.view.WindowInsetsAnimationListener.InsetsAnimation;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityEventSource;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeIdManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.AccessibilityNodeProvider;
@@ -18910,6 +18911,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        jumpDrawablesToCurrentState();
        AccessibilityNodeIdManager.getInstance().registerViewWithId(this, getAccessibilityViewId());
        resetSubtreeAccessibilityStateChanged();
        // rebuild, since Outline not maintained while View is detached
@@ -19302,6 +19304,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        if ((mViewFlags & TOOLTIP) == TOOLTIP) {
            hideTooltip();
        }
        AccessibilityNodeIdManager.getInstance().unregisterViewWithId(getAccessibilityViewId());
    }
    private void cleanupDraw() {
@@ -23814,24 +23818,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        return view;
    }
    /**
     * Finds a view by its unuque and stable accessibility id.
     *
     * @param accessibilityId The searched accessibility id.
     * @return The found view.
     */
    @UnsupportedAppUsage
    final <T extends View> T findViewByAccessibilityId(int accessibilityId) {
        if (accessibilityId < 0) {
            return null;
        }
        T view = findViewByAccessibilityIdTraversal(accessibilityId);
        if (view != null) {
            return view.includeForAccessibility() ? view : null;
        }
        return null;
    }
    /**
     * Performs the traversal to find a view by its unique and stable accessibility id.
     *
+12 −14
Original line number Diff line number Diff line
@@ -95,6 +95,7 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
import android.view.accessibility.AccessibilityManager.HighTextContrastChangeListener;
import android.view.accessibility.AccessibilityNodeIdManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.AccessibilityNodeProvider;
@@ -7901,17 +7902,14 @@ public final class ViewRootImpl implements ViewParent,
        // Intercept accessibility focus events fired by virtual nodes to keep
        // track of accessibility focus position in such nodes.
        final int eventType = event.getEventType();
        final View source = getSourceForAccessibilityEvent(event);
        switch (eventType) {
            case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
                final long sourceNodeId = event.getSourceNodeId();
                final int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
                        sourceNodeId);
                View source = mView.findViewByAccessibilityId(accessibilityViewId);
                if (source != null) {
                    AccessibilityNodeProvider provider = source.getAccessibilityNodeProvider();
                    if (provider != null) {
                        final int virtualNodeId = AccessibilityNodeInfo.getVirtualDescendantId(
                                sourceNodeId);
                                event.getSourceNodeId());
                        final AccessibilityNodeInfo node;
                        node = provider.createAccessibilityNodeInfo(virtualNodeId);
                        setAccessibilityFocus(source, node);
@@ -7919,16 +7917,9 @@ public final class ViewRootImpl implements ViewParent,
                }
            } break;
            case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
                final long sourceNodeId = event.getSourceNodeId();
                final int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
                        sourceNodeId);
                View source = mView.findViewByAccessibilityId(accessibilityViewId);
                if (source != null) {
                    AccessibilityNodeProvider provider = source.getAccessibilityNodeProvider();
                    if (provider != null) {
                if (source != null && source.getAccessibilityNodeProvider() != null) {
                    setAccessibilityFocus(null, null);
                }
                }
            } break;


@@ -7940,6 +7931,13 @@ public final class ViewRootImpl implements ViewParent,
        return true;
    }

    private View getSourceForAccessibilityEvent(AccessibilityEvent event) {
        final long sourceNodeId = event.getSourceNodeId();
        final int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
                sourceNodeId);
        return AccessibilityNodeIdManager.getInstance().findView(accessibilityViewId);
    }

    /**
     * Updates the focused virtual view, when necessary, in response to a
     * content changed event.
+68 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.util.SparseArray;
import android.view.View;

/** @hide */
public final class AccessibilityNodeIdManager {
    private SparseArray<View> mIdsToViews = new SparseArray<>();
    private static AccessibilityNodeIdManager sIdManager;

    /**
     * Gets singleton.
     * @return The instance.
     */
    public static synchronized AccessibilityNodeIdManager getInstance() {
        if (sIdManager == null) {
            sIdManager = new AccessibilityNodeIdManager();
        }
        return sIdManager;
    }

    private AccessibilityNodeIdManager() {
    }

    /**
     * Register view to be kept track of by the accessibility system.
     * Must be paired with unregisterView, otherwise this will leak.
     * @param view The view to be registered.
     * @param id The accessibilityViewId of the view.
     */
    public void registerViewWithId(View view, int id) {
        mIdsToViews.append(id, view);
    }

    /**
     * Unregister view, accessibility won't keep track of this view after this call.
     * @param id The id returned from registerView when the view as first associated.
     */
    public void unregisterViewWithId(int id) {
        mIdsToViews.remove(id);
    }

    /**
     * Accessibility uses this to find the view in the hierarchy.
     * @param id The accessibility view id.
     * @return The view.
     */
    public View findView(int id) {
        final View view = mIdsToViews.get(id);
        return view != null && view.includeForAccessibility() ? view : null;
    }
}
+0 −9
Original line number Diff line number Diff line
@@ -1601,15 +1601,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
        return false;
    }

    /** @hide */
    @Override
    public View findViewByAccessibilityIdTraversal(int accessibilityId) {
        if (accessibilityId == getAccessibilityViewId()) {
            return this;
        }
        return super.findViewByAccessibilityIdTraversal(accessibilityId);
    }

    /**
     * Indicates whether the children's drawing cache is used during a scroll.
     * By default, the drawing cache is enabled but this will consume more memory.
Loading