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

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

Merge "Accessibility focus traversal in virtual nodes." into jb-dev

parents 66757217 791fd31a
Loading
Loading
Loading
Loading
+22 −14
Original line number Diff line number Diff line
@@ -491,20 +491,28 @@ final class AccessibilityInteractionController {
                if ((direction & View.FOCUS_ACCESSIBILITY) ==  View.FOCUS_ACCESSIBILITY) {
                    AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
                    if (provider != null) {
                        next = provider.accessibilityFocusSearch(direction,
                                virtualDescendantId);
                    } else if (virtualDescendantId == View.NO_ID) {
                        next = provider.accessibilityFocusSearch(direction, virtualDescendantId);
                        if (next != null) {
                            return;
                        }
                    }
                    View nextView = root.focusSearch(direction);
                        if (nextView != null) {
                    while (nextView != null) {
                        // If the focus search reached a node with a provider
                        // we delegate to the provider to find the next one.
                        // If the provider does not return a virtual view to
                        // take accessibility focus we try the next view found
                        // by the focus search algorithm.
                        provider = nextView.getAccessibilityNodeProvider();
                        if (provider != null) {
                                next = provider.accessibilityFocusSearch(direction,
                                        virtualDescendantId);
                            next = provider.accessibilityFocusSearch(direction, View.NO_ID);
                            if (next != null) {
                                break;
                            }
                            nextView = nextView.focusSearch(direction);
                        } else {
                            next = nextView.createAccessibilityNodeInfo();
                             }
                            break;
                        }
                    }
                } else {
+8 −5
Original line number Diff line number Diff line
@@ -6027,7 +6027,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
            return;
        }
        if ((focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) {
            if (canTakeAccessibilityFocusFromHover()) {
            if (canTakeAccessibilityFocusFromHover() || getAccessibilityNodeProvider() != null) {
                views.add(this);
                return;
            }
@@ -6156,12 +6156,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
     * @hide
     */
    public void clearAccessibilityFocus() {
        if ((mPrivateFlags2 & ACCESSIBILITY_FOCUSED) != 0) {
            mPrivateFlags2 &= ~ACCESSIBILITY_FOCUSED;
        ViewRootImpl viewRootImpl = getViewRootImpl();
        if (viewRootImpl != null) {
            View focusHost = viewRootImpl.getAccessibilityFocusedHost();
            if (focusHost != null && ViewRootImpl.isViewDescendantOf(focusHost, this)) {
                viewRootImpl.setAccessibilityFocusedHost(null);
            }
        }
        if ((mPrivateFlags2 & ACCESSIBILITY_FOCUSED) != 0) {
            mPrivateFlags2 &= ~ACCESSIBILITY_FOCUSED;
            invalidate();
            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
            notifyAccessibilityStateChanged();
+74 −5
Original line number Diff line number Diff line
@@ -489,6 +489,8 @@ public final class ViewRootImpl implements ViewParent,
                mWindowAttributes.copyFrom(attrs);
                attrs = mWindowAttributes;

                setAccessibilityFocusedHost(null);

                if (view instanceof RootViewSurfaceTaker) {
                    mSurfaceHolderCallback =
                            ((RootViewSurfaceTaker)view).willYouTakeTheSurface();
@@ -556,6 +558,7 @@ public final class ViewRootImpl implements ViewParent,
                    mInputChannel = null;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocusedHost(null);
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
@@ -575,6 +578,7 @@ public final class ViewRootImpl implements ViewParent,
                    mAdded = false;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocusedHost(null);
                    switch (res) {
                        case WindowManagerImpl.ADD_BAD_APP_TOKEN:
                        case WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN:
@@ -635,8 +639,6 @@ public final class ViewRootImpl implements ViewParent,
                if (view.getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
                    view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
                }

                setAccessibilityFocusedHost(null);
            }
        }
    }
@@ -2543,11 +2545,51 @@ public final class ViewRootImpl implements ViewParent,
    }

    void setAccessibilityFocusedHost(View host) {
        if (mAccessibilityFocusedHost != null && mAccessibilityFocusedVirtualView == null) {
        // If we have a virtual view with accessibility focus we need
        // to clear the focus and invalidate the virtual view bounds.
        if (mAccessibilityFocusedVirtualView != null) {

            AccessibilityNodeInfo focusNode = mAccessibilityFocusedVirtualView;
            View focusHost = mAccessibilityFocusedHost;
            focusHost.clearAccessibilityFocusNoCallbacks();

            // Wipe the state of the current accessibility focus since
            // the call into the provider to clear accessibility focus
            // will fire an accessibility event which will end up calling
            // this method and we want to have clean state when this
            // invocation happens.
            mAccessibilityFocusedHost = null;
            mAccessibilityFocusedVirtualView = null;

            AccessibilityNodeProvider provider = focusHost.getAccessibilityNodeProvider();
            if (provider != null) {
                // Invalidate the area of the cleared accessibility focus.
                focusNode.getBoundsInParent(mTempRect);
                focusHost.invalidate(mTempRect);
                // Clear accessibility focus in the virtual node.
                final int virtualNodeId = AccessibilityNodeInfo.getVirtualDescendantId(
                        focusNode.getSourceNodeId());
                provider.performAction(virtualNodeId,
                        AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
            }
        }
        if (mAccessibilityFocusedHost != null) {
            // Clear accessibility focus in the view.
            mAccessibilityFocusedHost.clearAccessibilityFocusNoCallbacks();
        }

        // Set the new focus host.
        mAccessibilityFocusedHost = host;
        mAccessibilityFocusedVirtualView = null;

        // If the host has a provide find the virtual descendant that has focus.
        if (mAccessibilityFocusedHost != null) {
            AccessibilityNodeProvider provider =
                mAccessibilityFocusedHost.getAccessibilityNodeProvider();
            if (provider != null) {
                mAccessibilityFocusedVirtualView = provider.findAccessibilityFocus(View.NO_ID);
                return;
            }
        }
    }

    public void requestChildFocus(View child, View focused) {
@@ -2633,6 +2675,8 @@ public final class ViewRootImpl implements ViewParent,

        destroyHardwareRenderer();

        setAccessibilityFocusedHost(null);

        mView = null;
        mAttachInfo.mRootView = null;
        mAttachInfo.mSurface = null;
@@ -4608,6 +4652,31 @@ public final class ViewRootImpl implements ViewParent,
        if (mView == null) {
            return false;
        }
        // Watch for accessibility focus change events from virtual nodes
        // to keep track of accessibility focus being on a virtual node.
        final int eventType = event.getEventType();
        switch (eventType) {
            case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
                final long sourceId = event.getSourceNodeId();
                // If the event is not from a virtual node we are not interested.
                final int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId(sourceId);
                if (virtualViewId == AccessibilityNodeInfo.UNDEFINED) {
                    break;
                }
                final int realViewId = AccessibilityNodeInfo.getAccessibilityViewId(sourceId);
                View focusHost = mView.findViewByAccessibilityId(realViewId);
                setAccessibilityFocusedHost(focusHost);
            } break;
            case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
                final long sourceId = event.getSourceNodeId();
                // If the event is not from a virtual node we are not interested.
                final int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId(sourceId);
                if (virtualViewId == AccessibilityNodeInfo.UNDEFINED) {
                    break;
                }
                setAccessibilityFocusedHost(null);
            } break;
        }
        mAccessibilityManager.sendAccessibilityEvent(event);
        return true;
    }
+242 −12
Original line number Diff line number Diff line
@@ -950,6 +950,8 @@ public class NumberPicker extends LinearLayout {
                    provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId,
                            AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
                    mLastHoveredChildVirtualViewId = hoveredVirtualViewId;
                    provider.performAction(hoveredVirtualViewId,
                            AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
                } break;
                case MotionEvent.ACTION_HOVER_MOVE: {
                    if (mLastHoveredChildVirtualViewId != hoveredVirtualViewId
@@ -960,6 +962,8 @@ public class NumberPicker extends LinearLayout {
                        provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId,
                                AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
                        mLastHoveredChildVirtualViewId = hoveredVirtualViewId;
                        provider.performAction(hoveredVirtualViewId,
                                AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
                    }
                } break;
                case MotionEvent.ACTION_HOVER_EXIT: {
@@ -1413,9 +1417,16 @@ public class NumberPicker extends LinearLayout {
    }

    @Override
    public void sendAccessibilityEvent(int eventType) {
        // Do not send accessibility events - we want the user to
        // perceive this widget as several controls rather as a whole.
    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
        // We do not want the real descendant to be considered focus search
        // since it is managed by the accessibility node provider.
        if ((focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) {
            if (canTakeAccessibilityFocusFromHover() || getAccessibilityNodeProvider() != null) {
                views.add(this);
                return;
            }
        }
        super.addFocusables(views, direction, focusableMode);
    }

    @Override
@@ -2072,7 +2083,12 @@ public class NumberPicker extends LinearLayout {
        }
    }

    /**
     * Class for managing virtual view tree rooted at this picker.
     */
    class AccessibilityNodeProviderImpl extends AccessibilityNodeProvider {
        private static final int UNDEFINED = Integer.MIN_VALUE;

        private static final int VIRTUAL_VIEW_ID_INCREMENT = 1;

        private static final int VIRTUAL_VIEW_ID_INPUT = 2;
@@ -2083,6 +2099,8 @@ public class NumberPicker extends LinearLayout {

        private final int[] mTempArray = new int[2];

        private int mAccessibilityFocusedView = UNDEFINED;

        @Override
        public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
            switch (virtualViewId) {
@@ -2137,6 +2155,25 @@ public class NumberPicker extends LinearLayout {
        @Override
        public boolean performAction(int virtualViewId, int action, Bundle arguments) {
            switch (virtualViewId) {
                case View.NO_ID: {
                    switch (action) {
                        case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
                            if (mAccessibilityFocusedView != virtualViewId) {
                                mAccessibilityFocusedView = virtualViewId;
                                requestAccessibilityFocus();
                                return true;
                            }
                        } return false;
                        case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
                            if (mAccessibilityFocusedView == virtualViewId) {
                                mAccessibilityFocusedView = UNDEFINED;
                                clearAccessibilityFocus();
                                return true;
                            }
                            return false;
                        }
                    }
                } break;
                case VIRTUAL_VIEW_ID_INPUT: {
                    switch (action) {
                        case AccessibilityNodeInfo.ACTION_FOCUS: {
@@ -2149,25 +2186,182 @@ public class NumberPicker extends LinearLayout {
                                mInputText.clearFocus();
                                return true;
                            }
                            return false;
                        }
                        case AccessibilityNodeInfo.ACTION_CLICK: {
                            showSoftInput();
                            return true;
                        }
                        case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
                            if (mAccessibilityFocusedView != virtualViewId) {
                                mAccessibilityFocusedView = virtualViewId;
                                sendAccessibilityEventForVirtualView(virtualViewId,
                                        AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
                                mInputText.invalidate();
                                return true;
                            }
                        } return false;
                        case  AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
                            if (mAccessibilityFocusedView == virtualViewId) {
                                mAccessibilityFocusedView = UNDEFINED;
                                sendAccessibilityEventForVirtualView(virtualViewId,
                                        AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
                                mInputText.invalidate();
                                return true;
                            }
                        } return false;
                        default: {
                            return mInputText.performAccessibilityAction(action, arguments);
                        }
                    }
                } return false;
                case VIRTUAL_VIEW_ID_INCREMENT: {
                    switch (action) {
                        case AccessibilityNodeInfo.ACTION_CLICK: {
                            NumberPicker.this.changeValueByOne(true);
                            sendAccessibilityEventForVirtualView(virtualViewId,
                                    AccessibilityEvent.TYPE_VIEW_CLICKED);
                        } return true;
                        case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
                            if (mAccessibilityFocusedView != virtualViewId) {
                                mAccessibilityFocusedView = virtualViewId;
                                sendAccessibilityEventForVirtualView(virtualViewId,
                                        AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
                                invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
                                return true;
                            }
                        } return false;
                        case  AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
                            if (mAccessibilityFocusedView == virtualViewId) {
                                mAccessibilityFocusedView = UNDEFINED;
                                sendAccessibilityEventForVirtualView(virtualViewId,
                                        AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
                                invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
                                return true;
                            }
                        } return false;
                    }
                } return false;
                case VIRTUAL_VIEW_ID_DECREMENT: {
                    switch (action) {
                        case AccessibilityNodeInfo.ACTION_CLICK: {
                            final boolean increment = (virtualViewId == VIRTUAL_VIEW_ID_INCREMENT);
                            NumberPicker.this.changeValueByOne(increment);
                            sendAccessibilityEventForVirtualView(virtualViewId,
                                    AccessibilityEvent.TYPE_VIEW_CLICKED);
                        } return true;
                        case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
                            if (mAccessibilityFocusedView != virtualViewId) {
                                mAccessibilityFocusedView = virtualViewId;
                                sendAccessibilityEventForVirtualView(virtualViewId,
                                        AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
                                invalidate(0, 0, mRight, mTopSelectionDividerTop);
                                return true;
                            }
                        } return false;
                        case  AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
                            if (mAccessibilityFocusedView == virtualViewId) {
                                mAccessibilityFocusedView = UNDEFINED;
                                sendAccessibilityEventForVirtualView(virtualViewId,
                                        AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
                                invalidate(0, 0, mRight, mTopSelectionDividerTop);
                                return true;
                            }
                        } return false;
                    }
                } return false;
            }
            return super.performAction(virtualViewId, action, arguments);
        }

        @Override
        public AccessibilityNodeInfo findAccessibilityFocus(int virtualViewId) {
            return createAccessibilityNodeInfo(mAccessibilityFocusedView);
        }

        @Override
        public AccessibilityNodeInfo accessibilityFocusSearch(int direction, int virtualViewId) {
            switch (direction) {
                case View.ACCESSIBILITY_FOCUS_DOWN:
                case View.ACCESSIBILITY_FOCUS_FORWARD: {
                    switch (mAccessibilityFocusedView) {
                        case UNDEFINED: {
                            return createAccessibilityNodeInfo(View.NO_ID);
                        }
                        case View.NO_ID: {
                            if (hasVirtualDecrementButton()) {
                                return createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_DECREMENT);
                            }
                        }
                        //$FALL-THROUGH$
                        case VIRTUAL_VIEW_ID_DECREMENT: {
                            return createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT);
                        }
                        case VIRTUAL_VIEW_ID_INPUT: {
                            if (hasVirtualIncrementButton()) {
                                return createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INCREMENT);
                            }
                        }
                        //$FALL-THROUGH$
                        case VIRTUAL_VIEW_ID_INCREMENT: {
                            View nextFocus = NumberPicker.this.focusSearch(direction);
                            if (nextFocus != null) {
                                return nextFocus.createAccessibilityNodeInfo();
                            }
                            return null;
                        }
                    }
                } break;
                case View.ACCESSIBILITY_FOCUS_UP:
                case View.ACCESSIBILITY_FOCUS_BACKWARD: {
                    switch (mAccessibilityFocusedView) {
                        case UNDEFINED: {
                            return createAccessibilityNodeInfo(View.NO_ID);
                        }
                        case View.NO_ID: {
                            if (hasVirtualIncrementButton()) {
                                return createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INCREMENT);
                            }
                        }
                        //$FALL-THROUGH$
                        case VIRTUAL_VIEW_ID_INCREMENT: {
                            return createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT);
                        }
                        case VIRTUAL_VIEW_ID_INPUT: {
                            if (hasVirtualDecrementButton()) {
                                return createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_DECREMENT);
                            }
                        }
                        //$FALL-THROUGH$
                        case VIRTUAL_VIEW_ID_DECREMENT: {
                            View nextFocus = NumberPicker.this.focusSearch(direction);
                            if (nextFocus != null) {
                                return nextFocus.createAccessibilityNodeInfo();
                            }
                            return null;
                        }
                    }
                } break;
            }
            return super.performAction(virtualViewId, action, arguments);
            return null;
        }

        public void sendAccessibilityEventForVirtualView(int virtualViewId, int eventType) {
            switch (virtualViewId) {
                case VIRTUAL_VIEW_ID_DECREMENT: {
                    if (hasVirtualDecrementButton()) {
                        sendAccessibilityEventForVirtualButton(virtualViewId, eventType,
                                getVirtualDecrementButtonText());
                    }
                } break;
                case VIRTUAL_VIEW_ID_INPUT: {
                    sendAccessibilityEventForVirtualText(eventType);
                } break;
                case VIRTUAL_VIEW_ID_INCREMENT: {
                    if (hasVirtualIncrementButton()) {
                        sendAccessibilityEventForVirtualButton(virtualViewId, eventType,
                                getVirtualIncrementButtonText());
                    }
                } break;
            }
        }
@@ -2227,8 +2421,13 @@ public class NumberPicker extends LinearLayout {

        private AccessibilityNodeInfo createAccessibiltyNodeInfoForInputText() {
            AccessibilityNodeInfo info = mInputText.createAccessibilityNodeInfo();
            info.setLongClickable(true);
            info.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
            if (mAccessibilityFocusedView != VIRTUAL_VIEW_ID_INPUT) {
                info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
            }
            if (mAccessibilityFocusedView == VIRTUAL_VIEW_ID_INPUT) {
                info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
            }
            return info;
        }

@@ -2252,6 +2451,15 @@ public class NumberPicker extends LinearLayout {
            getLocationOnScreen(locationOnScreen);
            boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
            info.setBoundsInScreen(boundsInScreen);

            if (mAccessibilityFocusedView != virtualViewId) {
                info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
            }
            if (mAccessibilityFocusedView == virtualViewId) {
                info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
            }
            info.addAction(AccessibilityNodeInfo.ACTION_CLICK);

            return info;
        }

@@ -2261,9 +2469,15 @@ public class NumberPicker extends LinearLayout {
            info.setClassName(NumberPicker.class.getName());
            info.setPackageName(mContext.getPackageName());
            info.setSource(NumberPicker.this);

            if (hasVirtualDecrementButton()) {
                info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_DECREMENT);
            }
            info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
            if (hasVirtualIncrementButton()) {
                info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_INCREMENT);
            }

            info.setParent((View) getParent());
            info.setEnabled(NumberPicker.this.isEnabled());
            info.setScrollable(true);
@@ -2276,9 +2490,25 @@ public class NumberPicker extends LinearLayout {
            getLocationOnScreen(locationOnScreen);
            boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
            info.setBoundsInScreen(boundsInScreen);

            if (mAccessibilityFocusedView != View.NO_ID) {
                info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
            }
            if (mAccessibilityFocusedView == View.NO_ID) {
                info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
            }

            return info;
        }

        private boolean hasVirtualDecrementButton() {
            return getWrapSelectorWheel() || getValue() > getMinValue();
        }

        private boolean hasVirtualIncrementButton() {
            return getWrapSelectorWheel() || getValue() < getMaxValue();
        }

        private String getVirtualDecrementButtonText() {
            int value = mValue - 1;
            if (mWrapSelectorWheel) {
+1 −0

File changed.

Preview size limit exceeded, changes collapsed.