summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHyungtae Kim <hyungtaekim@google.com>2017-08-08 15:06:09 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2017-08-08 15:06:09 +0000
commitb8f1a6bb79b4897840a4509a4612fbdfd03210c5 (patch)
treeba35b141f931904d310b6c9ce65132aa9c5a0d8f
parentb8db6fd40167ba4b3a29d9eb18ec80a302b0ddcc (diff)
parentb1b5f173ba0c730c8eba35ad050cd06557f9ef91 (diff)
downloadplatform_testing-b8f1a6bb79b4897840a4509a4612fbdfd03210c5.tar.gz
Merge "Update TV Launcher strategy" into oc-mr1-dev
-rw-r--r--libraries/launcher-helper/src/android/support/test/launcherhelper/TvLauncherStrategy.java423
1 files changed, 373 insertions, 50 deletions
diff --git a/libraries/launcher-helper/src/android/support/test/launcherhelper/TvLauncherStrategy.java b/libraries/launcher-helper/src/android/support/test/launcherhelper/TvLauncherStrategy.java
index 610fdbba0..99dddd003 100644
--- a/libraries/launcher-helper/src/android/support/test/launcherhelper/TvLauncherStrategy.java
+++ b/libraries/launcher-helper/src/android/support/test/launcherhelper/TvLauncherStrategy.java
@@ -42,10 +42,15 @@ public class TvLauncherStrategy implements ILeanbackLauncherStrategy {
private static final String LOG_TAG = TvLauncherStrategy.class.getSimpleName();
private static final String PACKAGE_LAUNCHER = "com.google.android.tvlauncher";
+ private static final String PACKAGE_SETTINGS = "com.android.tv.settings";
- private static final int APP_LAUNCH_TIMEOUT = 10000;
- private static final int SHORT_WAIT_TIME = 5000; // 5 sec
- private static final int UI_TRANSITION_WAIT_TIME = 1000;
+ private static final String CHANNEL_TITLE_WATCH_NEXT = "Watch Next";
+
+ // Wait time
+ private static final int UI_APP_LAUNCH_WAIT_TIME_MS = 10000;
+ private static final int UI_WAIT_TIME_MS = 5000;
+ private static final int UI_TRANSITION_WAIT_TIME_MS = 1000;
+ private static final int NO_WAIT = 0;
// Note that the selector specifies criteria for matching an UI element from/to a focused item
private static final BySelector SELECTOR_TOP_ROW = By.res(PACKAGE_LAUNCHER, "top_row");
@@ -56,11 +61,61 @@ public class TvLauncherStrategy implements ILeanbackLauncherStrategy {
By.res(PACKAGE_LAUNCHER, "channel_logo").focused(true).descContains("Apps");
private static final BySelector SELECTOR_CONFIG_CHANNELS_ROW =
By.res(PACKAGE_LAUNCHER, "configure_channels_row");
+ private static final BySelector SELECTOR_CONTROLLER_MOVE = By.res(PACKAGE_LAUNCHER, "move");
+ private static final BySelector SELECTOR_CONTROLLER_REMOVE = By.res(PACKAGE_LAUNCHER, "remove");
+ private static final BySelector SELECTOR_NOTIFICATIONS_ROW = By.res(PACKAGE_LAUNCHER,
+ "notifications_row");
protected UiDevice mDevice;
protected DPadUtil mDPadUtil;
private Instrumentation mInstrumentation;
+ /** A {@link UiCondition} is a condition to be satisfied by BaseView or UI actions. */
+ public interface UiCondition {
+ boolean apply(UiObject2 focus);
+ }
+
+ /**
+ * State of an item in Apps row or channel row on the Home Screen.
+ */
+ public enum HomeRowState {
+ /**
+ * State of a row when this or some other items in Apps row or channel row is not selected
+ */
+ DEFAULT,
+ /**
+ * State of a row when this or some other items in Apps row or channel row is selected.
+ */
+ SELECTED,
+ /**
+ * State of an item when one of the zoomed out states is focused:
+ * zoomed_out, channel_actions, move
+ */
+ ZOOMED_OUT
+ }
+
+ /**
+ * State of an item in the HomeAppState.ZOOMED_OUT mode
+ */
+ public enum HomeControllerState {
+ /**
+ * Default state of an app. one of the program cards or non-channel rows is selected
+ */
+ DEFAULT,
+ /**
+ * One of the channel logos is selected, the channel title is zoomed out
+ */
+ CHANNEL_LOGO,
+ /**
+ * State when a channel is selected and showing channel actions (remove and move).
+ */
+ CHANNEL_ACTIONS,
+ /**
+ * State when a channel is being moved.
+ */
+ MOVE_CHANNEL
+ }
+
/**
* A TvLauncherUnsupportedOperationException is an exception specific to TV Launcher. This will
* be thrown when the feature/method is not available on the TV Launcher.
@@ -85,7 +140,6 @@ public class TvLauncherStrategy implements ILeanbackLauncherStrategy {
/**
* {@inheritDoc}
*/
- // TODO(hyungtaekim): Move this common implementation to abstract class for TV launchers
@Override
public void setUiDevice(UiDevice uiDevice) {
mDevice = uiDevice;
@@ -101,7 +155,7 @@ public class TvLauncherStrategy implements ILeanbackLauncherStrategy {
if (!mDevice.hasObject(getWorkspaceSelector())) {
mDPadUtil.pressHome();
// ensure launcher is shown
- if (!mDevice.wait(Until.hasObject(getWorkspaceSelector()), SHORT_WAIT_TIME)) {
+ if (!mDevice.wait(Until.hasObject(getWorkspaceSelector()), UI_WAIT_TIME_MS)) {
// HACK: dump hierarchy to logcat
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
@@ -128,13 +182,36 @@ public class TvLauncherStrategy implements ILeanbackLauncherStrategy {
*/
@Override
public UiObject2 openAllApps(boolean longpress) {
- if (longpress) {
- mDPadUtil.longPressKeyCode(KeyEvent.KEYCODE_HOME);
- } else {
- Assert.assertNotNull("Could not find all apps logo", selectAppsLogo());
- mDPadUtil.pressDPadCenter();
+ if (!mDevice.hasObject(getAllAppsSelector())) {
+ if (longpress) {
+ mDPadUtil.longPressKeyCode(KeyEvent.KEYCODE_HOME);
+ } else {
+ Assert.assertNotNull("Could not find all apps logo", selectAllAppsLogo());
+ mDPadUtil.pressDPadCenter();
+ }
}
- return mDevice.wait(Until.findObject(getAllAppsSelector()), SHORT_WAIT_TIME);
+ return mDevice.wait(Until.findObject(getAllAppsSelector()), UI_WAIT_TIME_MS);
+ }
+
+ public boolean openSettings() {
+ Assert.assertNotNull(selectTopRow());
+ Assert.assertNotNull(selectBidirect(By.res(getSupportedLauncherPackage(), "settings"),
+ Direction.RIGHT));
+ mDPadUtil.pressDPadCenter();
+ return mDevice.wait(
+ Until.hasObject(By.res(PACKAGE_SETTINGS, "decor_title").text("Settings")),
+ UI_WAIT_TIME_MS);
+ }
+
+ public boolean openCustomizeChannels() {
+ Assert.assertNotNull(selectCustomizeChannelsRow());
+ Assert.assertNotNull(
+ select(By.res(getSupportedLauncherPackage(), "button"), Direction.RIGHT,
+ UI_WAIT_TIME_MS));
+ mDPadUtil.pressDPadCenter();
+ return mDevice.wait(
+ Until.hasObject(By.res(PACKAGE_LAUNCHER, "decor_title").text("Customize channels")),
+ UI_WAIT_TIME_MS);
}
/**
@@ -194,7 +271,7 @@ public class TvLauncherStrategy implements ILeanbackLauncherStrategy {
* Returns a {@link BySelector} describing a given favorite app
*/
public BySelector getFavoriteAppSelector(String appName) {
- return By.res(getSupportedLauncherPackage(), "favorite_app_banner").text(appName);
+ return By.res(getSupportedLauncherPackage(), "favorite_app_banner").desc(appName);
}
/**
@@ -204,11 +281,107 @@ public class TvLauncherStrategy implements ILeanbackLauncherStrategy {
return By.res(getSupportedLauncherPackage(), "app_title").text(appName);
}
+ // Return a {@link BySelector} indicating a channel logo (in either zoom-in or default mode)
+ public BySelector getChannelLogoSelector() {
+ return By.res(getSupportedLauncherPackage(), "channel_logo");
+ }
+ public BySelector getChannelLogoSelector(String channelTitle) {
+ return getChannelLogoSelector().desc(channelTitle);
+ }
+
+ // Return the list of rows including "top_row", "apps_row", "channel"
+ // and "configure_channels_row"
+ public BySelector getRowListSelector() {
+ return By.res(getSupportedLauncherPackage(), "home_row_list");
+ }
+
+ public HomeRowState getHomeRowState() {
+ HomeRowState state = HomeRowState.DEFAULT;
+ if (isAppsRowSelected() || isChannelRowSelected()) {
+ if (getHomeControllerState() != HomeControllerState.DEFAULT) {
+ state = HomeRowState.ZOOMED_OUT;
+ } else {
+ state = HomeRowState.SELECTED;
+ }
+ }
+ Log.d(LOG_TAG, String.format("[HomeRowState]%s", state));
+ return state;
+ }
+
+ public HomeControllerState getHomeControllerState() {
+ HomeControllerState state = HomeControllerState.DEFAULT;
+ UiObject2 focus = findFocus();
+ if (focus.hasObject(getChannelLogoSelector())) {
+ state = HomeControllerState.CHANNEL_LOGO;
+ } else if (focus.hasObject(SELECTOR_CONTROLLER_MOVE)) {
+ state = HomeControllerState.MOVE_CHANNEL;
+ } else if (focus.hasObject(SELECTOR_CONTROLLER_REMOVE)) {
+ state = HomeControllerState.CHANNEL_ACTIONS;
+ }
+ Log.d(LOG_TAG, String.format("[HomeControllerState]%s", state));
+ return state;
+ }
+
+ // Return an index of a focused app or program in the Row. 0-based.
+ public int getFocusedItemIndexInRow() {
+ UiObject2 focusedChannel = mDevice.wait(Until.findObject(
+ By.res(getSupportedLauncherPackage(), "items_list")
+ .hasDescendant(By.focused(true))), UI_WAIT_TIME_MS);
+ if (focusedChannel == null) {
+ Log.w(LOG_TAG, "getFocusedItemIndexInRow: no channel has a focused item. "
+ + "A focus may be at a logo or the top row.");
+ return -1;
+ }
+ int index = 0;
+ for (UiObject2 program : focusedChannel.getChildren()) {
+ if (findFocus(program, NO_WAIT) != null) {
+ break;
+ }
+ ++index;
+ }
+ Log.d(LOG_TAG, String.format("getFocusedItemIndexInRow [index]%d", index));
+ return index;
+ }
+
+ /**
+ * Return true if any item in Channel row is selected. eg, program, zoomed out, channel actions
+ */
+ public boolean isChannelRowSelected(String channelTitle) {
+ return isChannelRowSelected(getChannelLogoSelector(channelTitle));
+ }
+ public boolean isChannelRowSelected() {
+ return isChannelRowSelected(getChannelLogoSelector());
+ }
+ protected boolean isChannelRowSelected(final BySelector channelSelector) {
+ UiObject2 rowList = mDevice.findObject(getRowListSelector());
+ for (UiObject2 row : rowList.getChildren()) {
+ if (findFocus(row, NO_WAIT) != null) {
+ return row.hasObject(channelSelector);
+ }
+ }
+ return false;
+ }
+
+ public boolean isOnHomeScreen() {
+ if (!isAppOpen(getSupportedLauncherPackage())) {
+ Log.w(LOG_TAG, "This launcher is not in foreground");
+ }
+ return mDevice.hasObject(getWorkspaceSelector());
+ }
+
+ public boolean isFirstAppSelected() {
+ if (!isAppsRowSelected()) {
+ return false;
+ }
+ return (getFocusedItemIndexInRow() == 0);
+ }
+
/**
* {@inheritDoc}
*/
@Override
public long launch(String appName, String packageName) {
+ Log.d(LOG_TAG, String.format("launching [name]%s [package]%s", appName, packageName));
return launchApp(this, appName, packageName, isGame(packageName));
}
@@ -237,18 +410,76 @@ public class TvLauncherStrategy implements ILeanbackLauncherStrategy {
*/
@Override
public UiObject2 selectAppsRow() {
- return selectBidirect(getAppsRowSelector().hasDescendant(By.focused(true)),
- Direction.DOWN);
+ return selectAppsRow(false);
}
- public UiObject2 selectChannelsRow(String channelName) {
- // TODO:
- return null;
+ public UiObject2 selectAppsRow(boolean useHomeKey) {
+ Log.d(LOG_TAG, "selectAppsRow");
+ if (!isOnHomeScreen()) {
+ Log.w(LOG_TAG, "selectAppsRow should be called on Home screen");
+ open();
+ }
+
+ if (useHomeKey) {
+ // Press the HOME key to move a focus to the first app in the Apps row.
+ mDPadUtil.pressHome();
+ } else {
+ selectBidirect(getAppsRowSelector().hasDescendant(By.focused(true)),
+ Direction.DOWN);
+ }
+ return isAppsRowSelected() ? findFocus() : null;
}
- public UiObject2 selectAppsLogo() {
- Assert.assertNotNull("Could not find all apps row", selectAppsRow());
- return selectBidirect(getAllAppsLogoSelector().hasDescendant(By.focused(true)),
+ /**
+ * Select a channel row that matches a given name.
+ */
+ public UiObject2 selectChannelRow(final String channelTitle) {
+ Log.d(LOG_TAG, String.format("selectChannelRow [channel]%s", channelTitle));
+
+ // Move out if any channel action button (eg, remove, move) is focused, so that
+ // it can scroll vertically to find a given row.
+ selectBidirect(
+ new UiCondition() {
+ @Override
+ public boolean apply(UiObject2 focus) {
+ HomeControllerState state = getHomeControllerState();
+ return !(state == HomeControllerState.CHANNEL_ACTIONS
+ || state == HomeControllerState.MOVE_CHANNEL);
+ }
+ }, Direction.RIGHT);
+
+ // Then scroll vertically to find a given row
+ UiObject2 focused = selectBidirect(
+ new UiCondition() {
+ @Override
+ public boolean apply(UiObject2 focus) {
+ return isChannelRowSelected(channelTitle);
+ }
+ }, Direction.DOWN);
+ return focused;
+ }
+
+ /**
+ * Select the All Apps logo (or icon).
+ */
+ public UiObject2 selectAllAppsLogo() {
+ Log.d(LOG_TAG, "selectAllAppsLogo");
+ return selectChannelLogo("Apps");
+ }
+
+ public UiObject2 selectChannelLogo(final String channelTitle) {
+ Log.d(LOG_TAG, String.format("selectChannelLogo [channel]%s", channelTitle));
+
+ if (!isChannelRowSelected(channelTitle)) {
+ Assert.assertNotNull(selectChannelRow(channelTitle));
+ }
+ return selectBidirect(
+ new UiCondition() {
+ @Override
+ public boolean apply(UiObject2 focus) {
+ return getHomeControllerState() == HomeControllerState.CHANNEL_LOGO;
+ }
+ },
Direction.LEFT);
}
@@ -257,17 +488,54 @@ public class TvLauncherStrategy implements ILeanbackLauncherStrategy {
* @return
*/
public UiObject2 selectTopRow() {
- return select(getSearchRowSelector().hasDescendant(By.focused(true)),
- Direction.UP, UI_TRANSITION_WAIT_TIME);
+ open();
+ mDPadUtil.pressHome();
+ // Move up until it reaches the top.
+ int maxAttempts = 3;
+ while (maxAttempts-- > 0 && move(Direction.UP)) {
+ SystemClock.sleep(UI_TRANSITION_WAIT_TIME_MS);
+ }
+ return mDevice.wait(
+ Until.findObject(getSearchRowSelector().hasDescendant(By.focused(true))),
+ UI_TRANSITION_WAIT_TIME_MS);
+ }
+
+ /**
+ * Returns a {@link UiObject2} describing the Notification row on TV Launcher
+ * @return
+ */
+ public UiObject2 selectNotificationRow() {
+ return selectBidirect(By.copy(SELECTOR_NOTIFICATIONS_ROW).hasDescendant(By.focused(true)),
+ Direction.UP);
}
/**
- * Returns a {@link UiObject2} describing the Config Channels row on TV Launcher
+ * Returns a {@link UiObject2} describing the customize channel row on TV Launcher
* @return
*/
- public UiObject2 selectConfigChannelsRow() {
- return select(SELECTOR_CONFIG_CHANNELS_ROW.hasDescendant(By.focused(true)),
- Direction.DOWN, UI_TRANSITION_WAIT_TIME);
+ public UiObject2 selectCustomizeChannelsRow() {
+ return select(By.copy(SELECTOR_CONFIG_CHANNELS_ROW).hasDescendant(By.focused(true)),
+ Direction.DOWN, UI_TRANSITION_WAIT_TIME_MS);
+ }
+
+ public UiObject2 selectWatchNextRow() {
+ return selectChannelRow(CHANNEL_TITLE_WATCH_NEXT);
+ }
+
+ /**
+ * Select the first app icon in the Apps row
+ */
+ public UiObject2 selectFirstAppIcon() {
+ if (!isFirstAppSelected()) {
+ Assert.assertNotNull("The Apps row must be selected.",
+ selectAppsRow(/*useHomeKey*/ true));
+ mDPadUtil.pressBack();
+ if (getHomeRowState() == HomeRowState.ZOOMED_OUT) {
+ mDPadUtil.pressDPadRight();
+ }
+ }
+ Assert.assertTrue("The first app in Apps row must be selected.", isFirstAppSelected());
+ return findFocus();
}
/**
@@ -294,8 +562,8 @@ public class TvLauncherStrategy implements ILeanbackLauncherStrategy {
UiObject2 focused = null;
UiObject2 expected = null;
while (attempts++ < maxScrollAttempts) {
- focused = mDevice.wait(Until.findObject(By.focused(true)), SHORT_WAIT_TIME);
- expected = mDevice.wait(Until.findObject(appSelector), SHORT_WAIT_TIME);
+ focused = mDevice.wait(Until.findObject(By.focused(true)), UI_WAIT_TIME_MS);
+ expected = mDevice.wait(Until.findObject(appSelector), UI_WAIT_TIME_MS);
if (expected == null) {
mDPadUtil.pressDPadDown();
@@ -341,11 +609,12 @@ public class TvLauncherStrategy implements ILeanbackLauncherStrategy {
* down to the next row like a zigzag pattern until it founds a given app.
*/
public UiObject2 selectAppInAllAppsZigZag(BySelector appSelector, String packageName) {
+ openAllApps(true);
Direction direction = Direction.RIGHT;
- UiObject2 app = select(appSelector, direction, UI_TRANSITION_WAIT_TIME);
+ UiObject2 app = select(appSelector, direction, UI_TRANSITION_WAIT_TIME_MS);
while (app == null && move(Direction.DOWN)) {
direction = Direction.reverse(direction);
- app = select(appSelector, direction, UI_TRANSITION_WAIT_TIME);
+ app = select(appSelector, direction, UI_TRANSITION_WAIT_TIME_MS);
}
if (app != null) {
Log.i(LOG_TAG, String.format("The app %s is selected", packageName));
@@ -353,6 +622,30 @@ public class TvLauncherStrategy implements ILeanbackLauncherStrategy {
return app;
}
+ /**
+ * Launch the given app in the Apps view.
+ */
+ public boolean launchAppInAppsView(String appName, String packageName) {
+ Log.d(LOG_TAG, String.format("launching in apps view [appName]%s [packageName]%s",
+ appName, packageName));
+ openAllApps(true);
+ UiObject2 app = selectAppInAllAppsZigZag(getAppInAppsViewSelector(appName), packageName);
+ if (app == null) {
+ throw new RuntimeException(
+ "Failed to navigate to the app icon in the Apps view: " + packageName);
+ }
+
+ // The app icon is already found and focused. Then wait for it to open.
+ BySelector appMain = By.pkg(packageName).depth(0);
+ mDPadUtil.pressDPadCenter();
+ if (!mDevice.wait(Until.hasObject(appMain), UI_APP_LAUNCH_WAIT_TIME_MS)) {
+ Log.w(LOG_TAG, String.format(
+ "No UI element with package name %s detected.", packageName));
+ return false;
+ }
+ return true;
+ }
+
protected long launchApp(ILauncherStrategy launcherStrategy, String appName,
String packageName, boolean isGame) {
unlockDeviceIfAsleep();
@@ -371,7 +664,7 @@ public class TvLauncherStrategy implements ILeanbackLauncherStrategy {
UiObject2 app = null;
BySelector favAppSelector = getFavoriteAppSelector(appName);
if (mDevice.hasObject(favAppSelector)) {
- app = selectBidirect(By.focused(true).hasDescendant(favAppSelector), Direction.RIGHT);
+ app = selectBidirect(By.copy(favAppSelector).focused(true), Direction.RIGHT);
} else {
openAllApps(true);
// Find app in Apps View in zigzag mode with app selector for Apps View
@@ -385,9 +678,10 @@ public class TvLauncherStrategy implements ILeanbackLauncherStrategy {
// The app icon is already found and focused. Then wait for it to open.
long ready = SystemClock.uptimeMillis();
+ BySelector appMain = By.pkg(packageName).depth(0);
mDPadUtil.pressDPadCenter();
if (packageName != null) {
- if (!mDevice.wait(Until.hasObject(By.pkg(packageName).depth(0)), APP_LAUNCH_TIMEOUT)) {
+ if (!mDevice.wait(Until.hasObject(appMain), UI_APP_LAUNCH_WAIT_TIME_MS)) {
Log.w(LOG_TAG, String.format(
"No UI element with package name %s detected.", packageName));
return ILauncherStrategy.LAUNCH_FAILED_TIMESTAMP;
@@ -480,25 +774,35 @@ public class TvLauncherStrategy implements ILeanbackLauncherStrategy {
* @param timeoutMs timeout in milliseconds to select
* @return a UiObject2 which represents the matched element
*/
- public UiObject2 select(BySelector selector, Direction direction, long timeoutMs) {
- UiObject2 focus = mDevice.wait(Until.findObject(By.focused(true)), SHORT_WAIT_TIME);
- while (!mDevice.wait(Until.hasObject(selector), timeoutMs)) {
- Log.d(LOG_TAG, String.format("select: moving a focus from %s to %s", focus, direction));
+ public UiObject2 select(final BySelector selector, Direction direction, long timeoutMs) {
+ return select(new UiCondition() {
+ @Override
+ public boolean apply(UiObject2 focus) {
+ return mDevice.hasObject(selector);
+ }
+ }, direction, timeoutMs);
+ }
+
+ public UiObject2 select(UiCondition condition, Direction direction, long timeoutMs) {
+ UiObject2 focus = findFocus(null, timeoutMs);
+ while (!condition.apply(focus)) {
+ Log.d(LOG_TAG, String.format("conditional select: moving a focus from %s to %s",
+ focus, direction));
UiObject2 focused = focus;
mDPadUtil.pressDPad(direction);
- focus = mDevice.wait(Until.findObject(By.focused(true)), SHORT_WAIT_TIME);
+ focus = findFocus();
// Hack: A focus might be lost in some UI. Take one more step forward.
if (focus == null) {
mDPadUtil.pressDPad(direction);
- focus = mDevice.wait(Until.findObject(By.focused(true)), SHORT_WAIT_TIME);
+ focus = findFocus(null, timeoutMs);
}
// Check if it reaches to an end where it no longer moves a focus to next element
if (focused.equals(focus)) {
- Log.d(LOG_TAG, "select: not found until it reaches to an end.");
+ Log.d(LOG_TAG, "conditional select: not found until it reaches to an end.");
return null;
}
}
- Log.i(LOG_TAG, String.format("select: %s is selected", focus));
+ Log.i(LOG_TAG, String.format("conditional select: selected", focus));
return focus;
}
@@ -507,9 +811,17 @@ public class TvLauncherStrategy implements ILeanbackLauncherStrategy {
*/
public UiObject2 selectBidirect(BySelector selector, Direction direction) {
Log.d(LOG_TAG, String.format("selectBidirect [direction]%s", direction));
- UiObject2 object = select(selector, direction, UI_TRANSITION_WAIT_TIME);
+ UiObject2 object = select(selector, direction, UI_TRANSITION_WAIT_TIME_MS);
if (object == null) {
- object = select(selector, Direction.reverse(direction), UI_TRANSITION_WAIT_TIME);
+ object = select(selector, Direction.reverse(direction), UI_TRANSITION_WAIT_TIME_MS);
+ }
+ return object;
+ }
+
+ public UiObject2 selectBidirect(UiCondition condition, Direction direction) {
+ UiObject2 object = select(condition, direction, UI_WAIT_TIME_MS);
+ if (object == null) {
+ object = select(condition, Direction.reverse(direction), UI_WAIT_TIME_MS);
}
return object;
}
@@ -538,12 +850,29 @@ public class TvLauncherStrategy implements ILeanbackLauncherStrategy {
direction));
}
UiObject2 focus = mDevice.wait(Until.findObject(By.focused(true)),
- UI_TRANSITION_WAIT_TIME);
+ UI_TRANSITION_WAIT_TIME_MS);
mDPadUtil.pressKeyCodeAndWait(keyCode);
return !focus.equals(mDevice.wait(Until.findObject(By.focused(true)),
- UI_TRANSITION_WAIT_TIME));
+ UI_TRANSITION_WAIT_TIME_MS));
+ }
+
+ /**
+ * Return the {@link UiObject2} that has a focused element searching through the entire view
+ * hierarchy.
+ */
+ public UiObject2 findFocus(UiObject2 fromObject, long timeoutMs) {
+ UiObject2 focused;
+ if (fromObject == null) {
+ focused = mDevice.wait(Until.findObject(By.focused(true)), timeoutMs);
+ } else {
+ focused = fromObject.wait(Until.findObject(By.focused(true)), timeoutMs);
+ }
+ return focused;
}
+ public UiObject2 findFocus() {
+ return findFocus(null, UI_WAIT_TIME_MS);
+ }
// Unsupported methods
@@ -573,12 +902,6 @@ public class TvLauncherStrategy implements ILeanbackLauncherStrategy {
@SuppressWarnings("unused")
@Override
- public UiObject2 selectNotificationRow() {
- throw new TvLauncherUnsupportedOperationException("No Notification row");
- }
-
- @SuppressWarnings("unused")
- @Override
public UiObject2 selectSettingsRow() {
throw new TvLauncherUnsupportedOperationException("No Settings row");
}