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

Commit db3cf0dd authored by Svetoslav Ganov's avatar Svetoslav Ganov Committed by Android Git Automerger
Browse files

am 0e29ac9e: Merge "Accessibility focus traversal in virtual nodes." into jb-dev

* commit '0e29ac9e':
  Accessibility focus traversal in virtual nodes.
parents d24286a1 0e29ac9e
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.