com.android.utils.traversal.ReorderedChildrenIterator.java Source code

Java tutorial

Introduction

Here is the source code for com.android.utils.traversal.ReorderedChildrenIterator.java

Source

/*
 * Copyright (C) 2015 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 com.android.utils.traversal;

import android.graphics.Rect;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import com.android.utils.AccessibilityNodeInfoUtils;
import com.android.utils.WebInterfaceUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

/**
 * Children nodes iterator that iterates its children according the order of AccessibilityNodeInfo
 * hierarchy. But for nodes that are not considered to be focused according to
 * AccessibilityNodeInfoUtils.shouldFocusNode() rules we calculate new bounds that is minimum
 * rectangle that contains all focusable children nodes. If that rectangle differs from
 * real node bounds that node is reordered according needSwapNodeOrder() logic and could be
 * traversed later.
 *
 * This class obtains new instances of AccessibilityNodeCompat. Call recycle to recycle those
 * instances. Do not use the iterator once it's been recycled.
 */
public class ReorderedChildrenIterator implements Iterator<AccessibilityNodeInfoCompat> {

    public static ReorderedChildrenIterator createAscendingIterator(AccessibilityNodeInfoCompat parent) {
        return createAscendingIterator(parent, null);
    }

    public static ReorderedChildrenIterator createDescendingIterator(AccessibilityNodeInfoCompat parent) {
        return createDescendingIterator(parent, null);
    }

    public static ReorderedChildrenIterator createAscendingIterator(AccessibilityNodeInfoCompat parent,
            NodeCachedBoundsCalculator boundsCalculator) {
        if (parent == null) {
            return null;
        }

        return new ReorderedChildrenIterator(parent, true, boundsCalculator);
    }

    public static ReorderedChildrenIterator createDescendingIterator(AccessibilityNodeInfoCompat parent,
            NodeCachedBoundsCalculator boundsCalculator) {
        if (parent == null) {
            return null;
        }

        return new ReorderedChildrenIterator(parent, false, boundsCalculator);
    }

    private AccessibilityNodeInfoCompat mParent;
    private int mCurrentIndex;
    private List<AccessibilityNodeInfoCompat> mNodes;
    private boolean mIsAscending;
    private NodeCachedBoundsCalculator mBoundsCalculator;

    // Avoid constantly creating and discarding Rects.
    private Rect mTempLeftBounds = new Rect();
    private Rect mTempRightBounds = new Rect();

    private ReorderedChildrenIterator(AccessibilityNodeInfoCompat parent, boolean isAscending,
            NodeCachedBoundsCalculator boundsCalculator) {
        mParent = parent;
        mIsAscending = isAscending;
        mBoundsCalculator = boundsCalculator;
        if (boundsCalculator == null) {
            mBoundsCalculator = new NodeCachedBoundsCalculator();
        }

        mNodes = new ArrayList<>(mParent.getChildCount());
        init(mParent);
        mCurrentIndex = mIsAscending ? 0 : mNodes.size() - 1;
    }

    private void init(AccessibilityNodeInfoCompat node) {
        fillNodesFromParent();
        if (!WebInterfaceUtils.isWebContainer(node) && needReordering(mNodes)) {
            reorder(mNodes);
        }
    }

    private boolean needReordering(List<AccessibilityNodeInfoCompat> nodes) {
        if (nodes == null || nodes.size() == 1) {
            return false;
        }

        for (AccessibilityNodeInfoCompat node : nodes) {
            if (mBoundsCalculator.usesChildrenBounds(node)) {
                return true;
            }
        }

        return false;
    }

    private void reorder(List<AccessibilityNodeInfoCompat> nodes) {
        if (nodes == null || nodes.size() == 1) {
            return;
        }

        int size = nodes.size();
        AccessibilityNodeInfoCompat[] nodeArray = new AccessibilityNodeInfoCompat[size];
        nodes.toArray(nodeArray);

        int currentIndex = size - 2;
        while (currentIndex >= 0) {
            AccessibilityNodeInfoCompat currentNode = nodeArray[currentIndex];
            if (mBoundsCalculator.usesChildrenBounds(currentNode)) {
                moveNodeIfNecessary(nodeArray, currentIndex);
            }

            currentIndex--;
        }

        nodes.clear();
        nodes.addAll(Arrays.asList(nodeArray));
    }

