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

Commit a5082a3f authored by Evan Rosky's avatar Evan Rosky Committed by Android (Google) Code Review
Browse files

Merge "Default focus order now tries to adhere to view hierarchy"

parents 3f57d7f7 3b94bf5c
Loading
Loading
Loading
Loading
+79 −53
Original line number Diff line number Diff line
@@ -52,7 +52,9 @@ public class FocusFinder {
    final Rect mFocusedRect = new Rect();
    final Rect mOtherRect = new Rect();
    final Rect mBestCandidateRect = new Rect();
    final SequentialFocusComparator mSequentialFocusComparator = new SequentialFocusComparator();
    private final UserSpecifiedFocusComparator mUserSpecifiedFocusComparator =
            new UserSpecifiedFocusComparator();
    private final FocusComparator mFocusComparator = new FocusComparator();

    private final ArrayList<View> mTempList = new ArrayList<View>();

@@ -225,12 +227,10 @@ public class FocusFinder {
            View focused, Rect focusedRect, int direction) {
        try {
            // Note: This sort is stable.
            mSequentialFocusComparator.setRoot(root);
            mSequentialFocusComparator.setIsLayoutRtl(root.isLayoutRtl());
            mSequentialFocusComparator.setFocusables(focusables);
            Collections.sort(focusables, mSequentialFocusComparator);
            mUserSpecifiedFocusComparator.setFocusables(focusables);
            Collections.sort(focusables, mUserSpecifiedFocusComparator);
        } finally {
            mSequentialFocusComparator.recycle();
            mUserSpecifiedFocusComparator.recycle();
        }

        final int count = focusables.size();
@@ -703,36 +703,80 @@ public class FocusFinder {
        return id != 0 && id != View.NO_ID;
    }

    /**
     * Sorts views according to their visual layout and geometry for default tab order.
     * If views are part of a focus chain (nextFocusForwardId), then they are all grouped
     * together. The head of the chain is used to determine the order of the chain and is
     * first in the order and the tail of the chain is the last in the order. The views
     * in the middle of the chain can be arbitrary order.
     * This is used for sequential focus traversal.
     */
    private static final class SequentialFocusComparator implements Comparator<View> {
    static FocusComparator getFocusComparator(ViewGroup root, boolean isRtl) {
        FocusComparator comparator = getInstance().mFocusComparator;
        comparator.setRoot(root);
        comparator.setIsLayoutRtl(isRtl);
        return comparator;
    }

    static final class FocusComparator implements Comparator<View> {
        private final Rect mFirstRect = new Rect();
        private final Rect mSecondRect = new Rect();
        private ViewGroup mRoot;
        private ViewGroup mRoot = null;
        private boolean mIsLayoutRtl;

        public void setIsLayoutRtl(boolean b) {
            mIsLayoutRtl = b;
        }

        public void setRoot(ViewGroup root) {
            mRoot = root;
        }

        public int compare(View first, View second) {
            if (first == second) {
                return 0;
            }

            getRect(first, mFirstRect);
            getRect(second, mSecondRect);

            if (mFirstRect.top < mSecondRect.top) {
                return -1;
            } else if (mFirstRect.top > mSecondRect.top) {
                return 1;
            } else if (mFirstRect.left < mSecondRect.left) {
                return mIsLayoutRtl ? 1 : -1;
            } else if (mFirstRect.left > mSecondRect.left) {
                return mIsLayoutRtl ? -1 : 1;
            } else if (mFirstRect.bottom < mSecondRect.bottom) {
                return -1;
            } else if (mFirstRect.bottom > mSecondRect.bottom) {
                return 1;
            } else if (mFirstRect.right < mSecondRect.right) {
                return mIsLayoutRtl ? 1 : -1;
            } else if (mFirstRect.right > mSecondRect.right) {
                return mIsLayoutRtl ? -1 : 1;
            } else {
                // The view are distinct but completely coincident so we consider
                // them equal for our purposes.  Since the sort is stable, this
                // means that the views will retain their layout order relative to one another.
                return 0;
            }
        }

        private void getRect(View view, Rect rect) {
            view.getDrawingRect(rect);
            mRoot.offsetDescendantRectToMyCoords(view, rect);
        }
    }

    /**
     * Sorts views according to any explicitly-specified focus-chains. If there are no explicitly
     * specified focus chains (eg. no nextFocusForward attributes defined), this should be a no-op.
     */
    private static final class UserSpecifiedFocusComparator implements Comparator<View> {
        private final SparseArray<View> mFocusables = new SparseArray<View>();
        private final SparseBooleanArray mIsConnectedTo = new SparseBooleanArray();
        private final ArrayMap<View, View> mHeadsOfChains = new ArrayMap<View, View>();
        private final ArrayMap<View, Integer> mOriginalOrdinal = new ArrayMap<>();

        public void recycle() {
            mRoot = null;
            mFocusables.clear();
            mHeadsOfChains.clear();
            mIsConnectedTo.clear();
        }

        public void setRoot(ViewGroup root) {
            mRoot = root;
        }

        public void setIsLayoutRtl(boolean b) {
            mIsLayoutRtl = b;
            mOriginalOrdinal.clear();
        }

        public void setFocusables(ArrayList<View> focusables) {
@@ -755,6 +799,10 @@ public class FocusFinder {
                    setHeadOfChain(view);
                }
            }

            for (int i = 0; i < focusables.size(); ++i) {
                mOriginalOrdinal.put(focusables.get(i), i);
            }
        }

        private void setHeadOfChain(View head) {
@@ -793,44 +841,22 @@ public class FocusFinder {
                    return 1; // first is end of chain
                }
            }
            boolean involvesChain = false;
            if (firstHead != null) {
                first = firstHead;
                involvesChain = true;
            }
            if (secondHead != null) {
                second = secondHead;
                involvesChain = true;
            }

            // First see if they belong to the same focus chain.
            getRect(first, mFirstRect);
            getRect(second, mSecondRect);

            if (mFirstRect.top < mSecondRect.top) {
                return -1;
            } else if (mFirstRect.top > mSecondRect.top) {
                return 1;
            } else if (mFirstRect.left < mSecondRect.left) {
                return mIsLayoutRtl ? 1 : -1;
            } else if (mFirstRect.left > mSecondRect.left) {
                return mIsLayoutRtl ? -1 : 1;
            } else if (mFirstRect.bottom < mSecondRect.bottom) {
                return -1;
            } else if (mFirstRect.bottom > mSecondRect.bottom) {
                return 1;
            } else if (mFirstRect.right < mSecondRect.right) {
                return mIsLayoutRtl ? 1 : -1;
            } else if (mFirstRect.right > mSecondRect.right) {
                return mIsLayoutRtl ? -1 : 1;
            if (involvesChain) {
                // keep original order between chains
                return mOriginalOrdinal.get(first) < mOriginalOrdinal.get(second) ? -1 : 1;
            } else {
                // The view are distinct but completely coincident so we consider
                // them equal for our purposes.  Since the sort is stable, this
                // means that the views will retain their layout order relative to one another.
                return 0;
            }
        }

        private void getRect(View view, Rect rect) {
            view.getDrawingRect(rect);
            mRoot.offsetDescendantRectToMyCoords(view, rect);
        }
    }
}
+28 −16
Original line number Diff line number Diff line
@@ -60,6 +60,7 @@ import com.android.internal.R;
import com.android.internal.util.Predicate;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
@@ -1140,31 +1141,42 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
        final int focusableCount = views.size();

        final int descendantFocusability = getDescendantFocusability();
        final boolean focusSelf = (isFocusableInTouchMode() || !shouldBlockFocusForTouchscreen());

        if (descendantFocusability == FOCUS_BLOCK_DESCENDANTS) {
            if (focusSelf) {
                super.addFocusables(views, direction, focusableMode);
            }
            return;
        }

        if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
        if (shouldBlockFocusForTouchscreen()) {
            focusableMode |= FOCUSABLES_TOUCH_MODE;
        }

            final int count = mChildrenCount;
            final View[] children = mChildren;
        if ((descendantFocusability == FOCUS_BEFORE_DESCENDANTS) && focusSelf) {
            super.addFocusables(views, direction, focusableMode);
        }

            for (int i = 0; i < count; i++) {
                final View child = children[i];
        int count = 0;
        final View[] children = new View[mChildrenCount];
        for (int i = 0; i < mChildrenCount; ++i) {
            View child = mChildren[i];
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                    child.addFocusables(views, direction, focusableMode);
                children[count++] = child;
            }
        }
        Arrays.sort(children, 0, count, FocusFinder.getFocusComparator(this, false));
        for (int i = 0; i < count; ++i) {
            children[i].addFocusables(views, direction, focusableMode);
        }

        // we add ourselves (if focusable) in all cases except for when we are
        // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable.  this is
        // When set to FOCUS_AFTER_DESCENDANTS, we only add ourselves if
        // there aren't any focusable descendants.  this is
        // to avoid the focus search finding layouts when a more precise search
        // among the focusable children would be more interesting.
        if ((descendantFocusability != FOCUS_AFTER_DESCENDANTS
                // No focusable descendants
                || (focusableCount == views.size())) &&
                (isFocusableInTouchMode() || !shouldBlockFocusForTouchscreen())) {
        if ((descendantFocusability == FOCUS_AFTER_DESCENDANTS) && focusSelf
                && focusableCount == views.size()) {
            super.addFocusables(views, direction, focusableMode);
        }
    }