1049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette/*
2049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * Copyright (C) 2015 The Android Open Source Project
3049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *
4049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * Licensed under the Apache License, Version 2.0 (the "License");
5049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * you may not use this file except in compliance with the License.
6049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * You may obtain a copy of the License at
7049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *
8049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *      http://www.apache.org/licenses/LICENSE-2.0
9049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette *
10049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * Unless required by applicable law or agreed to in writing, software
11049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * distributed under the License is distributed on an "AS IS" BASIS,
12049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * See the License for the specific language governing permissions and
14049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * limitations under the License.
15049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette */
16049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
17049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverettepackage android.support.v4.widget;
18049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
19049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viveretteimport android.graphics.Rect;
20049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viveretteimport android.support.annotation.NonNull;
21049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viveretteimport android.support.annotation.Nullable;
22049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viveretteimport android.view.View;
23049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
24049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viveretteimport android.support.v4.view.ViewCompat.FocusRealDirection;
25049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viveretteimport android.support.v4.view.ViewCompat.FocusRelativeDirection;
26049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
27049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viveretteimport java.util.ArrayList;
28049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viveretteimport java.util.Collections;
29049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viveretteimport java.util.Comparator;
30049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
31049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette/**
32049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * Implements absolute and relative focus movement strategies. Adapted from
33049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * android.view.FocusFinder to work with generic collections of bounded items.
34049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette */
35049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viveretteclass FocusStrategy {
36049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    public static <L,T> T findNextFocusInRelativeDirection(@NonNull L focusables,
37049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            @NonNull CollectionAdapter<L,T> collectionAdapter, @NonNull BoundsAdapter<T> adapter,
38049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            @Nullable T focused, @FocusRelativeDirection int direction, boolean isLayoutRtl,
39049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            boolean wrap) {
40049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final int count = collectionAdapter.size(focusables);
41049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final ArrayList<T> sortedFocusables = new ArrayList<>(count);
42049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        for (int i = 0; i < count; i++) {
43049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            sortedFocusables.add(collectionAdapter.get(focusables, i));
44049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
45049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
46049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final SequentialComparator<T> comparator = new SequentialComparator<>(isLayoutRtl, adapter);
47049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        Collections.sort(sortedFocusables, comparator);
48049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
49049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        switch (direction) {
50049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_FORWARD:
51049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return getNextFocusable(focused, sortedFocusables, wrap);
52049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_BACKWARD:
53049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return getPreviousFocusable(focused, sortedFocusables, wrap);
54049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            default:
55049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                throw new IllegalArgumentException("direction must be one of "
56049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                        + "{FOCUS_FORWARD, FOCUS_BACKWARD}.");
57049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
58049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
59049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
60049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private static <T> T getNextFocusable(T focused, ArrayList<T> focusables, boolean wrap) {
61049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final int count = focusables.size();
62049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
63049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // The position of the next focusable item, which is the first item if
64049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // no item is currently focused.
65049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final int position = (focused == null ? -1 : focusables.lastIndexOf(focused)) + 1;
66049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        if (position < count) {
67049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            return focusables.get(position);
68049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        } else if (wrap && count > 0) {
69049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            return focusables.get(0);
70049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        } else {
71049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            return null;
72049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
73049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
74049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
75049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private static <T> T getPreviousFocusable(T focused, ArrayList<T> focusables, boolean wrap) {
76049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final int count = focusables.size();
77049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
78049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // The position of the previous focusable item, which is the last item
79049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // if no item is currently focused.
80049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final int position = (focused == null ? count : focusables.indexOf(focused)) - 1;
81049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        if (position >= 0) {
82049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            return focusables.get(position);
83049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        } else if (wrap && count > 0) {
84049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            return focusables.get(count - 1);
85049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        } else {
86049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            return null;
87049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
88049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
89049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
90049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
91049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Sorts views according to their visual layout and geometry for default tab order.
92049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * This is used for sequential focus traversal.
93049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
94049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private static class SequentialComparator<T> implements Comparator<T> {
95049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        private final Rect mTemp1 = new Rect();
96049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        private final Rect mTemp2 = new Rect();
97049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
98049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        private final boolean mIsLayoutRtl;
99049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        private final BoundsAdapter<T> mAdapter;
100049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
101049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        public SequentialComparator(boolean isLayoutRtl, BoundsAdapter<T> adapter) {
102049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            mIsLayoutRtl = isLayoutRtl;
103049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            mAdapter = adapter;
104049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
105049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
106049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        public int compare(T first, T second) {
107049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            final Rect firstRect = mTemp1;
108049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            final Rect secondRect = mTemp2;
109049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
110049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            mAdapter.obtainBounds(first, firstRect);
111049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            mAdapter.obtainBounds(second, secondRect);
112049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
113049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            if (firstRect.top < secondRect.top) {
114049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return -1;
115049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            } else if (firstRect.top > secondRect.top) {
116049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return 1;
117049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            } else if (firstRect.left < secondRect.left) {
118049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return mIsLayoutRtl ? 1 : -1;
119049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            } else if (firstRect.left > secondRect.left) {
120049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return mIsLayoutRtl ? -1 : 1;
121049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            } else if (firstRect.bottom < secondRect.bottom) {
122049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return -1;
123049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            } else if (firstRect.bottom > secondRect.bottom) {
124049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return 1;
125049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            } else if (firstRect.right < secondRect.right) {
126049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return mIsLayoutRtl ? 1 : -1;
127049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            } else if (firstRect.right > secondRect.right) {
128049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return mIsLayoutRtl ? -1 : 1;
129049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            } else {
130049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                // The view are distinct but completely coincident so we
131049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                // consider them equal for our purposes. Since the sort is
132049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                // stable, this means that the views will retain their
133049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                // layout order relative to one another.
134049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return 0;
135049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            }
136049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
137049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
138049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
139049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    public static <L,T> T findNextFocusInAbsoluteDirection(@NonNull L focusables,
140049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            @NonNull CollectionAdapter<L,T> collectionAdapter, @NonNull BoundsAdapter<T> adapter,
141049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            @Nullable T focused, @NonNull Rect focusedRect, int direction) {
142049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // Initialize the best candidate to something impossible so that
143049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // the first plausible view will become the best choice.
144049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final Rect bestCandidateRect = new Rect(focusedRect);
145049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
146049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        switch (direction) {
147049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_LEFT:
148049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                bestCandidateRect.offset(focusedRect.width() + 1, 0);
149049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                break;
150049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_RIGHT:
151049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                bestCandidateRect.offset(-(focusedRect.width() + 1), 0);
152049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                break;
153049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_UP:
154049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                bestCandidateRect.offset(0, focusedRect.height() + 1);
155049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                break;
156049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_DOWN:
157049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                bestCandidateRect.offset(0, -(focusedRect.height() + 1));
158049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                break;
159049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            default:
160049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                throw new IllegalArgumentException("direction must be one of "
161049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                        + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
162049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
163049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
164049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        T closest = null;
165049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
166049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final int count = collectionAdapter.size(focusables);
167049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final Rect focusableRect = new Rect();
168049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        for (int i = 0; i < count; i++) {
169049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            final T focusable = collectionAdapter.get(focusables, i);
170049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            if (focusable == focused) {
171049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                continue;
172049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            }
173049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
174049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            // get focus bounds of other view
175049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            adapter.obtainBounds(focusable, focusableRect);
176049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            if (isBetterCandidate(direction, focusedRect, focusableRect, bestCandidateRect)) {
177049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                bestCandidateRect.set(focusableRect);
178049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                closest = focusable;
179049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            }
180049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
181049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
182049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        return closest;
183049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
184049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
185049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
186049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Is candidate a better candidate than currentBest for a focus search
187049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * in a particular direction from a source rect? This is the core
188049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * routine that determines the order of focus searching.
189049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *
190049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param direction   the direction (up, down, left, right)
191049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param source      the source from which we are searching
192049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param candidate   the candidate rectangle
193049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param currentBest the current best rectangle
194049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @return {@code true} if the candidate rectangle is a better than the
195049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * current best rectangle, {@code false} otherwise
196049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
197049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private static boolean isBetterCandidate(
198049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            @FocusRealDirection int direction, @NonNull Rect source,
199049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            @NonNull Rect candidate, @NonNull Rect currentBest) {
200049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // To be a better candidate, need to at least be a candidate in the
201049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // first place. :)
202049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        if (!isCandidate(source, candidate, direction)) {
203049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            return false;
204049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
205049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
206049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // We know that candidateRect is a candidate. If currentBest is not
207049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // a candidate, candidateRect is better.
208049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        if (!isCandidate(source, currentBest, direction)) {
209049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            return true;
210049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
211049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
212049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // If candidateRect is better by beam, it wins.
213049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        if (beamBeats(direction, source, candidate, currentBest)) {
214049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            return true;
215049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
216049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
217049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // If currentBest is better, then candidateRect cant' be. :)
218049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        if (beamBeats(direction, source, currentBest, candidate)) {
219049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            return false;
220049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
221049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
222049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // Otherwise, do fudge-tastic comparison of the major and minor
223049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // axis.
224049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final int candidateDist = getWeightedDistanceFor(
225049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                majorAxisDistance(direction, source, candidate),
226049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                minorAxisDistance(direction, source, candidate));
227049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final int currentBestDist = getWeightedDistanceFor(
228049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                majorAxisDistance(direction, source, currentBest),
229049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                minorAxisDistance(direction, source, currentBest));
230049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        return candidateDist < currentBestDist;
231049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
232049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
233049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
234049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * One rectangle may be another candidate than another by virtue of
235049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * being exclusively in the beam of the source rect.
236049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *
237049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @return whether rect1 is a better candidate than rect2 by virtue of
238049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * it being in source's beam
239049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
240049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private static boolean beamBeats(@FocusRealDirection int direction,
241049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            @NonNull Rect source, @NonNull Rect rect1, @NonNull Rect rect2) {
242049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final boolean rect1InSrcBeam = beamsOverlap(direction, source, rect1);
243049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        final boolean rect2InSrcBeam = beamsOverlap(direction, source, rect2);
244049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
245049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // If rect1 isn't exclusively in the src beam, it doesn't win.
246049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        if (rect2InSrcBeam || !rect1InSrcBeam) {
247049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            return false;
248049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
249049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
250049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // We know rect1 is in the beam, and rect2 is not.
251049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
252049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // If rect1 is to the direction of, and rect2 is not, rect1 wins.
253049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // For example, for direction left, if rect1 is to the left of the
254049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // source and rect2 is below, then we always prefer the in beam
255049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // rect1, since rect2 could be reached by going down.
256049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        if (!isToDirectionOf(direction, source, rect2)) {
257049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            return true;
258049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
259049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
260049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // For horizontal directions, being exclusively in beam always
261049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // wins.
262049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        if (direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT) {
263049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            return true;
264049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
265049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
266049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // For vertical directions, beams only beat up to a point: now, as
267049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // long as rect2 isn't completely closer, rect1 wins, e.g. for
268049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // direction down, completely closer means for rect2's top edge to
269049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        // be closer to the source's top edge than rect1's bottom edge.
270049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        return majorAxisDistance(direction, source, rect1)
271049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                < majorAxisDistanceToFarEdge(direction, source, rect2);
272049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
273049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
274049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
275049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Fudge-factor opportunity: how to calculate distance given major and
276049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * minor axis distances.
277049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * <p/>
278049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Warning: this fudge factor is finely tuned, be sure to run all focus
279049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * tests if you dare tweak it.
280049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
281049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private static int getWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance) {
282049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        return 13 * majorAxisDistance * majorAxisDistance
283049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                + minorAxisDistance * minorAxisDistance;
284049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
285049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
286049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
287049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Is destRect a candidate for the next focus given the direction? This
288049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * checks whether the dest is at least partially to the direction of
289049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * (e.g. left of) from source.
290049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * <p/>
291049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Includes an edge case for an empty rect,which is used in some cases
292049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * when searching from a point on the screen.
293049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
294049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private static boolean isCandidate(@NonNull Rect srcRect, @NonNull Rect destRect,
295049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            @FocusRealDirection int direction) {
296049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        switch (direction) {
297049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_LEFT:
298049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return (srcRect.right > destRect.right || srcRect.left >= destRect.right)
299049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                        && srcRect.left > destRect.left;
300049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_RIGHT:
301049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return (srcRect.left < destRect.left || srcRect.right <= destRect.left)
302049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                        && srcRect.right < destRect.right;
303049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_UP:
304049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom)
305049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                        && srcRect.top > destRect.top;
306049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_DOWN:
307049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top)
308049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                        && srcRect.bottom < destRect.bottom;
309049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
310049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        throw new IllegalArgumentException("direction must be one of "
311049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
312049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
313049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
314049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
315049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
316049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Do the "beams" w.r.t the given direction's axis of rect1 and rect2 overlap?
317049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *
318049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param direction the direction (up, down, left, right)
319049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param rect1     the first rectangle
320049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param rect2     the second rectangle
321049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @return whether the beams overlap
322049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
323049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private static boolean beamsOverlap(@FocusRealDirection int direction,
324049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            @NonNull Rect rect1, @NonNull Rect rect2) {
325049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        switch (direction) {
326049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_LEFT:
327049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_RIGHT:
328049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return (rect2.bottom >= rect1.top) && (rect2.top <= rect1.bottom);
329049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_UP:
330049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_DOWN:
331049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return (rect2.right >= rect1.left) && (rect2.left <= rect1.right);
332049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
333049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        throw new IllegalArgumentException("direction must be one of "
334049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
335049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
336049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
337049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
338049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * e.g for left, is 'to left of'
339049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
340049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private static boolean isToDirectionOf(@FocusRealDirection int direction,
341049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            @NonNull Rect src, @NonNull Rect dest) {
342049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        switch (direction) {
343049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_LEFT:
344049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return src.left >= dest.right;
345049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_RIGHT:
346049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return src.right <= dest.left;
347049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_UP:
348049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return src.top >= dest.bottom;
349049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_DOWN:
350049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return src.bottom <= dest.top;
351049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
352049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        throw new IllegalArgumentException("direction must be one of "
353049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
354049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
355049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
356049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
357049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @return the distance from the edge furthest in the given direction
358049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * of source to the edge nearest in the given direction of
359049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * dest. If the dest is not in the direction from source,
360049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * returns 0.
361049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
362049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private static int majorAxisDistance(@FocusRealDirection int direction,
363049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            @NonNull Rect source, @NonNull Rect dest) {
364049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        return Math.max(0, majorAxisDistanceRaw(direction, source, dest));
365049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
366049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
367049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private static int majorAxisDistanceRaw(@FocusRealDirection int direction,
368049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            @NonNull Rect source, @NonNull Rect dest) {
369049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        switch (direction) {
370049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_LEFT:
371049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return source.left - dest.right;
372049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_RIGHT:
373049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return dest.left - source.right;
374049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_UP:
375049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return source.top - dest.bottom;
376049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_DOWN:
377049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return dest.top - source.bottom;
378049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
379049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        throw new IllegalArgumentException("direction must be one of "
380049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
381049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
382049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
383049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
384049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @return the distance along the major axis w.r.t the direction from
385049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * the edge of source to the far edge of dest. If the dest is
386049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * not in the direction from source, returns 1 to break ties
387049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * with {@link #majorAxisDistance}.
388049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
389049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private static int majorAxisDistanceToFarEdge(@FocusRealDirection int direction,
390049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            @NonNull Rect source, @NonNull Rect dest) {
391049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        return Math.max(1, majorAxisDistanceToFarEdgeRaw(direction, source, dest));
392049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
393049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
394049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private static int majorAxisDistanceToFarEdgeRaw(
395049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            @FocusRealDirection int direction, @NonNull Rect source,
396049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            @NonNull Rect dest) {
397049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        switch (direction) {
398049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_LEFT:
399049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return source.left - dest.left;
400049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_RIGHT:
401049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return dest.right - source.right;
402049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_UP:
403049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return source.top - dest.top;
404049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_DOWN:
405049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return dest.bottom - source.bottom;
406049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
407049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        throw new IllegalArgumentException("direction must be one of "
408049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
409049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
410049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
411049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
412049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Finds the distance on the minor axis w.r.t the direction to the
413049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * nearest edge of the destination rectangle.
414049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     *
415049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param direction the direction (up, down, left, right)
416049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param source the source rect
417049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @param dest the destination rect
418049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * @return the distance
419049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
420049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    private static int minorAxisDistance(@FocusRealDirection int direction, @NonNull Rect source,
421049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            @NonNull Rect dest) {
422049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        switch (direction) {
423049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_LEFT:
424049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_RIGHT:
425049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                // the distance between the center verticals
426049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return Math.abs(
427049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                        ((source.top + source.height() / 2) -
428049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                                ((dest.top + dest.height() / 2))));
429049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_UP:
430049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette            case View.FOCUS_DOWN:
431049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                // the distance between the center horizontals
432049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                return Math.abs(
433049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                        ((source.left + source.width() / 2) -
434049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                                ((dest.left + dest.width() / 2))));
435049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        }
436049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        throw new IllegalArgumentException("direction must be one of "
437049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
438049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
439049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
440049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
441049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Adapter used to obtain bounds from a generic data type.
442049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
443049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    public interface BoundsAdapter<T> {
444049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        void obtainBounds(T data, Rect outBounds);
445049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
446049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette
447049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    /**
448049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     * Adapter used to obtain items from a generic collection type.
449049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette     */
450049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    public interface CollectionAdapter<T, V> {
451049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        V get(T collection, int index);
452049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette        int size(T collection);
453049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette    }
454049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette}
455