    private void moveNodeIfNecessary(AccessibilityNodeInfoCompat[] nodeArray, int index) {
        int size = nodeArray.length;
        int nextIndex = index + 1;
        AccessibilityNodeInfoCompat currentNode = nodeArray[index];
        while (nextIndex < size && needSwapNodeOrder(currentNode, nodeArray[nextIndex])) {
            nodeArray[nextIndex - 1] = nodeArray[nextIndex];
            nodeArray[nextIndex] = currentNode;
            nextIndex++;
        }
    }

    private boolean needSwapNodeOrder(AccessibilityNodeInfoCompat leftNode, AccessibilityNodeInfoCompat rightNode) {
        if (leftNode == null || rightNode == null) {
            return false;
        }

        Rect leftBounds = mBoundsCalculator.getBounds(leftNode);
        Rect rightBounds = mBoundsCalculator.getBounds(rightNode);

        // Sometimes the bounds compare() is overzealous, so swap the items only if the adjusted
        // (mBoundsCalculator) leftBounds > rightBounds but the original leftBounds < rightBounds,
        // i.e. the compare() method returns the existing ordering for the original bounds but
        // wants a swap for the adjusted bounds.
        // Simply, if compare() says that the original system ordering is wrong, then we cannot
        // trust its judgment in the adjusted bounds case.
        //
        // Example:
        // (1) Page scrolled to top  (2) Page scrolled to bottom.
        // +----------+              +----------+
        // | App bar  |              | App bar  |
        // +----------+              +----------+
        // | Item 1   |              | Item 2   |
        // | Item 2   |              | Item 3   |
        // | Item 3   |              | (spacer) |
        // +----------+              +----------+
        // Note: App bar overlays the top part of the list; the top, left, and right edges of the
        // list line up with the app bar. Assume that the spacer is not important for accessibility.
        // In this example, the traversal order for (1) is Item 1 -> Item 2 -> Item 3 -> App bar
        // but the traversal order for (2) gets reordered to App bar -> Item 2 -> Item 3.
        // So during auto-scrolling the app bar is actually excluded from the traversal order until
        // after the wrap-around.
        if (compare(leftBounds, rightBounds)) {
            leftNode.getBoundsInScreen(mTempLeftBounds);
            rightNode.getBoundsInScreen(mTempRightBounds);
            return !compare(mTempLeftBounds, mTempRightBounds);
        }

        return false;
    }

    // Returns true if leftBounds > rightBounds in the traversal order and false otherwise.
    private boolean compare(Rect leftBounds, Rect rightBounds) {
        if (leftBounds == null || rightBounds == null) {
            return true;
        }

        if (leftBounds.top != rightBounds.top) {
            return leftBounds.top > rightBounds.top;
        }

        if (leftBounds.left != rightBounds.left) {
            return leftBounds.left > rightBounds.left;
        }

        if (leftBounds.right != rightBounds.right) {
            return leftBounds.right > rightBounds.right;
        }

        return leftBounds.bottom > rightBounds.bottom;
    }

    public void recycle() {
        AccessibilityNodeInfoUtils.recycleNodes(mNodes);
        mNodes = null;
    }

    private void fillNodesFromParent() {
        int count = mParent.getChildCount();
        for (int i = 0; i < count; i++) {
            AccessibilityNodeInfoCompat node = mParent.getChild(i);
            if (node != null) {
                mNodes.add(node);
            }
        }
    }

    @Override
    public boolean hasNext() {
        return mIsAscending ? mCurrentIndex < mNodes.size() : mCurrentIndex >= 0;
    }

    @Override
    public AccessibilityNodeInfoCompat next() {
        AccessibilityNodeInfoCompat nextNode = mNodes.get(mCurrentIndex);
        if (mIsAscending) {
            mCurrentIndex++;
        } else {
            mCurrentIndex--;
        }

        return nextNode != null ? AccessibilityNodeInfoCompat.obtain(nextNode) : null;
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException("ReorderedChildrenIterator does not support remove operation");
    }
}