blob: b996c5e06486c6ea0d6e38a7f6ae87c65b28f731 [file] [log] [blame]
/*
* Copyright (C) 2016 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 android.platform.test.helpers.tv;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.platform.test.helpers.AbstractLeanbackAppHelper;
import android.platform.test.helpers.DPadHelper;
import android.platform.test.helpers.exceptions.UiTimeoutException;
import android.platform.test.helpers.exceptions.UnknownUiException;
import android.provider.Settings;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.Direction;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.Until;
import android.util.Log;
import java.util.regex.Pattern;
/**
* App helper implementation class for TV Settings
*/
public class SysUiSettingsHelperImpl extends AbstractLeanbackAppHelper {
private static final String LOG_TAG = SysUiSettingsHelperImpl.class.getSimpleName();
private static final String UI_PACKAGE = "com.android.tv.settings";
private static final String ACTION_SETTINGS = Settings.ACTION_SETTINGS;
private static final String RES_PKG_ANDROID = "android";
private static final String RES_TITLE = "title";
private static final String RES_SUMMARY = "summary";
private static final long SHORT_SLEEP_MS = 3000;
public static final int SETTINGS_SYSTEM = 0;
public static final int SETTINGS_SECURE = 1;
public static final int SETTINGS_GLOBAL = 2;
private Context mContext;
public SysUiSettingsHelperImpl(Instrumentation instrumentation) {
super(instrumentation);
mDPadHelper = DPadHelper.getInstance(instrumentation);
mContext = instrumentation.getContext();
}
/**
* {@inheritDoc}
*/
@Override
public String getPackage() {
return UI_PACKAGE;
}
/**
* {@inheritDoc}
*/
@Override
public String getLauncherName() {
throw new UnsupportedOperationException("This method is not supported for Settings");
}
/**
* {@inheritDoc}
*/
@Override
public void open() {
open(ACTION_SETTINGS, SHORT_SLEEP_MS);
}
/**
* Setup expectation: On the launcher home screen.
* <p>
* Launches the desired application and wait for it to begin running before returning.
* </p>
* @param timeoutMs timeout in milliseconds to open an activity
*/
public void open(String action, long timeoutMs) {
launchActivity(action);
if (timeoutMs > 0 && !waitForOpen(timeoutMs)) {
throw new UiTimeoutException(String.format("Timed out to open a target package %s:"
+ " %d(ms)", getPackage(), timeoutMs));
}
}
/**
* {@inheritDoc}
*/
@Override
public void exit() {
mDevice.pressHome();
mDevice.waitForIdle();
}
/**
* {@inheritDoc}
*/
@Override
public void dismissInitialDialogs() {
// Nothing to do.
}
/**
* Setup expectations: The Settings is open.
* <p>
* Selects the main Settings item by title text.
* </p>
* @param title the title string of the setting to find
* @return true if the setting that matches the name is open
*/
public boolean clickSetting(String title) {
UiObject2 setting = findSettingByTitle(title);
if (setting == null) {
return false;
}
mDPadHelper.pressDPadCenterAndWait(Until.newWindow(), SHORT_SLEEP_MS);
return true;
}
/**
* Setup expectations: The Settings is open. Selects the main Settings item by summary text.
* @param summary the summary string of the setting to find
* @return true if the setting that matches the name is open
*/
public boolean clickSettingBySummary(String summary) {
UiObject2 setting = findSettingBySummary(summary);
if (setting == null) {
return false;
}
mDPadHelper.pressDPadCenterAndWait(Until.newWindow(), SHORT_SLEEP_MS);
return true;
}
/**
* Setup expectations: The Settings is open.
* @param title the title string of the setting to find
* @return true if it finds {@link UiObject2} that has a given title text
*/
public boolean hasSettingByTitle(String title) {
return (findSettingByTitle(title) != null);
}
/**
* Setup expectations: The Settings is open.
* @param summary the summary string of the setting to find
* @return true if it finds {@link UiObject2} that has a given summary text
*/
public boolean hasSettingBySummary(String summary) {
return (findSettingBySummary(summary) != null);
}
/**
* Setup expectations: The Settings is open. Finds the setting that matches both
* a given title and summary
* @param title the title string of the setting to find
* @param summary the summary string of the setting to find
* @return true if it finds {@link UiObject2} that has both title and summary text passed.
*/
public boolean hasSettingByTitleAndSummary(String title, String summary) {
return (findSettingByTitleAndSummary(title, summary) != null);
}
/**
* Setup expectations: The Settings is open.
* @param text the name of the setting to find
* @return true if it finds {@link UiObject2} that has a given text either in title or summary
*/
public boolean hasSettingByTitleOrSummary(String text) {
return (findSettingByTitleOrSummary(text) != null);
}
/**
* Setup expectations: The Settings is open. Checks if the switch bar is turned on.
* @param title the name of the setting to check
* @return true if the setting is turned on
*/
public boolean isSwitchBarOn(String title) {
return "ON".equals(getSwitchBarText(title));
}
/**
* Setup expectations: The Settings is open. Checks if the switch bar is turned off.
* @param title the name of the setting to check
* @return true if the setting is turned off
*/
public boolean isSwitchBarOff(String title) {
return "OFF".equals(getSwitchBarText(title));
}
/**
* Setup expectations: The Accessibility Settings is open. Finds the preview text on screen.
* @return true if the preview text is displayed on screen.
*/
public boolean hasPreviewText() {
return mDevice.hasObject(By.res(UI_PACKAGE, "preview_text"));
}
/**
* Setup expectations: The Settings is open.
* @return {@link UiObject2} of the current focused setting
*/
public String getCurrentFocusedSettingTitle() {
return mDevice.wait(Until.findObject(By.focused(true)), SHORT_SLEEP_MS)
.findObject(By.res(RES_PKG_ANDROID, "title")).getText();
}
/**
* Setup expectations: The Settings is open. Returns the summary text of the selected Settings.
* @param title the name of the setting to get the summary text
* @return String of the summary text
*/
public String getSummaryTextByTitle(String title) {
UiObject2 settings = findSettingByTitle(title);
return settings.findObject(By.res(RES_PKG_ANDROID, RES_SUMMARY)).getText();
}
/**
* Setup expectations: The Settings is open.
*
* Exit the guided settings by pressing BACK key a given times
* @param maxDepth The maximum depth to exit the guided settings. Should be greater than 0
* @return true if the Settings is closed.
*/
public boolean goBackGuidedSettings(int maxDepth) {
if (maxDepth < 1) {
Log.w(LOG_TAG, "maxDepth should be greater than 0");
maxDepth = 1;
}
UiObject2 focused = mDevice.wait(Until.findObject(By.focused(true)), SHORT_SLEEP_MS);
if (focused == null) {
throw new IllegalStateException("No focused item is found");
}
while (maxDepth-- > 0) {
mDPadHelper.pressBack();
if (!waitForOpen(SHORT_SLEEP_MS)) {
Log.w(LOG_TAG, "Settings is closed.");
return false;
} else if (focused.equals(
mDevice.wait(Until.findObject(By.focused(true)), SHORT_SLEEP_MS))) {
Log.w(LOG_TAG, "The focused is the same. Nothing happened in Settings?");
return false;
}
}
return true;
}
/**
* Setup expectations: On waiting for a guided setting to open.
* @param title the title string of the guided setting
* @param timeoutMs timeout in milliseconds to get the header title
* @return true if the guided setting that has a given title is open in timeout
*/
public boolean waitForOpenGuidedSetting(String title, long timeoutMs) {
UiObject2 header = mDevice.wait(
Until.findObject(By.res(UI_PACKAGE, "decor_title").text(title)), timeoutMs);
return (header != null);
}
/**
* Setup expectations: The Settings is open.
* @return the title of the guided settings
*/
private String getGuidedSettingTitle() {
UiObject2 header = mDevice.findObject(By.res(UI_PACKAGE, "decor_title"));
if (header == null) {
throw new UnknownUiException("Header text is not found");
}
return header.getText();
}
public String getStringSetting(int type, String name) {
switch (type) {
case SETTINGS_SYSTEM:
return Settings.System.getString(mContext.getContentResolver(), name);
case SETTINGS_GLOBAL:
return Settings.Global.getString(mContext.getContentResolver(), name);
case SETTINGS_SECURE:
return Settings.Secure.getString(mContext.getContentResolver(), name);
}
return "";
}
public int getIntSetting(int type, String name, int def) {
int value = getIntSetting(type, name);
return value != Integer.MIN_VALUE ? value : def;
}
public int getIntSetting(int type, String name) {
try {
switch (type) {
case SETTINGS_SYSTEM:
return Settings.System.getInt(mContext.getContentResolver(), name);
case SETTINGS_GLOBAL:
return Settings.Global.getInt(mContext.getContentResolver(), name);
case SETTINGS_SECURE:
return Settings.Secure.getInt(mContext.getContentResolver(), name);
}
} catch (Settings.SettingNotFoundException e) {
Log.w(LOG_TAG, String.format("Settings not found name=%s, type=%d", name, type));
}
return Integer.MIN_VALUE;
}
public boolean isDeveloperOptionsEnabled() {
return getIntSetting(SETTINGS_GLOBAL, Settings.Global.DEVELOPMENT_SETTINGS_ENABLED,
android.os.Build.TYPE.equals("eng") ? 1 : 0) == 1;
}
/**
* Setup expectations: PIN code activity is open. Set a new PIN code
* @param pinCode the PIN code of 4 digits
* @return true if a PIN code is set
*/
public boolean setNewPinCode(String pinCode) {
return setPinCode("Set a new PIN", pinCode, Direction.DOWN);
}
public boolean reenterPinCode(String pinCode) {
return setPinCode("Re-enter new PIN", pinCode, Direction.DOWN);
}
public boolean enterPinCode(String pinCode) {
return setPinCode("Enter PIN", pinCode, Direction.DOWN);
}
protected BySelector getTitleSelector(String text) {
return By.res(RES_PKG_ANDROID, RES_TITLE).text(text);
}
protected BySelector getSummarySelector(String text) {
return By.res(RES_PKG_ANDROID, RES_SUMMARY).text(text);
}
/**
* Find the focused item in Settings that has a descendant matches the criteria.
* @param selector for a descendant of the setting to match.
* @param direction the direction to find, only accepts UP and DOWN.
* @return {@link UiObject2} of the focusable item that has a given descendant
*/
private UiObject2 findSettingHasDescendant(BySelector selector, Direction direction) {
if (!isAppInForeground()) {
throw new IllegalStateException("Required to open the Settings ahead");
}
if (direction != Direction.DOWN && direction != Direction.UP) {
throw new IllegalArgumentException("Required to go either up or down to find rows");
}
UiObject2 currentFocused = mDevice.findObject(By.focused(true));
UiObject2 prevFocused = null;
UiObject2 found = null;
while (!currentFocused.equals(prevFocused)) {
if ((found = mDevice.findObject(By.focused(true).hasDescendant(selector, 3)))
!= null) {
return found;
}
if (direction == Direction.DOWN) {
mDevice.pressDPadDown();
} else if (direction == Direction.UP) {
mDevice.pressDPadUp();
}
prevFocused = currentFocused;
currentFocused = mDevice.findObject(By.focused(true));
}
Log.d(LOG_TAG, "Failed to find the item until it reaches the end.");
return found;
}
private String getSwitchBarText(String title) {
UiObject2 setting = findSettingByTitle(title);
return setting.findObject(By.res(RES_PKG_ANDROID, "switch_widget")).getText();
}
private UiObject2 findSettingByTitle(String title) {
return findSettingBySelector(getTitleSelector(title), true);
}
private UiObject2 findSettingBySummary(String summary) {
return findSettingBySelector(getSummarySelector(summary), true);
}
private UiObject2 findSettingByTitleAndSummary(String title, String summary) {
BySelector titleSelector = getTitleSelector(title);
BySelector summarySelector = getSummarySelector(summary);
UiObject2 setting;
while ((setting = findSettingHasDescendant(titleSelector, Direction.DOWN)) != null) {
if (setting.hasObject(summarySelector)) {
return setting;
}
}
while ((setting = findSettingHasDescendant(titleSelector, Direction.UP)) != null) {
if (setting.hasObject(summarySelector)) {
return setting;
}
}
return null;
}
private UiObject2 findSettingByTitleOrSummary(String text) {
final Pattern RES_REGEX = Pattern.compile(
String.format("%s:id/(%s|%s)", RES_PKG_ANDROID, RES_TITLE, RES_SUMMARY));
return findSettingBySelector(By.res(RES_REGEX).text(text), true);
}
private UiObject2 findSettingBySelector(BySelector selector, boolean throwIfFail) {
UiObject2 setting;
if ((setting = findSettingHasDescendant(selector, Direction.DOWN)) != null) {
return setting;
}
if ((setting = findSettingHasDescendant(selector, Direction.UP)) != null) {
return setting;
}
if (throwIfFail) {
throw new UnknownUiException(
String.format("No focused setting matches a given selector: %s",
selector.toString()));
}
return null;
}
private boolean setPinCode(String title, String pinCode, Direction direction) {
if (!isValidPinCode(pinCode)) {
throw new IllegalArgumentException("4 digits PIN code is valid. pinCode=" + pinCode);
}
if (direction != Direction.DOWN && direction != Direction.UP) {
throw new IllegalArgumentException("Either up or down is allowed");
}
if (!mDevice.wait(Until.hasObject(By.res(getPackage(), "title").text(title)),
SHORT_SLEEP_MS)) {
throw new IllegalStateException("The title for PIN code not found: " + title);
}
// the PIN number starts from 0 and increases by going down
for (char c : pinCode.toCharArray()) {
int number = c - '0';
// Note that the resource ID for the number changes by the direction to search
String resId = (number == 0) ? "current_number"
: (direction == Direction.DOWN) ? "next_number" : "previous_number";
mDPadHelper.pressDPad(direction, number);
if (!mDevice.wait(
Until.hasObject(By.res(getPackage(), resId).text(String.valueOf(c))),
SHORT_SLEEP_MS)) {
throw new UnknownUiException("Couldn't find the number:" + c);
}
mDPadHelper.pressDPadCenter(); // Move next
}
return true;
}
private boolean isValidPinCode(String pinCode) {
final String PIN_CODE_FORMAT = "[0-9][0-9][0-9][0-9]";
return pinCode != null && pinCode.matches(PIN_CODE_FORMAT);
}
/**
* Setup expectations: None
*
* Starts the Settings activity
*/
// TODO Move to a base or utility class that each test could access
private void launchActivity() {
launchActivity(ACTION_SETTINGS);
}
private void launchActivity(String action) {
Intent intent = new Intent(action);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
Log.d(LOG_TAG, "launchActivity intent=" + intent.toString());
mInstrumentation.getContext().startActivity(intent);
}
}