FocusFinderTest.java revision 22e31e5b609136d5bf7d77b1dccd6b042b83ebdf
1/*
2 * Copyright (C) 2008 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
17package android.view;
18
19import android.graphics.Rect;
20import android.test.AndroidTestCase;
21import android.test.suitebuilder.annotation.MediumTest;
22import android.test.suitebuilder.annotation.SmallTest;
23
24public class FocusFinderTest extends AndroidTestCase {
25
26    private FocusFinderHelper mFocusFinder;
27
28    @Override
29    protected void setUp() throws Exception {
30        super.setUp();
31
32        mFocusFinder = new FocusFinderHelper(FocusFinder.getInstance());
33    }
34
35    @SmallTest
36    public void testPreconditions() {
37        assertNotNull("focus finder instance", mFocusFinder);
38    }
39
40    @SmallTest
41    public void testBelowNotCandidateForDirectionUp() {
42        assertIsNotCandidate(View.FOCUS_UP,
43                new Rect(0, 30, 10, 40),  // src  (left, top, right, bottom)
44                new Rect(0, 50, 10, 60));  // dest (left, top, right, bottom)
45    }
46
47    @SmallTest
48    public void testAboveShareEdgeEdgeOkForDirectionUp() {
49        final Rect src = new Rect(0, 30, 10, 40);
50
51        final Rect dest = new Rect(src);
52        dest.offset(0, -src.height());
53        assertEquals(src.top, dest.bottom);
54
55        assertDirectionIsCandidate(View.FOCUS_UP, src, dest);
56    }
57
58    @SmallTest
59    public void testCompletelyContainedNotCandidate() {
60        assertIsNotCandidate(
61                View.FOCUS_DOWN,
62                //       L  T   R   B
63                new Rect(0, 0,  50, 50),
64                new Rect(0, 1,  50, 49));
65    }
66
67    @SmallTest
68    public void testContinaedWithCommonBottomNotCandidate() {
69        assertIsNotCandidate(
70                View.FOCUS_DOWN,
71                //       L  T   R   B
72                new Rect(0, 0,  50, 50),
73                new Rect(0, 1,  50, 50));
74    }
75
76    @SmallTest
77    public void testOverlappingIsCandidateWhenBothEdgesAreInDirection() {
78        assertDirectionIsCandidate(
79                View.FOCUS_DOWN,
80                //       L  T   R   B
81                new Rect(0, 0,  50, 50),
82                new Rect(0, 1,  50, 51));
83    }
84
85    @SmallTest
86    public void testTopEdgeOfDestAtOrAboveTopOfSrcNotCandidateForDown() {
87        assertIsNotCandidate(
88                View.FOCUS_DOWN,
89                //       L  T   R   B
90                new Rect(0, 0,  50, 50),
91                new Rect(0, 0,  50, 51));
92        assertIsNotCandidate(
93                View.FOCUS_DOWN,
94                //       L  T   R   B
95                new Rect(0, 0,  50, 50),
96                new Rect(0, -1, 50, 51));
97    }
98
99    @SmallTest
100    public void testSameRectBeamsOverlap() {
101        final Rect rect = new Rect(0, 0, 20, 20);
102
103        assertBeamsOverlap(View.FOCUS_LEFT, rect, rect);
104        assertBeamsOverlap(View.FOCUS_RIGHT, rect, rect);
105        assertBeamsOverlap(View.FOCUS_UP, rect, rect);
106        assertBeamsOverlap(View.FOCUS_DOWN, rect, rect);
107    }
108
109    @SmallTest
110    public void testOverlapBeamsRightLeftUpToEdge() {
111        final Rect rect1 = new Rect(0, 0, 20, 20);
112        final Rect rect2 = new Rect(rect1);
113
114        // just below bottom edge
115        rect2.offset(0, rect1.height() - 1);
116        assertBeamsOverlap(View.FOCUS_LEFT, rect1, rect2);
117        assertBeamsOverlap(View.FOCUS_RIGHT, rect1, rect2);
118
119        // at edge
120        rect2.offset(0, 1);
121        assertBeamsOverlap(View.FOCUS_LEFT, rect1, rect2);
122        assertBeamsOverlap(View.FOCUS_RIGHT, rect1, rect2);
123
124        // just beyond
125        rect2.offset(0, 1);
126        assertBeamsDontOverlap(View.FOCUS_LEFT, rect1, rect2);
127        assertBeamsDontOverlap(View.FOCUS_RIGHT, rect1, rect2);
128
129        // just below top edge
130        rect2.set(rect1);
131        rect2.offset(0, -(rect1.height() - 1));
132        assertBeamsOverlap(View.FOCUS_LEFT, rect1, rect2);
133        assertBeamsOverlap(View.FOCUS_RIGHT, rect1, rect2);
134
135        // at top edge
136        rect2.offset(0, -1);
137        assertBeamsOverlap(View.FOCUS_LEFT, rect1, rect2);
138        assertBeamsOverlap(View.FOCUS_RIGHT, rect1, rect2);
139
140        // just beyond top edge
141        rect2.offset(0, -1);
142        assertBeamsDontOverlap(View.FOCUS_LEFT, rect1, rect2);
143        assertBeamsDontOverlap(View.FOCUS_RIGHT, rect1, rect2);
144    }
145
146    @SmallTest
147    public void testOverlapBeamsUpDownUpToEdge() {
148        final Rect rect1 = new Rect(0, 0, 20, 20);
149        final Rect rect2 = new Rect(rect1);
150
151        // just short of right edge
152        rect2.offset(rect1.width() - 1, 0);
153        assertBeamsOverlap(View.FOCUS_UP, rect1, rect2);
154        assertBeamsOverlap(View.FOCUS_DOWN, rect1, rect2);
155
156        // at edge
157        rect2.offset(1, 0);
158        assertBeamsOverlap(View.FOCUS_UP, rect1, rect2);
159        assertBeamsOverlap(View.FOCUS_DOWN, rect1, rect2);
160
161        // just beyond
162        rect2.offset(1, 0);
163        assertBeamsDontOverlap(View.FOCUS_UP, rect1, rect2);
164        assertBeamsDontOverlap(View.FOCUS_DOWN, rect1, rect2);
165
166        // just short of left edge
167        rect2.set(rect1);
168        rect2.offset(-(rect1.width() - 1), 0);
169        assertBeamsOverlap(View.FOCUS_UP, rect1, rect2);
170        assertBeamsOverlap(View.FOCUS_DOWN, rect1, rect2);
171
172        // at edge
173        rect2.offset(-1, 0);
174        assertBeamsOverlap(View.FOCUS_UP, rect1, rect2);
175        assertBeamsOverlap(View.FOCUS_DOWN, rect1, rect2);
176
177        // just beyond edge
178        rect2.offset(-1, 0);
179        assertBeamsDontOverlap(View.FOCUS_UP, rect1, rect2);
180        assertBeamsDontOverlap(View.FOCUS_DOWN, rect1, rect2);
181    }
182
183    @SmallTest
184    public void testDirectlyAboveTrumpsAboveLeft() {
185        Rect src = new Rect(0, 50, 20, 70);  // src (left, top, right, bottom)
186
187        Rect directlyAbove = new Rect(src);
188        directlyAbove.offset(0, -(1 + src.height()));
189
190        Rect aboveLeft = new Rect(src);
191        aboveLeft.offset(-(1 + src.width()), -(1 + src.height()));
192
193        assertBetterCandidate(View.FOCUS_UP, src, directlyAbove, aboveLeft);
194    }
195
196    @SmallTest
197    public void testAboveInBeamTrumpsSlightlyCloserOutOfBeam() {
198        Rect src = new Rect(0, 50, 20, 70);  // src (left, top, right, bottom)
199
200        Rect directlyAbove = new Rect(src);
201        directlyAbove.offset(0, -(1 + src.height()));
202
203        Rect aboveLeft = new Rect(src);
204        aboveLeft.offset(-(1 + src.width()), -(1 + src.height()));
205
206        // offset directly above a little further up
207        directlyAbove.offset(0, -5);
208        assertBetterCandidate(View.FOCUS_UP, src, directlyAbove, aboveLeft);
209    }
210
211    @SmallTest
212    public void testOutOfBeamBeatsInBeamUp() {
213
214        Rect src = new Rect(0, 0, 50, 50); // (left, top, right, bottom)
215
216        Rect aboveLeftOfBeam = new Rect(src);
217        aboveLeftOfBeam.offset(-(src.width() + 1), -src.height());
218        assertBeamsDontOverlap(View.FOCUS_UP, src, aboveLeftOfBeam);
219
220        Rect aboveInBeam = new Rect(src);
221        aboveInBeam.offset(0, -src.height());
222        assertBeamsOverlap(View.FOCUS_UP, src, aboveInBeam);
223
224        // in beam wins
225        assertBetterCandidate(View.FOCUS_UP, src, aboveInBeam, aboveLeftOfBeam);
226
227        // still wins while aboveInBeam's bottom edge is < out of beams' top
228        aboveInBeam.offset(0, -(aboveLeftOfBeam.height() - 1));
229        assertTrue("aboveInBeam.bottom > aboveLeftOfBeam.top", aboveInBeam.bottom > aboveLeftOfBeam.top);
230        assertBetterCandidate(View.FOCUS_UP, src, aboveInBeam, aboveLeftOfBeam);
231
232        // cross the threshold: the out of beam prevails
233        aboveInBeam.offset(0, -1);
234        assertEquals(aboveInBeam.bottom, aboveLeftOfBeam.top);
235        assertBetterCandidate(View.FOCUS_UP, src, aboveLeftOfBeam, aboveInBeam);
236    }
237
238    /**
239     * A non-candidate (even a much closer one) is always a worse choice
240     * than a real candidate.
241     */
242    @MediumTest
243    public void testSomeCandidateBetterThanNonCandidate() {
244        Rect src = new Rect(0, 0, 50, 50); // (left, top, right, bottom)
245
246        Rect nonCandidate = new Rect(src);
247        nonCandidate.offset(src.width() + 1, 0);
248
249        assertIsNotCandidate(View.FOCUS_LEFT, src, nonCandidate);
250
251        Rect candidate = new Rect(src);
252        candidate.offset(-(4 * src.width()), 0);
253        assertDirectionIsCandidate(View.FOCUS_LEFT, src, candidate);
254
255        assertBetterCandidate(View.FOCUS_LEFT, src, candidate, nonCandidate);
256    }
257
258    /**
259     * Grabbed from {@link com.android.frameworktest.focus.VerticalFocusSearchTest#testSearchFromMidLeft()}
260     */
261    @SmallTest
262    public void testVerticalFocusSearchScenario() {
263        assertBetterCandidate(View.FOCUS_DOWN,
264                //       L    T    R    B
265                new Rect(0,   109, 153, 169),   // src
266                new Rect(166, 169, 319, 229),  // expectedbetter
267                new Rect(0,   229, 320, 289)); // expectedworse
268
269        // failing test 4/10/2008, the values were tweaked somehow in functional
270        // test...
271        assertBetterCandidate(View.FOCUS_DOWN,
272                //       L    T    R    B
273                new Rect(0,   91, 153, 133),   // src
274                new Rect(166, 133, 319, 175),  // expectedbetter
275                new Rect(0,   175, 320, 217)); // expectedworse
276
277    }
278
279    /**
280     * Example: going down from a thin button all the way to the left of a
281     * screen where, just below, is a very wide button, and just below that,
282     * is an equally skinny button all the way to the left.  want to make
283     * sure any minor axis factor doesn't override the fact that the one below
284     * in vertical beam should be next focus
285     */
286    @SmallTest
287    public void testBeamsOverlapMajorAxisCloserMinorAxisFurther() {
288        assertBetterCandidate(View.FOCUS_DOWN,
289                //       L   T    R    B
290                new Rect(0,  0,   100,  100),  // src
291                new Rect(0,  100, 480,  200),  // expectedbetter
292                new Rect(0,  200, 100,  300)); // expectedworse
293    }
294
295    /**
296     * Real scenario grabbed from song playback screen.
297     */
298    @SmallTest
299    public void testMusicPlaybackScenario() {
300        assertBetterCandidate(View.FOCUS_LEFT,
301                //       L    T    R    B
302                new Rect(227, 185, 312, 231),   // src
303                new Rect(195, 386, 266, 438),   // expectedbetter
304                new Rect(124, 386, 195, 438));  // expectedworse
305    }
306
307    /**
308     * more generalized version of {@link #testMusicPlaybackScenario()}
309     */
310    @SmallTest
311    public void testOutOfBeamOverlapBeatsOutOfBeamFurtherOnMajorAxis() {
312        assertBetterCandidate(View.FOCUS_DOWN,
313                //       L    T    R    B
314                new Rect(0,   0,   50,  50),   // src
315                new Rect(60,  40,  110, 90),   // expectedbetter
316                new Rect(60,  70,  110, 120));  // expectedworse
317    }
318
319    /**
320     * Make sure that going down prefers views that are actually
321     * down (and not those next to but still a candidate because
322     * they are overlapping on the major axis)
323     */
324    @SmallTest
325    public void testInBeamTrumpsOutOfBeamOverlapping() {
326        assertBetterCandidate(View.FOCUS_DOWN,
327                //       L    T    R    B
328                new Rect(0,   0,   50,  50),   // src
329                new Rect(0,   60,  50,  110),  // expectedbetter
330                new Rect(51,  1,   101, 51)); // expectedworse
331    }
332
333    @SmallTest
334    public void testOverlappingBeatsNonOverlapping() {
335        assertBetterCandidate(View.FOCUS_DOWN,
336                //       L    T    R    B
337                new Rect(0,   0,   50,  50),   // src
338                new Rect(0,   40,  50,  90),   // expectedbetter
339                new Rect(0,   75,  50,  125)); // expectedworse
340    }
341
342    @SmallTest
343    public void testEditContactScenarioLeftFromDiscardChangesGoesToSaveContactInLandscape() {
344        assertBetterCandidate(View.FOCUS_LEFT,
345                //       L    T    R    B
346                new Rect(357, 258, 478, 318),  // src
347                new Rect(2,   258, 100, 318),  // better
348                new Rect(106, 120, 424, 184)); // worse
349    }
350
351    /**
352     * A dial pad with 9 squares arranged in a grid.  no padding, so
353     * the edges are equal.  see {@link com.android.frameworktest.focus.LinearLayoutGrid}
354     */
355    @SmallTest
356    public void testGridWithTouchingEdges() {
357        assertBetterCandidate(View.FOCUS_DOWN,
358                //       L    T    R    B
359                new Rect(106, 49,  212, 192),  // src
360                new Rect(106, 192, 212, 335),  // better
361                new Rect(0,   192, 106, 335)); // worse
362
363        assertBetterCandidate(View.FOCUS_DOWN,
364                //       L    T    R    B
365                new Rect(106, 49,  212, 192),  // src
366                new Rect(106, 192, 212, 335),  // better
367                new Rect(212, 192, 318, 335)); // worse
368    }
369
370    @SmallTest
371    public void testSearchFromEmptyRect() {
372        assertBetterCandidate(View.FOCUS_DOWN,
373                //       L   T    R    B
374                new Rect(0,  0,   0,   0),    // src
375                new Rect(0,  0,   320, 45),   // better
376                new Rect(0,  45,  320, 545)); // worse
377    }
378
379    /**
380     * Reproduce bug 1124559, drilling down to actual bug
381     * (majorAxisDistance was wrong for direction left)
382     */
383    @SmallTest
384    public void testGmailReplyButtonsScenario() {
385        assertBetterCandidate(View.FOCUS_LEFT,
386                //       L    T    R    B
387                new Rect(223, 380, 312, 417),  // src
388                new Rect(102, 380, 210, 417),  // better
389                new Rect(111, 443, 206, 480)); // worse
390
391        assertBeamBeats(View.FOCUS_LEFT,
392            //       L    T    R    B
393            new Rect(223, 380, 312, 417),  // src
394            new Rect(102, 380, 210, 417),  // better
395            new Rect(111, 443, 206, 480)); // worse
396
397        assertBeamsOverlap(View.FOCUS_LEFT,
398                //       L    T    R    B
399                new Rect(223, 380, 312, 417),
400                new Rect(102, 380, 210, 417));
401
402        assertBeamsDontOverlap(View.FOCUS_LEFT,
403                //       L    T    R    B
404                new Rect(223, 380, 312, 417),
405                new Rect(111, 443, 206, 480));
406
407        assertTrue(
408                "major axis distance less than major axis distance to "
409                        + "far edge",
410                FocusFinderHelper.majorAxisDistance(View.FOCUS_LEFT,
411                        //       L    T    R    B
412                        new Rect(223, 380, 312, 417),
413                        new Rect(102, 380, 210, 417)) <
414                FocusFinderHelper.majorAxisDistanceToFarEdge(View.FOCUS_LEFT,
415                        //       L    T    R    B
416                        new Rect(223, 380, 312, 417),
417                        new Rect(111, 443, 206, 480)));
418    }
419
420    @SmallTest
421    public void testGmailScenarioBug1203288() {
422        assertBetterCandidate(View.FOCUS_DOWN,
423                //       L    T    R    B
424                new Rect(0,   2,   480, 82),   // src
425                new Rect(344, 87,  475, 124),  // better
426                new Rect(0,   130, 480, 203)); // worse
427    }
428
429    @SmallTest
430    public void testHomeShortcutScenarioBug1295354() {
431        assertBetterCandidate(View.FOCUS_RIGHT,
432                //       L    T    R    B
433                new Rect(3, 338, 77, 413),   // src
434                new Rect(163, 338, 237, 413),  // better
435                new Rect(83, 38, 157, 113)); // worse
436    }
437
438    @SmallTest
439    public void testBeamAlwaysBeatsHoriz() {
440        assertBetterCandidate(View.FOCUS_RIGHT,
441                //       L    T    R    B
442                new Rect(0,   0,   50,  50),   // src
443                new Rect(150, 0,   200, 50),   // better, (way further, but in beam)
444                new Rect(60,  51,  110, 101)); // worse, even though it is closer
445
446        assertBetterCandidate(View.FOCUS_LEFT,
447                //       L    T    R    B
448                new Rect(150, 0,   200,  50),   // src
449                new Rect(0,   50,  50,   50),   // better, (way further, but in beam)
450                new Rect(49,  99,  149,  101)); // worse, even though it is closer
451    }
452
453    @SmallTest
454    public void testIsCandidateOverlappingEdgeFromEmptyRect() {
455        assertDirectionIsCandidate(View.FOCUS_DOWN,
456                //       L   T    R    B
457                new Rect(0,  0,   0,   0),   // src
458                new Rect(0,  0,   20,  1));  // candidate
459
460        assertDirectionIsCandidate(View.FOCUS_UP,
461                //       L   T    R    B
462                new Rect(0,  0,   0,   0),   // src
463                new Rect(0,  -1,  20,  0));  // candidate
464
465        assertDirectionIsCandidate(View.FOCUS_LEFT,
466                //       L   T    R    B
467                new Rect(0,  0,   0,   0),    // src
468                new Rect(-1,  0,  0,   20));  // candidate
469
470        assertDirectionIsCandidate(View.FOCUS_RIGHT,
471                //       L   T    R    B
472                new Rect(0,  0,   0,   0),    // src
473                new Rect(0,  0,   1,   20));  // candidate
474    }
475
476    private void assertBeamsOverlap(int direction, Rect rect1, Rect rect2) {
477        String directionStr = validateAndGetStringFor(direction);
478        String assertMsg = String.format("Expected beams to overlap in direction %s "
479                + "for rectangles %s and %s", directionStr, rect1, rect2);
480        assertTrue(assertMsg, mFocusFinder.beamsOverlap(direction, rect1, rect2));
481    }
482
483    private void assertBeamsDontOverlap(int direction, Rect rect1, Rect rect2) {
484        String directionStr = validateAndGetStringFor(direction);
485        String assertMsg = String.format("Expected beams not to overlap in direction %s "
486                + "for rectangles %s and %s", directionStr, rect1, rect2);
487        assertFalse(assertMsg, mFocusFinder.beamsOverlap(direction, rect1, rect2));
488    }
489
490    /**
491     * Assert that particular rect is a better focus search candidate from a
492     * source rect than another.
493     * @param direction The direction of focus search.
494     * @param srcRect The src rectangle.
495     * @param expectedBetter The candidate that should be better.
496     * @param expectedWorse The candidate that should be worse.
497     */
498    private void assertBetterCandidate(int direction, Rect srcRect,
499            Rect expectedBetter, Rect expectedWorse) {
500
501        String directionStr = validateAndGetStringFor(direction);
502        String assertMsg = String.format(
503                "expected %s to be a better focus search candidate than "
504                        + "%s when searching "
505                        + "from %s in direction %s",
506                expectedBetter, expectedWorse, srcRect, directionStr);
507
508        assertTrue(assertMsg,
509                mFocusFinder.isBetterCandidate(direction, srcRect,
510                        expectedBetter, expectedWorse));
511
512        assertMsg = String.format(
513                "expected %s to not be a better focus search candidate than "
514                        + "%s when searching "
515                        + "from %s in direction %s",
516                expectedWorse, expectedBetter, srcRect, directionStr);
517
518        assertFalse(assertMsg,
519                mFocusFinder.isBetterCandidate(direction, srcRect,
520                        expectedWorse, expectedBetter));
521    }
522
523    private void assertIsNotCandidate(int direction, Rect src, Rect dest) {
524        String directionStr = validateAndGetStringFor(direction);
525
526        final String assertMsg = String.format(
527                "expected going from %s to %s in direction %s to be an invalid "
528                        + "focus search candidate",
529                src, dest, directionStr);
530        assertFalse(assertMsg, mFocusFinder.isCandidate(src, dest, direction));
531    }
532
533    private void assertBeamBeats(int direction, Rect srcRect,
534            Rect rect1, Rect rect2) {
535
536        String directionStr = validateAndGetStringFor(direction);
537        String assertMsg = String.format(
538                "expecting %s to beam beat %s w.r.t %s in direction %s",
539                rect1, rect2, srcRect, directionStr);
540        assertTrue(assertMsg, mFocusFinder.beamBeats(direction, srcRect, rect1, rect2));
541    }
542
543
544    private void assertDirectionIsCandidate(int direction, Rect src, Rect dest) {
545        String directionStr = validateAndGetStringFor(direction);
546
547        final String assertMsg = String.format(
548                "expected going from %s to %s in direction %s to be a valid "
549                        + "focus search candidate",
550                src, dest, directionStr);
551        assertTrue(assertMsg, mFocusFinder.isCandidate(src, dest, direction));
552    }
553
554    private String validateAndGetStringFor(int direction) {
555        String directionStr = "??";
556        switch(direction) {
557            case View.FOCUS_UP:
558                directionStr = "FOCUS_UP";
559                break;
560            case View.FOCUS_DOWN:
561                directionStr = "FOCUS_DOWN";
562                break;
563            case View.FOCUS_LEFT:
564                directionStr = "FOCUS_LEFT";
565                break;
566            case View.FOCUS_RIGHT:
567                directionStr = "FOCUS_RIGHT";
568                break;
569            default:
570                fail("passed in unknown direction, ya blewit!");
571        }
572        return directionStr;
573    }
574
575
576}
577