• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.view;
18 
19 import android.graphics.Rect;
20 
21 import java.util.ArrayList;
22 import java.util.Collections;
23 import java.util.Comparator;
24 
25 /**
26  * The algorithm used for finding the next focusable view in a given direction
27  * from a view that currently has focus.
28  */
29 public class FocusFinder {
30 
31     private static final ThreadLocal<FocusFinder> tlFocusFinder =
32             new ThreadLocal<FocusFinder>() {
33                 @Override
34                 protected FocusFinder initialValue() {
35                     return new FocusFinder();
36                 }
37             };
38 
39     /**
40      * Get the focus finder for this thread.
41      */
getInstance()42     public static FocusFinder getInstance() {
43         return tlFocusFinder.get();
44     }
45 
46     final Rect mFocusedRect = new Rect();
47     final Rect mOtherRect = new Rect();
48     final Rect mBestCandidateRect = new Rect();
49     final SequentialFocusComparator mSequentialFocusComparator = new SequentialFocusComparator();
50 
51     private final ArrayList<View> mTempList = new ArrayList<View>();
52 
53     // enforce thread local access
FocusFinder()54     private FocusFinder() {}
55 
56     /**
57      * Find the next view to take focus in root's descendants, starting from the view
58      * that currently is focused.
59      * @param root Contains focused. Cannot be null.
60      * @param focused Has focus now.
61      * @param direction Direction to look.
62      * @return The next focusable view, or null if none exists.
63      */
findNextFocus(ViewGroup root, View focused, int direction)64     public final View findNextFocus(ViewGroup root, View focused, int direction) {
65         return findNextFocus(root, focused, null, direction);
66     }
67 
68     /**
69      * Find the next view to take focus in root's descendants, searching from
70      * a particular rectangle in root's coordinates.
71      * @param root Contains focusedRect. Cannot be null.
72      * @param focusedRect The starting point of the search.
73      * @param direction Direction to look.
74      * @return The next focusable view, or null if none exists.
75      */
findNextFocusFromRect(ViewGroup root, Rect focusedRect, int direction)76     public View findNextFocusFromRect(ViewGroup root, Rect focusedRect, int direction) {
77         mFocusedRect.set(focusedRect);
78         return findNextFocus(root, null, mFocusedRect, direction);
79     }
80 
findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction)81     private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
82         View next = null;
83         if (focused != null) {
84             next = findNextUserSpecifiedFocus(root, focused, direction);
85         }
86         if (next != null) {
87             return next;
88         }
89         ArrayList<View> focusables = mTempList;
90         try {
91             focusables.clear();
92             root.addFocusables(focusables, direction);
93             if (!focusables.isEmpty()) {
94                 next = findNextFocus(root, focused, focusedRect, direction, focusables);
95             }
96         } finally {
97             focusables.clear();
98         }
99         return next;
100     }
101 
findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction)102     private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) {
103         // check for user specified next focus
104         View userSetNextFocus = focused.findUserSetNextFocus(root, direction);
105         if (userSetNextFocus != null && userSetNextFocus.isFocusable()
106                 && (!userSetNextFocus.isInTouchMode()
107                         || userSetNextFocus.isFocusableInTouchMode())) {
108             return userSetNextFocus;
109         }
110         return null;
111     }
112 
findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction, ArrayList<View> focusables)113     private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,
114             int direction, ArrayList<View> focusables) {
115         if (focused != null) {
116             if (focusedRect == null) {
117                 focusedRect = mFocusedRect;
118             }
119             // fill in interesting rect from focused
120             focused.getFocusedRect(focusedRect);
121             root.offsetDescendantRectToMyCoords(focused, focusedRect);
122         } else {
123             if (focusedRect == null) {
124                 focusedRect = mFocusedRect;
125                 // make up a rect at top left or bottom right of root
126                 switch (direction) {
127                     case View.FOCUS_RIGHT:
128                     case View.FOCUS_DOWN:
129                         setFocusTopLeft(root, focusedRect);
130                         break;
131                     case View.FOCUS_FORWARD:
132                         if (root.isLayoutRtl()) {
133                             setFocusBottomRight(root, focusedRect);
134                         } else {
135                             setFocusTopLeft(root, focusedRect);
136                         }
137                         break;
138 
139                     case View.FOCUS_LEFT:
140                     case View.FOCUS_UP:
141                         setFocusBottomRight(root, focusedRect);
142                         break;
143                     case View.FOCUS_BACKWARD:
144                         if (root.isLayoutRtl()) {
145                             setFocusTopLeft(root, focusedRect);
146                         } else {
147                             setFocusBottomRight(root, focusedRect);
148                         break;
149                     }
150                 }
151             }
152         }
153 
154         switch (direction) {
155             case View.FOCUS_FORWARD:
156             case View.FOCUS_BACKWARD:
157                 return findNextFocusInRelativeDirection(focusables, root, focused, focusedRect,
158                         direction);
159             case View.FOCUS_UP:
160             case View.FOCUS_DOWN:
161             case View.FOCUS_LEFT:
162             case View.FOCUS_RIGHT:
163                 return findNextFocusInAbsoluteDirection(focusables, root, focused,
164                         focusedRect, direction);
165             default:
166                 throw new IllegalArgumentException("Unknown direction: " + direction);
167         }
168     }
169 
findNextFocusInRelativeDirection(ArrayList<View> focusables, ViewGroup root, View focused, Rect focusedRect, int direction)170     private View findNextFocusInRelativeDirection(ArrayList<View> focusables, ViewGroup root,
171             View focused, Rect focusedRect, int direction) {
172         try {
173             // Note: This sort is stable.
174             mSequentialFocusComparator.setRoot(root);
175             Collections.sort(focusables, mSequentialFocusComparator);
176         } finally {
177             mSequentialFocusComparator.recycle();
178         }
179 
180         final int count = focusables.size();
181         switch (direction) {
182             case View.FOCUS_FORWARD:
183                 return getForwardFocusable(root, focused, focusables, count);
184             case View.FOCUS_BACKWARD:
185                 return getBackwardFocusable(root, focused, focusables, count);
186         }
187         return focusables.get(count - 1);
188     }
189 
setFocusBottomRight(ViewGroup root, Rect focusedRect)190     private void setFocusBottomRight(ViewGroup root, Rect focusedRect) {
191         final int rootBottom = root.getScrollY() + root.getHeight();
192         final int rootRight = root.getScrollX() + root.getWidth();
193         focusedRect.set(rootRight, rootBottom, rootRight, rootBottom);
194     }
195 
setFocusTopLeft(ViewGroup root, Rect focusedRect)196     private void setFocusTopLeft(ViewGroup root, Rect focusedRect) {
197         final int rootTop = root.getScrollY();
198         final int rootLeft = root.getScrollX();
199         focusedRect.set(rootLeft, rootTop, rootLeft, rootTop);
200     }
201 
findNextFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused, Rect focusedRect, int direction)202     View findNextFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused,
203             Rect focusedRect, int direction) {
204         // initialize the best candidate to something impossible
205         // (so the first plausible view will become the best choice)
206         mBestCandidateRect.set(focusedRect);
207         switch(direction) {
208             case View.FOCUS_LEFT:
209                 mBestCandidateRect.offset(focusedRect.width() + 1, 0);
210                 break;
211             case View.FOCUS_RIGHT:
212                 mBestCandidateRect.offset(-(focusedRect.width() + 1), 0);
213                 break;
214             case View.FOCUS_UP:
215                 mBestCandidateRect.offset(0, focusedRect.height() + 1);
216                 break;
217             case View.FOCUS_DOWN:
218                 mBestCandidateRect.offset(0, -(focusedRect.height() + 1));
219         }
220 
221         View closest = null;
222 
223         int numFocusables = focusables.size();
224         for (int i = 0; i < numFocusables; i++) {
225             View focusable = focusables.get(i);
226 
227             // only interested in other non-root views
228             if (focusable == focused || focusable == root) continue;
229 
230             // get focus bounds of other view in same coordinate system
231             focusable.getFocusedRect(mOtherRect);
232             root.offsetDescendantRectToMyCoords(focusable, mOtherRect);
233 
234             if (isBetterCandidate(direction, focusedRect, mOtherRect, mBestCandidateRect)) {
235                 mBestCandidateRect.set(mOtherRect);
236                 closest = focusable;
237             }
238         }
239         return closest;
240     }
241 
getForwardFocusable(ViewGroup root, View focused, ArrayList<View> focusables, int count)242     private static View getForwardFocusable(ViewGroup root, View focused,
243                                             ArrayList<View> focusables, int count) {
244         return (root.isLayoutRtl()) ?
245                 getPreviousFocusable(focused, focusables, count) :
246                 getNextFocusable(focused, focusables, count);
247     }
248 
getNextFocusable(View focused, ArrayList<View> focusables, int count)249     private static View getNextFocusable(View focused, ArrayList<View> focusables, int count) {
250         if (focused != null) {
251             int position = focusables.lastIndexOf(focused);
252             if (position >= 0 && position + 1 < count) {
253                 return focusables.get(position + 1);
254             }
255         }
256         if (!focusables.isEmpty()) {
257             return focusables.get(0);
258         }
259         return null;
260     }
261 
getBackwardFocusable(ViewGroup root, View focused, ArrayList<View> focusables, int count)262     private static View getBackwardFocusable(ViewGroup root, View focused,
263                                              ArrayList<View> focusables, int count) {
264         return (root.isLayoutRtl()) ?
265                 getNextFocusable(focused, focusables, count) :
266                 getPreviousFocusable(focused, focusables, count);
267     }
268 
getPreviousFocusable(View focused, ArrayList<View> focusables, int count)269     private static View getPreviousFocusable(View focused, ArrayList<View> focusables, int count) {
270         if (focused != null) {
271             int position = focusables.indexOf(focused);
272             if (position > 0) {
273                 return focusables.get(position - 1);
274             }
275         }
276         if (!focusables.isEmpty()) {
277             return focusables.get(count - 1);
278         }
279         return null;
280     }
281 
282     /**
283      * Is rect1 a better candidate than rect2 for a focus search in a particular
284      * direction from a source rect?  This is the core routine that determines
285      * the order of focus searching.
286      * @param direction the direction (up, down, left, right)
287      * @param source The source we are searching from
288      * @param rect1 The candidate rectangle
289      * @param rect2 The current best candidate.
290      * @return Whether the candidate is the new best.
291      */
isBetterCandidate(int direction, Rect source, Rect rect1, Rect rect2)292     boolean isBetterCandidate(int direction, Rect source, Rect rect1, Rect rect2) {
293 
294         // to be a better candidate, need to at least be a candidate in the first
295         // place :)
296         if (!isCandidate(source, rect1, direction)) {
297             return false;
298         }
299 
300         // we know that rect1 is a candidate.. if rect2 is not a candidate,
301         // rect1 is better
302         if (!isCandidate(source, rect2, direction)) {
303             return true;
304         }
305 
306         // if rect1 is better by beam, it wins
307         if (beamBeats(direction, source, rect1, rect2)) {
308             return true;
309         }
310 
311         // if rect2 is better, then rect1 cant' be :)
312         if (beamBeats(direction, source, rect2, rect1)) {
313             return false;
314         }
315 
316         // otherwise, do fudge-tastic comparison of the major and minor axis
317         return (getWeightedDistanceFor(
318                         majorAxisDistance(direction, source, rect1),
319                         minorAxisDistance(direction, source, rect1))
320                 < getWeightedDistanceFor(
321                         majorAxisDistance(direction, source, rect2),
322                         minorAxisDistance(direction, source, rect2)));
323     }
324 
325     /**
326      * One rectangle may be another candidate than another by virtue of being
327      * exclusively in the beam of the source rect.
328      * @return Whether rect1 is a better candidate than rect2 by virtue of it being in src's
329      *      beam
330      */
beamBeats(int direction, Rect source, Rect rect1, Rect rect2)331     boolean beamBeats(int direction, Rect source, Rect rect1, Rect rect2) {
332         final boolean rect1InSrcBeam = beamsOverlap(direction, source, rect1);
333         final boolean rect2InSrcBeam = beamsOverlap(direction, source, rect2);
334 
335         // if rect1 isn't exclusively in the src beam, it doesn't win
336         if (rect2InSrcBeam || !rect1InSrcBeam) {
337             return false;
338         }
339 
340         // we know rect1 is in the beam, and rect2 is not
341 
342         // if rect1 is to the direction of, and rect2 is not, rect1 wins.
343         // for example, for direction left, if rect1 is to the left of the source
344         // and rect2 is below, then we always prefer the in beam rect1, since rect2
345         // could be reached by going down.
346         if (!isToDirectionOf(direction, source, rect2)) {
347             return true;
348         }
349 
350         // for horizontal directions, being exclusively in beam always wins
351         if ((direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT)) {
352             return true;
353         }
354 
355         // for vertical directions, beams only beat up to a point:
356         // now, as long as rect2 isn't completely closer, rect1 wins
357         // e.g for direction down, completely closer means for rect2's top
358         // edge to be closer to the source's top edge than rect1's bottom edge.
359         return (majorAxisDistance(direction, source, rect1)
360                 < majorAxisDistanceToFarEdge(direction, source, rect2));
361     }
362 
363     /**
364      * Fudge-factor opportunity: how to calculate distance given major and minor
365      * axis distances.  Warning: this fudge factor is finely tuned, be sure to
366      * run all focus tests if you dare tweak it.
367      */
getWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance)368     int getWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance) {
369         return 13 * majorAxisDistance * majorAxisDistance
370                 + minorAxisDistance * minorAxisDistance;
371     }
372 
373     /**
374      * Is destRect a candidate for the next focus given the direction?  This
375      * checks whether the dest is at least partially to the direction of (e.g left of)
376      * from source.
377      *
378      * Includes an edge case for an empty rect (which is used in some cases when
379      * searching from a point on the screen).
380      */
isCandidate(Rect srcRect, Rect destRect, int direction)381     boolean isCandidate(Rect srcRect, Rect destRect, int direction) {
382         switch (direction) {
383             case View.FOCUS_LEFT:
384                 return (srcRect.right > destRect.right || srcRect.left >= destRect.right)
385                         && srcRect.left > destRect.left;
386             case View.FOCUS_RIGHT:
387                 return (srcRect.left < destRect.left || srcRect.right <= destRect.left)
388                         && srcRect.right < destRect.right;
389             case View.FOCUS_UP:
390                 return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom)
391                         && srcRect.top > destRect.top;
392             case View.FOCUS_DOWN:
393                 return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top)
394                         && srcRect.bottom < destRect.bottom;
395         }
396         throw new IllegalArgumentException("direction must be one of "
397                 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
398     }
399 
400 
401     /**
402      * Do the "beams" w.r.t the given direction's axis of rect1 and rect2 overlap?
403      * @param direction the direction (up, down, left, right)
404      * @param rect1 The first rectangle
405      * @param rect2 The second rectangle
406      * @return whether the beams overlap
407      */
beamsOverlap(int direction, Rect rect1, Rect rect2)408     boolean beamsOverlap(int direction, Rect rect1, Rect rect2) {
409         switch (direction) {
410             case View.FOCUS_LEFT:
411             case View.FOCUS_RIGHT:
412                 return (rect2.bottom >= rect1.top) && (rect2.top <= rect1.bottom);
413             case View.FOCUS_UP:
414             case View.FOCUS_DOWN:
415                 return (rect2.right >= rect1.left) && (rect2.left <= rect1.right);
416         }
417         throw new IllegalArgumentException("direction must be one of "
418                 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
419     }
420 
421     /**
422      * e.g for left, is 'to left of'
423      */
isToDirectionOf(int direction, Rect src, Rect dest)424     boolean isToDirectionOf(int direction, Rect src, Rect dest) {
425         switch (direction) {
426             case View.FOCUS_LEFT:
427                 return src.left >= dest.right;
428             case View.FOCUS_RIGHT:
429                 return src.right <= dest.left;
430             case View.FOCUS_UP:
431                 return src.top >= dest.bottom;
432             case View.FOCUS_DOWN:
433                 return src.bottom <= dest.top;
434         }
435         throw new IllegalArgumentException("direction must be one of "
436                 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
437     }
438 
439     /**
440      * @return The distance from the edge furthest in the given direction
441      *   of source to the edge nearest in the given direction of dest.  If the
442      *   dest is not in the direction from source, return 0.
443      */
majorAxisDistance(int direction, Rect source, Rect dest)444     static int majorAxisDistance(int direction, Rect source, Rect dest) {
445         return Math.max(0, majorAxisDistanceRaw(direction, source, dest));
446     }
447 
majorAxisDistanceRaw(int direction, Rect source, Rect dest)448     static int majorAxisDistanceRaw(int direction, Rect source, Rect dest) {
449         switch (direction) {
450             case View.FOCUS_LEFT:
451                 return source.left - dest.right;
452             case View.FOCUS_RIGHT:
453                 return dest.left - source.right;
454             case View.FOCUS_UP:
455                 return source.top - dest.bottom;
456             case View.FOCUS_DOWN:
457                 return dest.top - source.bottom;
458         }
459         throw new IllegalArgumentException("direction must be one of "
460                 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
461     }
462 
463     /**
464      * @return The distance along the major axis w.r.t the direction from the
465      *   edge of source to the far edge of dest. If the
466      *   dest is not in the direction from source, return 1 (to break ties with
467      *   {@link #majorAxisDistance}).
468      */
majorAxisDistanceToFarEdge(int direction, Rect source, Rect dest)469     static int majorAxisDistanceToFarEdge(int direction, Rect source, Rect dest) {
470         return Math.max(1, majorAxisDistanceToFarEdgeRaw(direction, source, dest));
471     }
472 
majorAxisDistanceToFarEdgeRaw(int direction, Rect source, Rect dest)473     static int majorAxisDistanceToFarEdgeRaw(int direction, Rect source, Rect dest) {
474         switch (direction) {
475             case View.FOCUS_LEFT:
476                 return source.left - dest.left;
477             case View.FOCUS_RIGHT:
478                 return dest.right - source.right;
479             case View.FOCUS_UP:
480                 return source.top - dest.top;
481             case View.FOCUS_DOWN:
482                 return dest.bottom - source.bottom;
483         }
484         throw new IllegalArgumentException("direction must be one of "
485                 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
486     }
487 
488     /**
489      * Find the distance on the minor axis w.r.t the direction to the nearest
490      * edge of the destination rectangle.
491      * @param direction the direction (up, down, left, right)
492      * @param source The source rect.
493      * @param dest The destination rect.
494      * @return The distance.
495      */
minorAxisDistance(int direction, Rect source, Rect dest)496     static int minorAxisDistance(int direction, Rect source, Rect dest) {
497         switch (direction) {
498             case View.FOCUS_LEFT:
499             case View.FOCUS_RIGHT:
500                 // the distance between the center verticals
501                 return Math.abs(
502                         ((source.top + source.height() / 2) -
503                         ((dest.top + dest.height() / 2))));
504             case View.FOCUS_UP:
505             case View.FOCUS_DOWN:
506                 // the distance between the center horizontals
507                 return Math.abs(
508                         ((source.left + source.width() / 2) -
509                         ((dest.left + dest.width() / 2))));
510         }
511         throw new IllegalArgumentException("direction must be one of "
512                 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
513     }
514 
515     /**
516      * Find the nearest touchable view to the specified view.
517      *
518      * @param root The root of the tree in which to search
519      * @param x X coordinate from which to start the search
520      * @param y Y coordinate from which to start the search
521      * @param direction Direction to look
522      * @param deltas Offset from the <x, y> to the edge of the nearest view. Note that this array
523      *        may already be populated with values.
524      * @return The nearest touchable view, or null if none exists.
525      */
findNearestTouchable(ViewGroup root, int x, int y, int direction, int[] deltas)526     public View findNearestTouchable(ViewGroup root, int x, int y, int direction, int[] deltas) {
527         ArrayList<View> touchables = root.getTouchables();
528         int minDistance = Integer.MAX_VALUE;
529         View closest = null;
530 
531         int numTouchables = touchables.size();
532 
533         int edgeSlop = ViewConfiguration.get(root.mContext).getScaledEdgeSlop();
534 
535         Rect closestBounds = new Rect();
536         Rect touchableBounds = mOtherRect;
537 
538         for (int i = 0; i < numTouchables; i++) {
539             View touchable = touchables.get(i);
540 
541             // get visible bounds of other view in same coordinate system
542             touchable.getDrawingRect(touchableBounds);
543 
544             root.offsetRectBetweenParentAndChild(touchable, touchableBounds, true, true);
545 
546             if (!isTouchCandidate(x, y, touchableBounds, direction)) {
547                 continue;
548             }
549 
550             int distance = Integer.MAX_VALUE;
551 
552             switch (direction) {
553             case View.FOCUS_LEFT:
554                 distance = x - touchableBounds.right + 1;
555                 break;
556             case View.FOCUS_RIGHT:
557                 distance = touchableBounds.left;
558                 break;
559             case View.FOCUS_UP:
560                 distance = y - touchableBounds.bottom + 1;
561                 break;
562             case View.FOCUS_DOWN:
563                 distance = touchableBounds.top;
564                 break;
565             }
566 
567             if (distance < edgeSlop) {
568                 // Give preference to innermost views
569                 if (closest == null ||
570                         closestBounds.contains(touchableBounds) ||
571                         (!touchableBounds.contains(closestBounds) && distance < minDistance)) {
572                     minDistance = distance;
573                     closest = touchable;
574                     closestBounds.set(touchableBounds);
575                     switch (direction) {
576                     case View.FOCUS_LEFT:
577                         deltas[0] = -distance;
578                         break;
579                     case View.FOCUS_RIGHT:
580                         deltas[0] = distance;
581                         break;
582                     case View.FOCUS_UP:
583                         deltas[1] = -distance;
584                         break;
585                     case View.FOCUS_DOWN:
586                         deltas[1] = distance;
587                         break;
588                     }
589                 }
590             }
591         }
592         return closest;
593     }
594 
595 
596     /**
597      * Is destRect a candidate for the next touch given the direction?
598      */
isTouchCandidate(int x, int y, Rect destRect, int direction)599     private boolean isTouchCandidate(int x, int y, Rect destRect, int direction) {
600         switch (direction) {
601             case View.FOCUS_LEFT:
602                 return destRect.left <= x && destRect.top <= y && y <= destRect.bottom;
603             case View.FOCUS_RIGHT:
604                 return destRect.left >= x && destRect.top <= y && y <= destRect.bottom;
605             case View.FOCUS_UP:
606                 return destRect.top <= y && destRect.left <= x && x <= destRect.right;
607             case View.FOCUS_DOWN:
608                 return destRect.top >= y && destRect.left <= x && x <= destRect.right;
609         }
610         throw new IllegalArgumentException("direction must be one of "
611                 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
612     }
613 
614     /**
615      * Sorts views according to their visual layout and geometry for default tab order.
616      * This is used for sequential focus traversal.
617      */
618     private static final class SequentialFocusComparator implements Comparator<View> {
619         private final Rect mFirstRect = new Rect();
620         private final Rect mSecondRect = new Rect();
621         private ViewGroup mRoot;
622 
recycle()623         public void recycle() {
624             mRoot = null;
625         }
626 
setRoot(ViewGroup root)627         public void setRoot(ViewGroup root) {
628             mRoot = root;
629         }
630 
compare(View first, View second)631         public int compare(View first, View second) {
632             if (first == second) {
633                 return 0;
634             }
635 
636             getRect(first, mFirstRect);
637             getRect(second, mSecondRect);
638 
639             if (mFirstRect.top < mSecondRect.top) {
640                 return -1;
641             } else if (mFirstRect.top > mSecondRect.top) {
642                 return 1;
643             } else if (mFirstRect.left < mSecondRect.left) {
644                 return -1;
645             } else if (mFirstRect.left > mSecondRect.left) {
646                 return 1;
647             } else if (mFirstRect.bottom < mSecondRect.bottom) {
648                 return -1;
649             } else if (mFirstRect.bottom > mSecondRect.bottom) {
650                 return 1;
651             } else if (mFirstRect.right < mSecondRect.right) {
652                 return -1;
653             } else if (mFirstRect.right > mSecondRect.right) {
654                 return 1;
655             } else {
656                 // The view are distinct but completely coincident so we consider
657                 // them equal for our purposes.  Since the sort is stable, this
658                 // means that the views will retain their layout order relative to one another.
659                 return 0;
660             }
661         }
662 
getRect(View view, Rect rect)663         private void getRect(View view, Rect rect) {
664             view.getDrawingRect(rect);
665             mRoot.offsetDescendantRectToMyCoords(view, rect);
666         }
667     }
668 }
669