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

Commit 8c4e97db authored by Svetoslav's avatar Svetoslav Committed by Svetoslav Ganov
Browse files

Enhance computation of click point for accessibility.

In explore by touch mode the user performs a double tap to click on
an item. In this case the system sends down and up events at the
location of accessibility focus. The accessibility focused view may
be partially covered. In order to click in this view we compute a
point where to send the down and up events. This clicking strategy
is a bridge-gap and we will switch to accessibility actions in the
future.

When computing the point to click we were taking into account whether
the view was covered by a clickable sibling or a clickable sibling of
a predecessor. Despite our expectation cases in which this is not
enough happen in practice. In particular, the focused view may be
covered by a clickable descendant of a non-clickable sibling of a
predecessor that covers the focused view. This change takes care
of handling this case. Note that computing the click point is a fair
amount of work but this happens very rarely and on demand. Also the
code is short circuiting where possible.

Change-Id: I4d3cd8b67a7baf0bcc12f370ea7ba1b04c42c355
parent bec0c53a
Loading
Loading
Loading
Loading
+15 −0
Original line number Original line Diff line number Diff line
@@ -5877,6 +5877,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        return true;
        return true;
    }
    }
    /**
     * Adds the clickable rectangles withing the bounds of this view. They
     * may overlap. This method is intended for use only by the accessibility
     * layer.
     *
     * @param outRects List to which to add clickable areas.
     */
    void addClickableRectsForAccessibility(List<RectF> outRects) {
        if (isClickable() || isLongClickable()) {
            RectF bounds = new RectF();
            bounds.set(0, 0, getWidth(), getHeight());
            outRects.add(bounds);
        }
    }
    static void offsetRects(List<RectF> rects, float offsetX, float offsetY) {
    static void offsetRects(List<RectF> rects, float offsetX, float offsetY) {
        final int rectCount = rects.size();
        final int rectCount = rects.size();
        for (int i = 0; i < rectCount; i++) {
        for (int i = 0; i < rectCount; i++) {
+174 −36
Original line number Original line Diff line number Diff line
@@ -51,8 +51,10 @@ import com.android.internal.util.Predicate;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Collections;
import java.util.HashSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.List;
import java.util.Map;
import java.util.Map;
import java.util.NoSuchElementException;


import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;


@@ -468,6 +470,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
    @ViewDebug.ExportedProperty(category = "layout")
    @ViewDebug.ExportedProperty(category = "layout")
    private int mChildCountWithTransientState = 0;
    private int mChildCountWithTransientState = 0;


    // Iterator over the children in decreasing Z order (top children first).
    private OrderedChildIterator mOrderedChildIterator;

    /**
    /**
     * Currently registered axes for nested scrolling. Flag set consisting of
     * Currently registered axes for nested scrolling. Flag set consisting of
     * {@link #SCROLL_AXIS_HORIZONTAL} {@link #SCROLL_AXIS_VERTICAL} or {@link #SCROLL_AXIS_NONE}
     * {@link #SCROLL_AXIS_HORIZONTAL} {@link #SCROLL_AXIS_VERTICAL} or {@link #SCROLL_AXIS_NONE}
@@ -817,19 +822,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
            return false;
            return false;
        }
        }


        // Check whether any clickable siblings cover the child
        Iterator<View> iterator = obtainOrderedChildIterator();
        // view and if so keep track of the intersections. Also
        while (iterator.hasNext()) {
        // respect Z ordering when iterating over children.
            View sibling = iterator.next();
        ArrayList<View> orderedList = buildOrderedChildList();
        final boolean useCustomOrder = orderedList == null
                && isChildrenDrawingOrderEnabled();

        final int childCount = mChildrenCount;
        for (int i = childCount - 1; i >= 0; i--) {
            final int childIndex = useCustomOrder
                    ? getChildDrawingOrder(childCount, i) : i;
            final View sibling = (orderedList == null)
                    ? mChildren[childIndex] : orderedList.get(childIndex);


            // We care only about siblings over the child.
            // We care only about siblings over the child.
            if (sibling == child) {
            if (sibling == child) {
@@ -837,12 +832,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
            }
            }


            // Ignore invisible views as they are not interactive.
            // Ignore invisible views as they are not interactive.
            if (sibling.getVisibility() != View.VISIBLE) {
            if (!isVisible(sibling)) {
                continue;
            }

            // If sibling is not interactive we do not care.
            if (!sibling.isClickable() && !sibling.isLongClickable()) {
                continue;
                continue;
            }
            }


@@ -850,29 +840,36 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
            RectF siblingBounds = mAttachInfo.mTmpTransformRect1;
            RectF siblingBounds = mAttachInfo.mTmpTransformRect1;
            siblingBounds.set(0, 0, sibling.getWidth(), sibling.getHeight());
            siblingBounds.set(0, 0, sibling.getWidth(), sibling.getHeight());


            // Take into account the sibling transformation matrix.
            // Translate the sibling bounds to our coordinates.
            if (!sibling.hasIdentityMatrix()) {
            offsetChildRectToMyCoords(siblingBounds, sibling);
                sibling.getMatrix().mapRect(siblingBounds);
            }

            // Offset the sibling to our coordinates.
            final int siblingDx = sibling.mLeft - mScrollX;
            final int siblingDy = sibling.mTop - mScrollY;
            siblingBounds.offset(siblingDx, siblingDy);


            // Compute the intersection between the child and the sibling.
            // Compute the intersection between the child and the sibling.
            if (siblingBounds.intersect(bounds)) {
            if (siblingBounds.intersect(bounds)) {
                // If an interactive sibling completely covers the child, done.
                List<RectF> clickableRects = new ArrayList<>();
                if (siblingBounds.equals(bounds)) {
                sibling.addClickableRectsForAccessibility(clickableRects);
                    if (orderedList != null) orderedList.clear();

                final int clickableRectCount = clickableRects.size();
                for (int j = 0; j < clickableRectCount; j++) {
                    RectF clickableRect = clickableRects.get(j);

                    // Translate the clickable rect to our coordinates.
                    offsetChildRectToMyCoords(clickableRect, sibling);

                    // Compute the intersection between the child and the clickable rects.
                    if (clickableRect.intersect(bounds)) {
                        // If a clickable rect completely covers the child, done.
                        if (clickableRect.equals(bounds)) {
                            releaseOrderedChildIterator();
                            return false;
                            return false;
                        }
                        }
                        // Keep track of the intersection rectangle.
                        // Keep track of the intersection rectangle.
                RectF intersection = new RectF(siblingBounds);
                        intersections.add(clickableRect);
                intersections.add(intersection);
                    }
                }
                }
            }
            }
        if (orderedList != null) orderedList.clear();
        }

        releaseOrderedChildIterator();


        if (mParent instanceof ViewGroup) {
        if (mParent instanceof ViewGroup) {
            ViewGroup parentGroup = (ViewGroup) mParent;
            ViewGroup parentGroup = (ViewGroup) mParent;
@@ -883,6 +880,94 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
        return true;
        return true;
    }
    }


    @Override
    void addClickableRectsForAccessibility(List<RectF> outRects) {
        int sizeBefore = outRects.size();

        super.addClickableRectsForAccessibility(outRects);

        // If we added ourselves, then no need to visit children.
        if (outRects.size() > sizeBefore) {
            return;
        }

        Iterator<View> iterator = obtainOrderedChildIterator();
        while (iterator.hasNext()) {
            View child = iterator.next();

            // Cannot click on an invisible view.
            if (!isVisible(child)) {
                continue;
            }

            sizeBefore = outRects.size();

            // Add clickable rects in the child bounds.
            child.addClickableRectsForAccessibility(outRects);

            // Offset the clickable rects for out children to our coordinates.
            final int sizeAfter = outRects.size();
            for (int j = sizeBefore; j < sizeAfter; j++) {
                RectF rect = outRects.get(j);

                // Translate the clickable rect to our coordinates.
                offsetChildRectToMyCoords(rect, child);

                // If a clickable rect fills the parent, done.
                if ((int) rect.left == 0 && (int) rect.top == 0
                        && (int) rect.right == mRight && (int) rect.bottom == mBottom) {
                    releaseOrderedChildIterator();
                    return;
                }
            }
        }

        releaseOrderedChildIterator();
    }

    private void offsetChildRectToMyCoords(RectF rect, View child) {
        if (!child.hasIdentityMatrix()) {
            child.getMatrix().mapRect(rect);
        }
        final int childDx = child.mLeft - mScrollX;
        final int childDy = child.mTop - mScrollY;
        rect.offset(childDx, childDy);
    }

    private static boolean isVisible(View view) {
        return (view.getAlpha() > 0 && view.getTransitionAlpha() > 0 &&
                view.getVisibility() == VISIBLE);
    }

    /**
     * Obtains the iterator to traverse the children in a descending Z order.
     * Only one party can use the iterator at any given time and you cannot
     * modify the children while using this iterator. Acquisition if already
     * obtained is an error.
     *
     * @return The child iterator.
     */
    OrderedChildIterator obtainOrderedChildIterator() {
        if (mOrderedChildIterator == null) {
            mOrderedChildIterator = new OrderedChildIterator();
        } else if (mOrderedChildIterator.isInitialized()) {
            throw new IllegalStateException("Already obtained");
        }
        mOrderedChildIterator.initialize();
        return mOrderedChildIterator;
    }

    /**
     * Releases the iterator to traverse the children in a descending Z order.
     * Release if not obtained is an error.
     */
    void releaseOrderedChildIterator() {
        if (mOrderedChildIterator == null || !mOrderedChildIterator.isInitialized()) {
            throw new IllegalStateException("Not obtained");
        }
        mOrderedChildIterator.release();
    }

    /**
    /**
     * Called when a child view has changed whether or not it is tracking transient state.
     * Called when a child view has changed whether or not it is tracking transient state.
     */
     */
@@ -7295,4 +7380,57 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager


        canvas.drawLines(sDebugLines, paint);
        canvas.drawLines(sDebugLines, paint);
    }
    }

    private final class OrderedChildIterator implements Iterator<View> {
        private List<View> mOrderedChildList;
        private boolean mUseCustomOrder;
        private int mCurrentIndex;
        private boolean mInitialized;

        public void initialize() {
            mOrderedChildList = buildOrderedChildList();
            mUseCustomOrder = (mOrderedChildList == null)
                    && isChildrenDrawingOrderEnabled();
            mCurrentIndex = mChildrenCount - 1;
            mInitialized = true;
        }

        public void release() {
            if (mOrderedChildList != null) {
                mOrderedChildList.clear();
            }
            mUseCustomOrder = false;
            mCurrentIndex = 0;
            mInitialized = false;
        }

        public boolean isInitialized() {
            return mInitialized;
        }

        @Override
        public boolean hasNext() {
            return (mCurrentIndex >= 0);
        }

        @Override
        public View next() {
            if (!hasNext()) {
                throw new NoSuchElementException("No such element");
            }
            return getChild(mCurrentIndex--);
        }

        private View getChild(int index) {
            final int childIndex = mUseCustomOrder
                    ? getChildDrawingOrder(mChildrenCount, index) : index;
            return (mOrderedChildList == null)
                    ? mChildren[childIndex] : mOrderedChildList.get(childIndex);
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }
}
}