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