blob: f17d9c375b620ef883415eed8a9f84ac888184a4 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.rotary;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.fail;
import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertTrue;
import android.graphics.Rect;
import android.view.View;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Most of the tests are copied from {@link android.view.FocusFinderTest}. */
@RunWith(AndroidJUnit4.class)
public class FocusFinderTest {
@Test
public void testPartiallyInDirection() {
final Rect src = new Rect(100, 100, 200, 200);
assertIsPartiallyInDirection(View.FOCUS_LEFT, src,
new Rect(199, 50, 300, 60));
assertIsPartiallyInDirection(View.FOCUS_RIGHT, src,
new Rect(0, 50, 101, 60));
assertIsPartiallyInDirection(View.FOCUS_UP, src,
new Rect(50, 199, 60, 300));
assertIsPartiallyInDirection(View.FOCUS_DOWN, src,
new Rect(50, 0, 60, 101));
}
@Test
public void testInDirection() {
final Rect src = new Rect(100, 100, 200, 200);
// Strictly in the given direction.
assertIsInDirection(View.FOCUS_LEFT, src, new Rect(99, 100, 200, 200));
assertIsInDirection(View.FOCUS_RIGHT, src, new Rect(100, 99, 201, 200));
assertIsInDirection(View.FOCUS_UP, src, new Rect(101, 99, 199, 200));
assertIsInDirection(View.FOCUS_DOWN, src, new Rect(99, 100, 201, 201));
// Loosely in the given direction.
assertIsInDirection(View.FOCUS_LEFT, src, new Rect(0, 0, 50, 50));
assertIsInDirection(View.FOCUS_RIGHT, src, new Rect(250, 50, 300, 150));
assertIsInDirection(View.FOCUS_UP, src, new Rect(250, 50, 300, 150));
assertIsInDirection(View.FOCUS_DOWN, src, new Rect(50, 150, 150, 250));
}
@Test
public void testNotInDirection() {
final Rect src = new Rect(100, 100, 200, 200);
// None of destRect is in the given direction of srcRect.
assertIsNotInDirection(View.FOCUS_LEFT, src, new Rect(100, 300, 150, 400));
assertIsNotInDirection(View.FOCUS_RIGHT, src, new Rect(150, 300, 200, 400));
assertIsNotInDirection(View.FOCUS_UP, src, new Rect(300, 100, 400, 150));
assertIsNotInDirection(View.FOCUS_DOWN, src, new Rect(300, 150, 400, 200));
// destRect is strictly in other direction of srcRect.
assertIsNotInDirection(View.FOCUS_LEFT, src, new Rect(0, 0, 200, 50));
assertIsNotInDirection(View.FOCUS_RIGHT, src, new Rect(100, 300, 300, 400));
assertIsNotInDirection(View.FOCUS_UP, src, new Rect(50, 0, 150, 200));
assertIsNotInDirection(View.FOCUS_DOWN, src, new Rect(50, 100, 150, 300));
}
@Test
public void testBelowNotCandidateForDirectionUp() {
assertIsNotCandidate(View.FOCUS_UP,
new Rect(0, 30, 10, 40), // src (left, top, right, bottom)
new Rect(0, 50, 10, 60)); // dest (left, top, right, bottom)
}
@Test
public void testAboveShareEdgeEdgeOkForDirectionUp() {
final Rect src = new Rect(0, 30, 10, 40);
final Rect dest = new Rect(src);
dest.offset(0, -src.height());
assertEquals(src.top, dest.bottom);
assertDirectionIsCandidate(View.FOCUS_UP, src, dest);
}
@Test
public void testCompletelyContainedNotCandidate() {
assertIsNotCandidate(
View.FOCUS_DOWN,
// L T R B
new Rect(0, 0, 50, 50),
new Rect(0, 1, 50, 49));
}
@Test
public void testContainedWithCommonBottomNotCandidate() {
assertIsNotCandidate(
View.FOCUS_DOWN,
// L T R B
new Rect(0, 0, 50, 50),
new Rect(0, 1, 50, 50));
}
@Test
public void testOverlappingIsCandidateWhenBothEdgesAreInDirection() {
assertDirectionIsCandidate(
View.FOCUS_DOWN,
// L T R B
new Rect(0, 0, 50, 50),
new Rect(0, 1, 50, 51));
}
@Test
public void testTopEdgeOfDestAtOrAboveTopOfSrcNotCandidateForDown() {
assertIsNotCandidate(
View.FOCUS_DOWN,
// L T R B
new Rect(0, 0, 50, 50),
new Rect(0, 0, 50, 51));
assertIsNotCandidate(
View.FOCUS_DOWN,
// L T R B
new Rect(0, 0, 50, 50),
new Rect(0, -1, 50, 51));
}
@Test
public void testSameRectBeamsOverlap() {
final Rect rect = new Rect(0, 0, 20, 20);
assertBeamsOverlap(View.FOCUS_LEFT, rect, rect);
assertBeamsOverlap(View.FOCUS_RIGHT, rect, rect);
assertBeamsOverlap(View.FOCUS_UP, rect, rect);
assertBeamsOverlap(View.FOCUS_DOWN, rect, rect);
}
@Test
public void testOverlapBeamsRightLeftUpToEdge() {
final Rect rect1 = new Rect(0, 0, 20, 20);
final Rect rect2 = new Rect(rect1);
// Just below bottom edge.
rect2.offset(0, rect1.height() - 1);
assertBeamsOverlap(View.FOCUS_LEFT, rect1, rect2);
assertBeamsOverlap(View.FOCUS_RIGHT, rect1, rect2);
// At edge.
rect2.offset(0, 1);
assertBeamsDontOverlap(View.FOCUS_LEFT, rect1, rect2);
assertBeamsDontOverlap(View.FOCUS_RIGHT, rect1, rect2);
// Just beyond.
rect2.offset(0, 1);
assertBeamsDontOverlap(View.FOCUS_LEFT, rect1, rect2);
assertBeamsDontOverlap(View.FOCUS_RIGHT, rect1, rect2);
// Just below top edge.
rect2.set(rect1);
rect2.offset(0, -(rect1.height() - 1));
assertBeamsOverlap(View.FOCUS_LEFT, rect1, rect2);
assertBeamsOverlap(View.FOCUS_RIGHT, rect1, rect2);
// At top edge.
rect2.offset(0, -1);
assertBeamsDontOverlap(View.FOCUS_LEFT, rect1, rect2);
assertBeamsDontOverlap(View.FOCUS_RIGHT, rect1, rect2);
// Just beyond top edge.
rect2.offset(0, -1);
assertBeamsDontOverlap(View.FOCUS_LEFT, rect1, rect2);
assertBeamsDontOverlap(View.FOCUS_RIGHT, rect1, rect2);
}
@Test
public void testOverlapBeamsUpDownUpToEdge() {
final Rect rect1 = new Rect(0, 0, 20, 20);
final Rect rect2 = new Rect(rect1);
// Just short of right edge.
rect2.offset(rect1.width() - 1, 0);
assertBeamsOverlap(View.FOCUS_UP, rect1, rect2);
assertBeamsOverlap(View.FOCUS_DOWN, rect1, rect2);
// At edge.
rect2.offset(1, 0);
assertBeamsDontOverlap(View.FOCUS_UP, rect1, rect2);
assertBeamsDontOverlap(View.FOCUS_DOWN, rect1, rect2);
// Just beyond.
rect2.offset(1, 0);
assertBeamsDontOverlap(View.FOCUS_UP, rect1, rect2);
assertBeamsDontOverlap(View.FOCUS_DOWN, rect1, rect2);
// Just short of left edge.
rect2.set(rect1);
rect2.offset(-(rect1.width() - 1), 0);
assertBeamsOverlap(View.FOCUS_UP, rect1, rect2);
assertBeamsOverlap(View.FOCUS_DOWN, rect1, rect2);
// At edge.
rect2.offset(-1, 0);
assertBeamsDontOverlap(View.FOCUS_UP, rect1, rect2);
assertBeamsDontOverlap(View.FOCUS_DOWN, rect1, rect2);
// Just beyond edge.
rect2.offset(-1, 0);
assertBeamsDontOverlap(View.FOCUS_UP, rect1, rect2);
assertBeamsDontOverlap(View.FOCUS_DOWN, rect1, rect2);
}
@Test
public void testDirectlyAboveTrumpsAboveLeft() {
Rect src = new Rect(0, 50, 20, 70); // src (left, top, right, bottom)
Rect directlyAbove = new Rect(src);
directlyAbove.offset(0, -(1 + src.height()));
Rect aboveLeft = new Rect(src);
aboveLeft.offset(-(1 + src.width()), -(1 + src.height()));
assertBetterCandidate(View.FOCUS_UP, src, directlyAbove, aboveLeft);
}
@Test
public void testAboveInBeamTrumpsSlightlyCloserOutOfBeam() {
Rect src = new Rect(0, 50, 20, 70); // src (left, top, right, bottom)
Rect directlyAbove = new Rect(src);
directlyAbove.offset(0, -(1 + src.height()));
Rect aboveLeft = new Rect(src);
aboveLeft.offset(-(1 + src.width()), -(1 + src.height()));
// Offset directly above a little further up.
directlyAbove.offset(0, -5);
assertBetterCandidate(View.FOCUS_UP, src, directlyAbove, aboveLeft);
}
@Test
public void testOutOfBeamBeatsInBeamUp() {
Rect src = new Rect(0, 0, 50, 50); // (left, top, right, bottom)
Rect aboveLeftOfBeam = new Rect(src);
aboveLeftOfBeam.offset(-(src.width() + 1), -src.height());
assertBeamsDontOverlap(View.FOCUS_UP, src, aboveLeftOfBeam);
Rect aboveInBeam = new Rect(src);
aboveInBeam.offset(0, -src.height());
assertBeamsOverlap(View.FOCUS_UP, src, aboveInBeam);
// In beam wins.
assertBetterCandidate(View.FOCUS_UP, src, aboveInBeam, aboveLeftOfBeam);
// Still wins while aboveInBeam's bottom edge is < out of beams' top.
aboveInBeam.offset(0, -(aboveLeftOfBeam.height() - 1));
assertTrue("aboveInBeam.bottom > aboveLeftOfBeam.top",
aboveInBeam.bottom > aboveLeftOfBeam.top);
assertBetterCandidate(View.FOCUS_UP, src, aboveInBeam, aboveLeftOfBeam);
// Cross the threshold: the out of beam prevails.
aboveInBeam.offset(0, -1);
assertEquals(aboveInBeam.bottom, aboveLeftOfBeam.top);
assertBetterCandidate(View.FOCUS_UP, src, aboveLeftOfBeam, aboveInBeam);
}
/** A non-candidate is not excluded when searching for a better candidate. */
@Test
public void testNonCandidateCanBeBetterCandidate() {
Rect src = new Rect(0, 0, 50, 50); // (left, top, right, bottom)
Rect nonCandidate = new Rect(src);
nonCandidate.offset(src.width() + 1, 0);
assertIsNotCandidate(View.FOCUS_LEFT, src, nonCandidate);
Rect candidate = new Rect(src);
candidate.offset(-(4 * src.width()), 0);
assertDirectionIsCandidate(View.FOCUS_LEFT, src, candidate);
assertBetterCandidate(View.FOCUS_LEFT, src, nonCandidate, candidate);
}
/** Grabbed from {@link android.widget.focus.VerticalFocusSearchTest#testSearchFromMidLeft()} */
@Test
public void testVerticalFocusSearchScenario() {
assertBetterCandidate(View.FOCUS_DOWN,
// L T R B
new Rect(0, 109, 153, 169), // src
new Rect(166, 169, 319, 229), // expected better
new Rect(0, 229, 320, 289)); // expected worse
// Failing test 4/10/2008, the values were tweaked somehow in functional test.
assertBetterCandidate(View.FOCUS_DOWN,
// L T R B
new Rect(0, 91, 153, 133), // src
new Rect(166, 133, 319, 175), // expected better
new Rect(0, 175, 320, 217)); // expected worse
}
/**
* Example: going down from a thin button all the way to the left of a screen where, just below,
* is a very wide button, and just below that, is an equally skinny button all the way to the
* left. We want to make sure any minor axis factor doesn't override the fact that the one below
* in vertical beam should be next focus.
*/
@Test
public void testBeamsOverlapMajorAxisCloserMinorAxisFurther() {
assertBetterCandidate(View.FOCUS_DOWN,
// L T R B
new Rect(0, 0, 100, 100), // src
new Rect(0, 100, 480, 200), // expected better
new Rect(0, 200, 100, 300)); // expected worse
}
/** Real scenario grabbed from song playback screen. */
@Test
public void testMusicPlaybackScenario() {
assertBetterCandidate(View.FOCUS_LEFT,
// L T R B
new Rect(227, 185, 312, 231), // src
new Rect(195, 386, 266, 438), // expected better
new Rect(124, 386, 195, 438)); // expected worse
}
/** More generalized version of {@link #testMusicPlaybackScenario()} */
@Test
public void testOutOfBeamOverlapBeatsOutOfBeamFurtherOnMajorAxis() {
assertBetterCandidate(View.FOCUS_DOWN,
// L T R B
new Rect(0, 0, 50, 50), // src
new Rect(60, 40, 110, 90), // expected better
new Rect(60, 70, 110, 120)); // expected worse
}
/**
* Make sure that going down prefers views that are actually down (and not those next to but
* still a candidate because they are overlapping on the major axis)
*/
@Test
public void testInBeamTrumpsOutOfBeamOverlapping() {
assertBetterCandidate(View.FOCUS_DOWN,
// L T R B
new Rect(0, 0, 50, 50), // src
new Rect(0, 60, 50, 110), // expected better
new Rect(51, 1, 101, 51)); // expected worse
}
@Test
public void testOverlappingBeatsNonOverlapping() {
assertBetterCandidate(View.FOCUS_DOWN,
// L T R B
new Rect(0, 0, 50, 50), // src
new Rect(0, 40, 50, 90), // expected better
new Rect(0, 75, 50, 125)); // expected worse
}
@Test
public void testEditContactScenarioLeftFromDiscardChangesGoesToSaveContactInLandscape() {
assertBetterCandidate(View.FOCUS_LEFT,
// L T R B
new Rect(357, 258, 478, 318), // src
new Rect(2, 258, 100, 318), // better
new Rect(106, 120, 424, 184)); // worse
}
/**
* A dial pad with 9 squares arranged in a grid, no padding, so the edges are equal. See {@link
* android.widget.focus.LinearLayoutGrid}.
*/
@Test
public void testGridWithTouchingEdges() {
assertBetterCandidate(View.FOCUS_DOWN,
// L T R B
new Rect(106, 49, 212, 192), // src
new Rect(106, 192, 212, 335), // better
new Rect(0, 192, 106, 335)); // worse
assertBetterCandidate(View.FOCUS_DOWN,
// L T R B
new Rect(106, 49, 212, 192), // src
new Rect(106, 192, 212, 335), // better
new Rect(212, 192, 318, 335)); // worse
}
@Test
public void testSearchFromEmptyRect() {
assertBetterCandidate(View.FOCUS_DOWN,
// L T R B
new Rect(0, 0, 0, 0), // src
new Rect(0, 0, 320, 45), // better
new Rect(0, 45, 320, 545)); // worse
}
/**
* Reproduce b/1124559, drilling down to actual bug (majorAxisDistance was wrong for direction
* left).
*/
@Test
public void testGmailReplyButtonsScenario() {
assertBetterCandidate(View.FOCUS_LEFT,
// L T R B
new Rect(223, 380, 312, 417), // src
new Rect(102, 380, 210, 417), // better
new Rect(111, 443, 206, 480)); // worse
assertBeamBeats(View.FOCUS_LEFT,
// L T R B
new Rect(223, 380, 312, 417), // src
new Rect(102, 380, 210, 417), // better
new Rect(111, 443, 206, 480)); // worse
assertBeamsOverlap(View.FOCUS_LEFT,
// L T R B
new Rect(223, 380, 312, 417),
new Rect(102, 380, 210, 417));
assertBeamsDontOverlap(View.FOCUS_LEFT,
// L T R B
new Rect(223, 380, 312, 417),
new Rect(111, 443, 206, 480));
assertTrue(
"major axis distance less than major axis distance to far edge",
FocusFinder.majorAxisDistance(View.FOCUS_LEFT,
// L T R B
new Rect(223, 380, 312, 417),
new Rect(102, 380, 210, 417)) <
FocusFinder.majorAxisDistanceToFarEdge(View.FOCUS_LEFT,
// L T R B
new Rect(223, 380, 312, 417),
new Rect(111, 443, 206, 480)));
}
@Test
public void testGmailScenarioBug1203288() {
assertBetterCandidate(View.FOCUS_DOWN,
// L T R B
new Rect(0, 2, 480, 82), // src
new Rect(344, 87, 475, 124), // better
new Rect(0, 130, 480, 203)); // worse
}
@Test
public void testHomeShortcutScenarioBug1295354() {
assertBetterCandidate(View.FOCUS_RIGHT,
// L T R B
new Rect(3, 338, 77, 413), // src
new Rect(163, 338, 237, 413), // better
new Rect(83, 38, 157, 113)); // worse
}
@Test
public void testBeamAlwaysBeatsHoriz() {
assertBetterCandidate(View.FOCUS_RIGHT,
// L T R B
new Rect(0, 0, 50, 50), // src
new Rect(150, 0, 200, 50), // better(way further, but in beam)
new Rect(60, 51, 110, 101)); // worse, even though it's closer
assertBetterCandidate(View.FOCUS_LEFT,
// L T R B
new Rect(150, 0, 200, 50), // src
new Rect(0, 0, 50, 50), // better (way further, but in beam)
new Rect(49, 99, 149, 101)); // worse, even though it's closer
}
@Test
public void testIsCandidateOverlappingEdgeFromEmptyRect() {
assertDirectionIsCandidate(View.FOCUS_DOWN,
// L T R B
new Rect(0, 0, 0, 0), // src
new Rect(0, 0, 20, 1)); // candidate
assertDirectionIsCandidate(View.FOCUS_UP,
// L T R B
new Rect(0, 0, 0, 0), // src
new Rect(0, -1, 20, 0)); // candidate
assertDirectionIsCandidate(View.FOCUS_LEFT,
// L T R B
new Rect(0, 0, 0, 0), // src
new Rect(-1, 0, 0, 20)); // candidate
assertDirectionIsCandidate(View.FOCUS_RIGHT,
// L T R B
new Rect(0, 0, 0, 0), // src
new Rect(0, 0, 1, 20)); // candidate
}
private void assertIsPartiallyInDirection(int direction, Rect src, Rect dest) {
String directionStr = validateAndGetStringFor(direction);
final String assertMsg = String.format(
"expected %s is at least partially in direction %s of %s ",
dest, directionStr, src);
assertTrue(assertMsg, FocusFinder.isPartiallyInDirection(src, dest, direction));
}
private void assertIsInDirection(int direction, Rect src, Rect dest) {
String directionStr = validateAndGetStringFor(direction);
final String assertMsg = String.format("expected part of %s is in direction %s of %s ",
dest, directionStr, src);
assertTrue(assertMsg, FocusFinder.isInDirection(src, dest, direction));
}
private void assertIsNotInDirection(int direction, Rect src, Rect dest) {
String directionStr = validateAndGetStringFor(direction);
final String assertMsg = String.format("expected no part of %s is in direction %s of %s ",
dest, directionStr, src);
assertFalse(assertMsg, FocusFinder.isInDirection(src, dest, direction));
}
private void assertBeamsOverlap(int direction, Rect rect1, Rect rect2) {
String directionStr = validateAndGetStringFor(direction);
String assertMsg = String.format(
"Expected beams to overlap in direction %s for rectangles %s and %s",
directionStr, rect1, rect2);
assertTrue(assertMsg, FocusFinder.beamsOverlap(direction, rect1, rect2));
}
private void assertBeamsDontOverlap(int direction, Rect rect1, Rect rect2) {
String directionStr = validateAndGetStringFor(direction);
String assertMsg = String.format(
"Expected beams not to overlap in direction %s for rectangles %s and %s",
directionStr, rect1, rect2);
assertFalse(assertMsg, FocusFinder.beamsOverlap(direction, rect1, rect2));
}
/**
* Assert that particular rect is a better focus search candidate from a source rect than
* another.
*
* @param direction The direction of focus search
* @param srcRect The src rectangle
* @param expectedBetter The candidate that should be better
* @param expectedWorse The candidate that should be worse
*/
private void assertBetterCandidate(int direction, Rect srcRect, Rect expectedBetter,
Rect expectedWorse) {
String directionStr = validateAndGetStringFor(direction);
String assertMsg = String.format(
"expected %s to be a better focus search candidate than %s when searching from %s"
+ " in direction %s",
expectedBetter, expectedWorse, srcRect, directionStr);
assertTrue(assertMsg,
FocusFinder.isBetterCandidate(direction, srcRect, expectedBetter, expectedWorse));
assertMsg = String.format(
"expected %s to not be a better focus search candidate than %s when searching "
+ "from %s in direction %s",
expectedWorse, expectedBetter, srcRect, directionStr);
assertFalse(assertMsg,
FocusFinder.isBetterCandidate(direction, srcRect, expectedWorse, expectedBetter));
}
private void assertIsNotCandidate(int direction, Rect src, Rect dest) {
String directionStr = validateAndGetStringFor(direction);
final String assertMsg = String.format(
"expected going from %s to %s in direction %s to be an invalid focus search "
+ "candidate",
src, dest, directionStr);
assertFalse(assertMsg, FocusFinder.isCandidate(src, dest, direction));
}
private void assertBeamBeats(int direction, Rect srcRect, Rect rect1, Rect rect2) {
String directionStr = validateAndGetStringFor(direction);
String assertMsg = String.format(
"expecting %s to beam beat %s w.r.t %s in direction %s",
rect1, rect2, srcRect, directionStr);
assertTrue(assertMsg, FocusFinder.beamBeats(direction, srcRect, rect1, rect2));
}
private void assertDirectionIsCandidate(int direction, Rect src, Rect dest) {
String directionStr = validateAndGetStringFor(direction);
final String assertMsg = String.format(
"expected going from %s to %s in direction %s to be a valid focus search candidate",
src, dest, directionStr);
assertTrue(assertMsg, FocusFinder.isCandidate(src, dest, direction));
}
private String validateAndGetStringFor(int direction) {
String directionStr = "??";
switch (direction) {
case View.FOCUS_UP:
directionStr = "FOCUS_UP";
break;
case View.FOCUS_DOWN:
directionStr = "FOCUS_DOWN";
break;
case View.FOCUS_LEFT:
directionStr = "FOCUS_LEFT";
break;
case View.FOCUS_RIGHT:
directionStr = "FOCUS_RIGHT";
break;
default:
fail("passed in unknown direction!");
}
return directionStr;
}
}