Merge "TV PIP functional tests" into nyc-mr1-dev
diff --git a/tests/functional/tv/TvSysUiTests/AndroidManifest.xml b/tests/functional/tv/TvSysUiTests/AndroidManifest.xml
index be86f89..fa0dd4c 100644
--- a/tests/functional/tv/TvSysUiTests/AndroidManifest.xml
+++ b/tests/functional/tv/TvSysUiTests/AndroidManifest.xml
@@ -18,8 +18,7 @@
package="android.test.functional.tv.sysui"
android:sharedUserId="android.uid.system" >
- <uses-sdk android:minSdkVersion="21"
- android:targetSdkVersion="24"/>
+ <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="24"/>
<application>
<uses-library android:name="android.test.runner" />
@@ -29,4 +28,9 @@
android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.test.functional.tv.sysui"
android:label="TV Platform System UI Functional Tests" />
+
+ <instrumentation
+ android:name="android.test.functional.tv.common.TestSetupInstrumentation"
+ android:targetPackage="android.test.functional.tv.sysui"
+ android:label="TV test setup instrumentation" />
</manifest>
diff --git a/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/common/TestSetupInstrumentation.java b/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/common/TestSetupInstrumentation.java
new file mode 100644
index 0000000..edf03c6
--- /dev/null
+++ b/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/common/TestSetupInstrumentation.java
@@ -0,0 +1,126 @@
+/*
+ * 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.test.functional.tv.common;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.test.functional.tv.settings.MultiUserInRestrictedProfileTests;
+import android.util.Log;
+
+/**
+ * Test setup instrumentation for Functional verification tests
+ *
+ * adb shell am instrument -w -r \
+ * -e restrictedProfile [create | delete | exit] \
+ * -e pinCode <4 digit code> \
+ * android.test.functional.tv.sysui/android.test.functional.tv.common.TestSetupInstrumentation
+ */
+public class TestSetupInstrumentation extends Instrumentation {
+
+ private static final String TAG = TestSetupInstrumentation.class.getSimpleName();
+ private static final String ARGUMENT_SETUP_MODE = "restrictedProfile";
+ private static final String ARGUMENT_PINCODE = "pinCode";
+ private static final String SETUP_MODE_CREATE_RESTRICTED_PROFILE = "create";
+ private static final String SETUP_MODE_DELETE_RESTRICTED_PROFILE = "delete";
+ private static final String SETUP_MODE_EXIT_RESTRICTED_PROFILE = "exit";
+ private static final String PIN_CODE = "1010";
+
+ private Bundle mArguments;
+
+ @Override
+ public void onCreate(Bundle arguments) {
+ super.onCreate(arguments);
+ InstrumentationRegistry.registerInstance(this, arguments);
+ mArguments = arguments;
+ start();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ try {
+ setup();
+ finish(Activity.RESULT_OK, new Bundle());
+ } catch (TestSetupException e) {
+ error(e.getMessage());
+ }
+ }
+
+ private void setup() throws TestSetupException {
+ final String setupMode = mArguments.getString(ARGUMENT_SETUP_MODE, "");
+ if (setupMode == null) {
+ error("Performing no setup actions because " + ARGUMENT_SETUP_MODE
+ + " was not passed as an argument");
+ } else {
+ Log.i(TAG, "Running setup for " + setupMode + " tests.");
+ switch (setupMode) {
+ case SETUP_MODE_CREATE_RESTRICTED_PROFILE:
+ createRestrictedProfile();
+ break;
+ case SETUP_MODE_DELETE_RESTRICTED_PROFILE:
+ deleteRestrictedProfile();
+ break;
+ case SETUP_MODE_EXIT_RESTRICTED_PROFILE:
+ exitRestrictedProfile();
+ break;
+ default:
+ throw new TestSetupException(
+ "Unknown " + ARGUMENT_SETUP_MODE + " of " + setupMode);
+ }
+ }
+ }
+
+ private void createRestrictedProfile() throws TestSetupException {
+ final String pinCode = mArguments.getString(ARGUMENT_PINCODE, PIN_CODE);
+ if (!MultiUserInRestrictedProfileTests.Setup.createRestrictedProfile(this, pinCode, true)) {
+ throw new TestSetupException("Failed to create the restricted profile");
+ }
+ }
+
+ private void deleteRestrictedProfile() throws TestSetupException {
+ final String pinCode = mArguments.getString(ARGUMENT_PINCODE, PIN_CODE);
+ if (!MultiUserInRestrictedProfileTests.Setup.deleteRestrictedProfile(this, pinCode)) {
+ throw new TestSetupException("Failed to delete the restricted profile");
+ }
+ }
+
+ private void exitRestrictedProfile() throws TestSetupException {
+ if (!MultiUserInRestrictedProfileTests.Setup.exitRestrictedProfile(this)) {
+ throw new TestSetupException("Failed to exit the restricted profile");
+ }
+ }
+
+ /**
+ * Provide an error message to the instrumentation result
+ * @param message
+ */
+ public void error(String message) {
+ Log.e(TAG, String.format("error message=%s", message));
+ Bundle output = new Bundle();
+ output.putString("error", message);
+ finish(Activity.RESULT_CANCELED, output);
+ }
+
+ static class TestSetupException extends Exception {
+ public TestSetupException(String msg) {
+ super(msg);
+ }
+ }
+}
+
diff --git a/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/settings/AccessibilitySettingsTests.java b/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/settings/AccessibilitySettingsTests.java
new file mode 100644
index 0000000..9f18835
--- /dev/null
+++ b/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/settings/AccessibilitySettingsTests.java
@@ -0,0 +1,170 @@
+/*
+ * 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.test.functional.tv.settings;
+
+import android.provider.Settings;
+import android.test.functional.tv.common.SysUiTestBase;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Functional verification tests for the caption display on TV.
+ *
+ * adb shell am instrument -w -r \
+ * -e class android.test.functional.tv.settings.AccessibilitySettingsTests \
+ * android.test.functional.tv.sysui/android.support.test.runner.AndroidJUnitRunner
+ */
+public class AccessibilitySettingsTests extends SysUiTestBase {
+ private static final String TAG = AccessibilitySettingsTests.class.getSimpleName();
+ private static final long LOADING_TIMEOUT_MS = 5000; // 5 seconds
+
+ // The following constants are hidden in API 24.
+ private static final String ACCESSIBILITY_CAPTIONING_ENABLED =
+ "accessibility_captioning_enabled";
+ private static final String ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED =
+ "high_text_contrast_enabled";
+
+ private static final String TEXT_ACCESSIBILITY = "Accessibility";
+ private static final String TEXT_CAPTIONS = "Captions";
+ private static final String TEXT_DISPLAY = "Display";
+ private static final String TEXT_HIGHCONTRASTTEXT = "High contrast text";
+ private static final int TIMEOUT_MS = 3000;
+
+ private boolean mHighContrastTextOn = false;
+
+ @Before
+ public void setUp() {
+ mLauncherStrategy.open();
+ }
+
+ @After
+ public void tearDown() {
+ // Clean up: Turn off High contrast text if on.
+ if (mHighContrastTextOn) {
+ mHighContrastTextOn = false;
+ if (mSettingsHelper.isSwitchBarOn(TEXT_HIGHCONTRASTTEXT)) {
+ mSettingsHelper.clickSetting(TEXT_HIGHCONTRASTTEXT);
+ }
+ }
+
+ mSettingsHelper.exit();
+ }
+
+ /**
+ * Objective: Verify the captioning display is enabled.
+ */
+ @Test
+ public void testEnableCaptionDisplay() throws Exception {
+ // Open captions menu
+ mSettingsHelper.open(Settings.ACTION_CAPTIONING_SETTINGS, LOADING_TIMEOUT_MS);
+
+ // Turn off if the option is already turned on
+ if (mSettingsHelper.isSwitchBarOn(TEXT_DISPLAY)) {
+ mSettingsHelper.clickSetting(TEXT_DISPLAY);
+ }
+ if (!mSettingsHelper.isSwitchBarOff(TEXT_DISPLAY)) {
+ throw new IllegalStateException(
+ "The Display setting should be turned off before this test");
+ }
+
+ // Enable the caption display
+ Assert.assertTrue(mSettingsHelper.clickSetting(TEXT_DISPLAY));
+ Assert.assertTrue(mSettingsHelper.isSwitchBarOn(TEXT_DISPLAY));
+
+ // Ensure that the sample text appears and the setting is configured correctly
+ Assert.assertNotNull("Sample text not found!", mSettingsHelper.hasPreviewText());
+ int value = Settings.Secure.getInt(mContext.getContentResolver(),
+ ACCESSIBILITY_CAPTIONING_ENABLED);
+ Assert.assertEquals("Error: Caption display not enabled!", value, 1);
+ }
+
+ /**
+ * Objective: Verify the captioning display is disabled.
+ */
+ @Test
+ public void testDisableCaptionDisplay() throws Exception {
+ // Open captions menu from main Settings activity
+ mSettingsHelper.open();
+ mSettingsHelper.clickSetting(TEXT_ACCESSIBILITY);
+ mSettingsHelper.clickSetting(TEXT_CAPTIONS);
+
+ // Turn on if the option is already turned off
+ if (mSettingsHelper.isSwitchBarOff(TEXT_DISPLAY)) {
+ mSettingsHelper.clickSetting(TEXT_DISPLAY);
+ }
+ if (!mSettingsHelper.isSwitchBarOn(TEXT_DISPLAY)) {
+ throw new IllegalStateException(
+ "The Display setting should be turned on before this test");
+ }
+
+ // Disable the caption display
+ Assert.assertTrue(mSettingsHelper.clickSetting(TEXT_DISPLAY));
+ Assert.assertTrue(mSettingsHelper.isSwitchBarOff(TEXT_DISPLAY));
+
+ // Ensure that the setting is configured correctly
+ int value = Settings.Secure.getInt(mContext.getContentResolver(),
+ ACCESSIBILITY_CAPTIONING_ENABLED);
+ Assert.assertEquals("Error: Caption display not disabled!", value, 0);
+ }
+
+ /**
+ * Objective: Verify that the high contrast text is turned on.
+ */
+ @Test
+ public void testHighContrastTextOn() throws Settings.SettingNotFoundException {
+ // Launch accessibility settings
+ mSettingsHelper.open();
+ Assert.assertTrue(mSettingsHelper.clickSetting(TEXT_ACCESSIBILITY));
+
+ // Turn on High contrast text
+ if (mSettingsHelper.isSwitchBarOff(TEXT_HIGHCONTRASTTEXT)) {
+ Assert.assertTrue(mSettingsHelper.clickSetting(TEXT_HIGHCONTRASTTEXT));
+ }
+ Assert.assertTrue(mSettingsHelper.isSwitchBarOn(TEXT_HIGHCONTRASTTEXT));
+
+ // Ensure that the setting is configured correctly
+ int value = Settings.Secure.getInt(mContext.getContentResolver(),
+ ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED);
+ Assert.assertEquals("Error: High contrast text not enabled!", value, 1);
+ mHighContrastTextOn = true;
+ }
+
+ /**
+ * Objective: Verify that the high contrast text is turned off.
+ */
+ @Test
+ public void testHighContrastTextOff() throws Settings.SettingNotFoundException {
+ // Launch accessibility settings
+ mSettingsHelper.open();
+ Assert.assertTrue(mSettingsHelper.clickSetting(TEXT_ACCESSIBILITY));
+
+ // Turn off High contrast text
+ if (mSettingsHelper.isSwitchBarOn(TEXT_HIGHCONTRASTTEXT)) {
+ Assert.assertTrue(mSettingsHelper.clickSetting(TEXT_HIGHCONTRASTTEXT));
+ }
+ Assert.assertTrue(mSettingsHelper.isSwitchBarOff(TEXT_HIGHCONTRASTTEXT));
+
+ // Ensure that the setting is configured correctly
+ int value = Settings.Secure.getInt(mContext.getContentResolver(),
+ ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED);
+ Assert.assertEquals("Error: High contrast text not disabled!", value, 0);
+ }
+}
+
diff --git a/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/settings/AccountTests.java b/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/settings/AccountTests.java
new file mode 100644
index 0000000..43c6bdd
--- /dev/null
+++ b/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/settings/AccountTests.java
@@ -0,0 +1,351 @@
+/*
+ * 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.test.functional.tv.settings;
+
+import static junit.framework.Assert.fail;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.os.Bundle;
+import android.platform.test.helpers.exceptions.UiTimeoutException;
+import android.support.test.uiautomator.Until;
+import android.test.functional.tv.common.SysUiTestBase;
+import android.util.Log;
+import android.util.Patterns;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Functional verification tests for managing accounts on TV.
+ *
+ * adb shell am instrument -w -r \
+ * -e account <accountName1,accountName2,...> -e password <password1,password2,...> \
+ * -e class android.test.functional.tv.settings.AccountTests \
+ * android.test.functional.tv.sysui/android.support.test.runner.AndroidJUnitRunner
+ */
+public class AccountTests extends SysUiTestBase {
+ private static final String TAG = AccountTests.class.getSimpleName();
+ private static final String ARGUMENT_ACCOUNT = "account";
+ private static final String ARGUMENT_PASSWORD = "password";
+ private static final char SEPARATOR = ','; // used to separate multiple accounts and passwords
+ private static final String GOOGLE_ACCOUNT = "com.google";
+ private static final String DEFAULT_EMAIL_DOMAIN = "@gmail.com";
+ private static final String TITLE_ADD_ACCOUNT = "Add account";
+ private static final String TITLE_REMOVE_ACCOUNT = "Remove account";
+
+ private static final long SHORT_SLEEP_MS = 3000; // 3 seconds
+ private static final long LONG_SLEEP_MS = 30000; // 30 seconds
+
+ private List<String> mAccountNames = new ArrayList<>();
+ private List<String> mPasswords = new ArrayList<>();
+ private AccountManager mAm = null;
+
+
+ public AccountTests() {
+ mAm = AccountManager.get(mContext);
+ parseArguments();
+ }
+
+ @Before
+ public void setUp() {
+ // Remove all accounts
+ removeAccounts();
+ mLauncherStrategy.open();
+ }
+
+ @After
+ public void tearDown() {
+ mSettingsHelper.exit();
+ }
+
+ /**
+ * Objective: Able to sign in with an account when no account on TV, and remove the account
+ */
+ @Test
+ public void testSignInOneAccountAndRemove() {
+ // Log in with the first account info
+ String accountName = getEmailFromAccountName(mAccountNames.get(0));
+ String password = mPasswords.get(0);
+ openSettingsAndLogin(accountName, password);
+
+ // Verify that the login is successful and the account name appears in summary
+ Assert.assertTrue(mSettingsHelper.hasSettingBySummary(accountName));
+ Assert.assertEquals(getAccounts().size(), 1);
+
+ // Remove the account in Settings
+ Assert.assertTrue(
+ mSettingsHelper.clickSettingBySummary(accountName));
+ Assert.assertTrue(mSettingsHelper.clickSetting(TITLE_REMOVE_ACCOUNT));
+ Assert.assertNotNull(mSettingsHelper.selectGuidedAction("OK"));
+ mDPadHelper.pressDPadCenterAndWait(Until.newWindow(), SHORT_SLEEP_MS);
+
+ // Verify that the account is removed
+ Assert.assertEquals(getAccounts().size(), 0);
+ }
+
+ /**
+ * Objective: Verify that user cannot log in the same account
+ */
+ @Test
+ public void testDisallowSignInSameAccount() {
+ // Log in with the first account info
+ String accountName = mAccountNames.get(0);
+ String password = mPasswords.get(0);
+ openSettingsAndLogin(accountName, password);
+ if (getAccounts().size() != 1) {
+ throw new IllegalStateException("The first login was not successful.");
+ }
+
+ // Log in with the account already registered should fail
+ mSettingsHelper.clickSetting(TITLE_ADD_ACCOUNT);
+ mNoTouchAuthHelper.waitForOpen(SHORT_SLEEP_MS);
+ if (mNoTouchAuthHelper.loginAccount(accountName, password)) {
+ fail("The login with the account already registered should be disallowed.");
+ }
+
+ // Verify that the attempt to login with the same account is rejected
+ Assert.assertEquals(getAccounts().size(), 1);
+ }
+
+ /**
+ * Objective: Able to sign in with multiple accounts when no account on TV
+ */
+ @Test
+ public void testSignInWithMultiAccounts() {
+ int accountsCount = mAccountNames.size();
+ if (accountsCount < 2) {
+ throw new IllegalArgumentException("More than one account required");
+ }
+
+ // Log in with multiple accounts
+ for (int i = 0; i < accountsCount; ++i) {
+ openSettingsAndLogin(mAccountNames.get(i), mPasswords.get(i));
+ }
+
+ // Verify that the login with multiple accounts is successful
+ for (int i = accountsCount - 1; i >= 0; --i) {
+ Assert.assertTrue(mSettingsHelper.hasSettingByTitleOrSummary(
+ getEmailFromAccountName(mAccountNames.get(i))));
+ }
+ Assert.assertEquals(getAccounts().size(), accountsCount);
+ }
+
+ /**
+ * Objective: Verify that user can switch accounts in the YouTube app.
+ */
+ @Test
+ public void testSwitchAccountsInYouTube() {
+ // Clean data, and set up two accounts
+ mCmdHelper.executeShellCommand(String.format("pm clear %s", mYouTubeHelper.getPackage()));
+ int accountsCount = mAccountNames.size();
+ if (accountsCount < 2) {
+ throw new IllegalArgumentException("More than one account required");
+ }
+ for (int i = 0; i < accountsCount; ++i) {
+ openSettingsAndLogin(getEmailFromAccountName(mAccountNames.get(i)), mPasswords.get(i));
+ }
+ if (getAccounts().size() != accountsCount) {
+ throw new IllegalStateException(
+ String.format("This test requires to log in with more than one account. "
+ + "%d expected, %d found", accountsCount, getAccounts().size()));
+ }
+
+ // Verify that the login is successful in Settings
+ Assert.assertEquals(getAccounts().size(), accountsCount);
+
+ // Select the first account to log in YouTube
+ mYouTubeHelper.open();
+ // Note that the Sign-in page appears only when no account has been set up.
+ // Once signed in, it would be no longer prompted even after the signout.
+ // Clean app data on top of this test
+ String firstAccount = getEmailFromAccountName(mAccountNames.get(0));
+ Assert.assertTrue(mYouTubeHelper.signIn(firstAccount));
+ mYouTubeHelper.waitForContentLoaded(SHORT_SLEEP_MS);
+
+ // Verify that the account is set up in YouTube
+ Assert.assertEquals(mYouTubeHelper.getSignInUserName(), firstAccount);
+
+ // Sign out
+ mYouTubeHelper.signOut();
+
+ // Open the Setting, switch to the second account
+ mYouTubeHelper.openSettings();
+ mYouTubeHelper.openCardInRow("Sign in");
+ String secondAccount = getEmailFromAccountName(mAccountNames.get(1));
+ Assert.assertTrue(mYouTubeHelper.signIn(secondAccount));
+ mYouTubeHelper.waitForContentLoaded(SHORT_SLEEP_MS);
+
+ // Verify that the account is set up in YouTube
+ Assert.assertEquals(mYouTubeHelper.getSignInUserName(), secondAccount);
+ }
+
+ @Ignore("Not yet implemented")
+ @Test
+ public void testSwitchAccountsInPlayStore() {
+
+ }
+
+ private void openSettingsAndLogin(String accountName, String password) {
+ // Open the sign-in page
+ mSettingsHelper.open();
+ mSettingsHelper.clickSetting(TITLE_ADD_ACCOUNT);
+ mNoTouchAuthHelper.waitForOpen(SHORT_SLEEP_MS);
+
+ // Log in with an account
+ mNoTouchAuthHelper.loginAccount(accountName, password);
+
+ // Wait for it to return to the Settings
+ if (!mSettingsHelper.waitForOpen(LONG_SLEEP_MS)) {
+ throw new UiTimeoutException(
+ "Failed to return to the Settings after attempting to login");
+ }
+ }
+
+ /**
+ * Parse account names and passwords from arguments in following format:
+ * -e account accountName1,accountName2,...
+ * -e password password1,password2,...
+ *
+ * @return list of TestArg data, empty list if input is null
+ */
+ private void parseArguments() {
+ mAccountNames.clear();
+ mPasswords.clear();
+ String accountNamesArg = mArguments.getString(ARGUMENT_ACCOUNT, "");
+ for (String accountName : accountNamesArg.split(String.valueOf(SEPARATOR))) {
+ // The account name needs to be unique
+ if (!"".equals(accountName) && !mAccountNames.contains(accountName)) {
+ mAccountNames.add(accountName);
+ }
+ }
+ String passwordsArg = mArguments.getString(ARGUMENT_PASSWORD, "");
+ for (String password : passwordsArg.split(String.valueOf(SEPARATOR))) {
+ if (!"".equals(password)) {
+ mPasswords.add(password);
+ }
+ }
+
+ if (mAccountNames.size() == 0) {
+ throw new IllegalArgumentException(
+ String.format("The argument '%s' required for test not found",
+ ARGUMENT_ACCOUNT));
+ } else if (mPasswords.size() == 0) {
+ throw new IllegalArgumentException(
+ String.format("The argument '%s' required for test not found",
+ ARGUMENT_PASSWORD));
+ } else if (mAccountNames.size() != mPasswords.size()) {
+ throw new IllegalArgumentException(String.format(
+ "The number of 'account' and 'password' arguments should be same. %d != %d",
+ mAccountNames.size(), mPasswords.size()));
+ }
+ }
+
+ // The following helper functions to manage accounts requires to sign the test apk with
+ // the platform keys and the system uid.
+ private List<String> getAccounts() {
+ List<String> accountNames = new ArrayList<>();
+ Account[] accounts = mAm.getAccountsByType(GOOGLE_ACCOUNT);
+ for (Account account : accounts) {
+ Log.i(TAG, String.format("Found account %s", account.name));
+ accountNames.add(account.name);
+ }
+ return accountNames;
+ }
+
+ private void removeAccounts() {
+ Account[] accounts = mAm.getAccountsByType(GOOGLE_ACCOUNT);
+ for (Account account : accounts) {
+ Log.i(TAG, String.format("Removing account %s", account.name));
+ RemoveCallback callback = new RemoveCallback();
+ mAm.removeAccount(account, null, callback, null);
+ if (callback.waitForRemoveCompletion() == null) {
+ Log.e(TAG, String.format("Failed to remove account %s: Reason: %s",
+ account.name, callback.getErrorMessage()));
+ return;
+ }
+ }
+ }
+
+ /**
+ * @param accountName the account name passed in arguments. This may or may not be an email
+ * @return the account name if the username is an email address or the account name appended
+ * with @gmail.com if not
+ */
+ private String getEmailFromAccountName(String accountName) {
+ StringBuilder sb = new StringBuilder(accountName);
+ if (!Patterns.EMAIL_ADDRESS.matcher(sb).matches()) {
+ sb.append(DEFAULT_EMAIL_DOMAIN);
+ }
+ return sb.toString();
+ }
+
+ static class RemoveCallback implements AccountManagerCallback<Bundle> {
+ // stores the result of account removal. null means not finished
+ private Bundle mResult = null;
+ private String mErrorMessage = null;
+
+ public synchronized Bundle waitForRemoveCompletion() {
+ while (mResult == null) {
+ try {
+ wait(LONG_SLEEP_MS);
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ }
+ return mResult;
+ }
+
+ @Override
+ public void run(AccountManagerFuture<Bundle> future) {
+ try {
+ mResult = future.getResult();
+ } catch (OperationCanceledException e) {
+ handleException(e);
+ } catch (IOException e) {
+ handleException(e);
+ } catch (AuthenticatorException e) {
+ handleException(e);
+ }
+ synchronized (this) {
+ notifyAll();
+ }
+ }
+
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ private void handleException(Exception e) {
+ Log.e(TAG, "Failed to remove account", e);
+ mResult = null;
+ mErrorMessage = e.toString();
+ }
+ }
+}
+
diff --git a/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/settings/MainSettingsTests.java b/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/settings/MainSettingsTests.java
new file mode 100644
index 0000000..2d9ea4a
--- /dev/null
+++ b/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/settings/MainSettingsTests.java
@@ -0,0 +1,140 @@
+/*
+ * 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.test.functional.tv.settings;
+
+import android.test.functional.tv.common.SysUiTestBase;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Functional verification tests for the main settings on TV.
+ *
+ * adb shell am instrument -w -r \
+ * -e class android.test.functional.tv.settings.MainSettingsTests \
+ * android.test.functional.tv.sysui/android.support.test.runner.AndroidJUnitRunner
+ */
+public class MainSettingsTests extends SysUiTestBase {
+
+ private static final String TAG = MainSettingsTests.class.getSimpleName();
+ private static final long LOADING_TIMEOUT_MS = 5000; // 5 seconds
+
+ private static final Map<String, String[]> DEVICE_SETTINGS = new HashMap<>();
+ private static final Map<String, String[]> PREFERENCES_SETTINGS = new HashMap<>();
+ private static final Map<String, String[]> REMOTES_SETTINGS = new HashMap<>();
+ private static final Map<String, String[]> PERSONAL_SETTINGS = new HashMap<>();
+ private static final Map<String, String[]> ACCOUNTS_SETTINGS = new HashMap<>();
+ private static final String DEVICE_CATEGORY = "Device";
+ private static final String PREFERENCES_CATEGORY = "Preferences";
+ private static final String REMOTES_CATEGORY = "Remotes & accessories";
+ private static final String PERSONAL_CATEGORY = "Personal";
+ private static final String ACCOUNTS_CATEGORY = "Accounts";
+
+ private static final String DEVELOPER_OPTIONS_MENU = "Developer options";
+ private static final String PROP_BUILD_DISPLAY = "ro.build.display.id";
+
+
+ static {
+ DEVICE_SETTINGS.put(DEVICE_CATEGORY,
+ new String[]{"Network", "Google Cast", "Sound", "Apps", "Screen saver",
+ "Storage & reset", "About"});
+ PREFERENCES_SETTINGS.put(PREFERENCES_CATEGORY,
+ new String[]{"Date & time", "Language", "Keyboard", "Home screen", "Search",
+ "Speech", "Accessibility"});
+ REMOTES_SETTINGS.put(REMOTES_CATEGORY,
+ new String[]{"Add accessory"});
+ PERSONAL_SETTINGS.put(PERSONAL_CATEGORY,
+ new String[]{"Location", "Security & restrictions", "Usage & Diagnostics"});
+ ACCOUNTS_SETTINGS.put(ACCOUNTS_CATEGORY,
+ new String[]{"Add account"});
+
+ }
+
+
+ public MainSettingsTests() {
+ }
+
+ @Before
+ public void setUp() {
+ mLauncherStrategy.open();
+ mSettingsHelper.open();
+ }
+
+ @After
+ public void tearDown() {
+ mSettingsHelper.exit();
+ }
+
+ /**
+ * Objective: Verify the important Settings items are visible and accessible.
+ */
+ @Test
+ public void testEnsureSettingsVisible() {
+ for (String s : DEVICE_SETTINGS.get(DEVICE_CATEGORY)) {
+ Assert.assertTrue(selectSettingsAndExit(s));
+ }
+ for (String s : PREFERENCES_SETTINGS.get(PREFERENCES_CATEGORY)) {
+ Assert.assertTrue(selectSettingsAndExit(s));
+ }
+ // The Developer options may not appear
+ if (mSettingsHelper.isDeveloperOptionsEnabled()) {
+ Assert.assertTrue(selectSettingsAndExit(DEVELOPER_OPTIONS_MENU));
+ }
+ // Skipping "Remotes & accessories" that needs to be covered in pairing steps.
+ //for (String s : REMOTES_SETTINGS.get(REMOTES_CATEGORY)) {
+ // Assert.assertTrue(selectSettingsAndExit(s));
+ //}
+ for (String s : PERSONAL_SETTINGS.get(PERSONAL_CATEGORY)) {
+ Assert.assertTrue(selectSettingsAndExit(s));
+ }
+ for (String s : ACCOUNTS_SETTINGS.get(ACCOUNTS_CATEGORY)) {
+ Assert.assertTrue(selectSettingsAndExit(s));
+ }
+
+ }
+
+ /**
+ * Objective: Verify the build version details match.
+ */
+ @Test
+ public void testBuildVersion() {
+ // Open "About"
+ Assert.assertTrue(mSettingsHelper.clickSetting("About"));
+
+ // Open "Build"
+ // eg, fugu-userdebug 7.0 NRD90E 3040393 dev-keys
+ String buildDisplay = mCmdHelper.executeGetProp(PROP_BUILD_DISPLAY);
+ Assert.assertTrue(buildDisplay.equals(mSettingsHelper.getSummaryTextByTitle("Build")));
+ }
+
+ private boolean selectSettingsAndExit(String title) {
+ Log.d(TAG, String.format("Checking %s ...", title));
+ boolean ret = mSettingsHelper.clickSetting(title) &&
+ mSettingsHelper.goBackGuidedSettings(1);
+ if (!ret) {
+ Log.e(TAG, String.format("Failed to find and exit the setting \"%s\"", title));
+ }
+ return ret;
+ }
+}
+
diff --git a/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/settings/MultiUserInRestrictedProfileTests.java b/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/settings/MultiUserInRestrictedProfileTests.java
new file mode 100644
index 0000000..8fa471c
--- /dev/null
+++ b/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/settings/MultiUserInRestrictedProfileTests.java
@@ -0,0 +1,229 @@
+/*
+ * 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.test.functional.tv.settings;
+
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.os.SystemClock;
+import android.platform.test.helpers.DPadHelper;
+import android.platform.test.helpers.tv.SysUiSettingsHelperImpl;
+import android.support.test.launcherhelper.ILauncherStrategy;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.Direction;
+import android.test.functional.tv.common.SysUiTestBase;
+import android.test.functional.tv.common.TestSetupInstrumentation;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+
+/**
+ * Functional verification tests for multi users in the restricted profile
+ *
+ * Note that this test requires that the primary user created restricted profile and switched to
+ * the restricted profile.
+ * The test harness needs to set this up before test starts using {@link TestSetupInstrumentation}
+ * that provides a way of creating/deleting profiles.
+ *
+ * adb shell am instrument -w -r \
+ * -e class android.test.functional.tv.settings.MultiUserInRestrictedProfileTests \
+ * android.test.functional.tv.sysui/android.support.test.runner.AndroidJUnitRunner
+ */
+public class MultiUserInRestrictedProfileTests extends SysUiTestBase {
+ private static final String TAG = MultiUserInRestrictedProfileTests.class.getSimpleName();
+ private static final String TEXT_CREATE_RESTRICTED_PROFILE = "Create restricted profile";
+ private static final String TEXT_DELETE_RESTRICTED_PROFILE = "Delete restricted profile";
+ private static final String TEXT_ENTER_RESTRICTED_PROFILE = "Enter restricted profile";
+ private static final String TEXT_EXIT_RESTRICTED_PROFILE = "Exit restricted profile";
+ private static final String TITLE_RESTRICTIONS = "Security & restrictions";
+ private static final long SHORT_SLEEP_MS = 3000; // 3 seconds
+
+ private static final List<String> ALLOWED_SETTINGS = Arrays.asList(
+ "Network", "About", "Accessibility", "Location", "Security & restrictions");
+ private static final List<String> DISALLOWED_SETTINGS = Arrays.asList(
+ // Device category
+ "Google Cast", "Sound", "Apps", "Screen saver", "Storage & reset",
+ // Preferences category
+ "Date & time", "Language", "Keyboard", "Home screen", "Search", "Speech",
+ // Personal category
+ "Usage & Diagnostics",
+ // Accounts category
+ "Add account");
+
+ private static final String PIN_CODE = "1010";
+
+
+ public MultiUserInRestrictedProfileTests() {
+ }
+
+ private MultiUserInRestrictedProfileTests(Instrumentation instrumentation) {
+ super(instrumentation);
+ }
+
+ @Before
+ public void setUp() {
+ mLauncherStrategy.open();
+ }
+
+ @After
+ public void tearDown() {
+ }
+
+ /**
+ * Objective: Verify that the restricted user could see only allowed apps and limited settings.
+ */
+ @Test
+ public void testEnsureSettingsAsRestrictedUser() {
+ // TODO Verify that only the selected apps are shown up on the launcher
+
+ // Verify that the Settings has only limited items:
+ // Network, About, Accessibility, Add accessory, Location, Security & restrictions
+ mSettingsHelper.open();
+ for (int count = ALLOWED_SETTINGS.size(); count > 0; count--) {
+ String setting = mSettingsHelper.getCurrentFocusedSettingTitle();
+ if (DISALLOWED_SETTINGS.contains(setting)) {
+ fail(String.format(
+ "This setting is disallowed in the Restricted Profile: " + setting));
+ }
+ if (ALLOWED_SETTINGS.contains(setting)) {
+ Assert.assertTrue(selectSettingsAndExit(setting));
+ }
+ mDPadHelper.pressDPad(Direction.DOWN);
+ }
+ mSettingsHelper.exit();
+ }
+
+ /**
+ * Objective: Verify that the restricted profile doesn't have permission to use Play store
+ */
+ @Test
+ public void testDisallowPlaystoreAsRestrictedUser() {
+ long timestamp = mLauncherStrategy.launch("Play Store", "com.android.vending");
+ if (timestamp == ILauncherStrategy.LAUNCH_FAILED_TIMESTAMP) {
+ throw new IllegalStateException("Failed to launch Play store");
+ }
+ // TODO Move this to Play store app helper
+ Assert.assertTrue(mDevice.hasObject(
+ By.res("android", "message").textStartsWith("You don't have permission to use")));
+ }
+
+ private boolean selectSettingsAndExit(String title) {
+ Log.d(TAG, String.format("Checking %s ...", title));
+ boolean ret = mSettingsHelper.clickSetting(title) &&
+ mSettingsHelper.goBackGuidedSettings(1);
+ if (!ret) {
+ Log.e(TAG, String.format("Failed to find and exit the setting \"%s\"", title));
+ }
+ return ret;
+ }
+
+ /**
+ * Instrumentation setup class for {@link MultiUserInRestrictedProfileTests}
+ */
+ public static class Setup {
+
+ public static boolean createRestrictedProfile(Instrumentation instrumentation,
+ String pinCode, boolean switchUser) {
+ if (isRestrictedUser(instrumentation.getContext())) {
+ Log.e(TAG, "Already in the restricted mode. The test setup has stopped.");
+ return false;
+ }
+ if (hasRestrictedUser(instrumentation.getContext())) {
+ Log.e(TAG, "Already has a restricted user. The test setup has stopped.");
+ return false;
+ }
+ SysUiSettingsHelperImpl settingsHelper = new MultiUserInRestrictedProfileTests(
+ instrumentation).mSettingsHelper;
+ // Start from Home screen
+ DPadHelper.getInstance(instrumentation).pressHome();
+
+ // Create the restricted profile
+ settingsHelper.open();
+ settingsHelper.clickSetting(TITLE_RESTRICTIONS);
+ settingsHelper.clickSetting(TEXT_CREATE_RESTRICTED_PROFILE);
+ settingsHelper.setNewPinCode(pinCode);
+ settingsHelper.reenterPinCode(pinCode);
+
+ // Pick a few apps in Allowed apps
+ settingsHelper.waitForOpenGuidedSetting("Allowed apps", SHORT_SLEEP_MS);
+
+ // Then, go back and enter the restricted profile.
+ settingsHelper.goBackGuidedSettings(1);
+ if (switchUser) {
+ settingsHelper.clickSetting(TEXT_ENTER_RESTRICTED_PROFILE);
+ }
+ return hasRestrictedUser(instrumentation.getContext());
+ }
+
+ public static boolean deleteRestrictedProfile(Instrumentation instrumentation,
+ String pinCode) {
+ if (isRestrictedUser(instrumentation.getContext())) {
+ Log.e(TAG,
+ "A restricted user cannot delete the profile. The test setup has stopped.");
+ return false;
+ }
+ if (!hasRestrictedUser(instrumentation.getContext())) {
+ Log.e(TAG, "There is no restricted user to delete. The test setup has stopped.");
+ return false;
+ }
+ SysUiSettingsHelperImpl settingsHelper = new MultiUserInRestrictedProfileTests(
+ instrumentation).mSettingsHelper;
+ // Start from Home screen
+ DPadHelper.getInstance(instrumentation).pressHome();
+
+ // Delete the restricted profile
+ settingsHelper.open();
+ settingsHelper.clickSetting(TITLE_RESTRICTIONS);
+ settingsHelper.clickSetting(TEXT_DELETE_RESTRICTED_PROFILE);
+ settingsHelper.enterPinCode(pinCode);
+ SystemClock.sleep(SHORT_SLEEP_MS);
+ if (TEXT_DELETE_RESTRICTED_PROFILE.equals(
+ settingsHelper.getCurrentFocusedSettingTitle())) {
+ Log.e(TAG, "The setting to delete the restricted profile should be"
+ + "gone. The test setup has stopped.");
+ return false;
+ }
+ return !hasRestrictedUser(instrumentation.getContext());
+ }
+
+ public static boolean exitRestrictedProfile(Instrumentation instrumentation) {
+ if (!isRestrictedUser(instrumentation.getContext())) {
+ Log.e(TAG, "Not in the restricted mode. The test setup has stopped.");
+ return false;
+ }
+ SysUiSettingsHelperImpl settingsHelper = new MultiUserInRestrictedProfileTests(
+ instrumentation).mSettingsHelper;
+ // Start from Home screen
+ DPadHelper.getInstance(instrumentation).pressHome();
+
+ // Exit the restricted profile
+ settingsHelper.open();
+ settingsHelper.clickSetting(TITLE_RESTRICTIONS);
+ settingsHelper.clickSetting(TEXT_EXIT_RESTRICTED_PROFILE);
+ settingsHelper.enterPinCode(PIN_CODE);
+ return true;
+ }
+ }
+}
+
diff --git a/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/settings/MultiUserTests.java b/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/settings/MultiUserTests.java
new file mode 100644
index 0000000..8f1d5e6
--- /dev/null
+++ b/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/settings/MultiUserTests.java
@@ -0,0 +1,158 @@
+/*
+ * 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.test.functional.tv.settings;
+
+import android.os.SystemClock;
+import android.support.test.uiautomator.Direction;
+import android.test.functional.tv.common.SysUiTestBase;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+/**
+ * Functional verification tests for multi users not in the restricted profile
+ *
+ * adb shell am instrument -w -r \
+ * -e class android.test.functional.tv.settings.MultiUserTests \
+ * android.test.functional.tv.sysui/android.support.test.runner.AndroidJUnitRunner
+ */
+public class MultiUserTests extends SysUiTestBase {
+ private static final String TAG = MultiUserTests.class.getSimpleName();
+ private static final String PIN_CODE = "1010";
+ private static final String TEXT_CREATE_RESTRICTED_PROFILE = "Create restricted profile";
+ private static final String TEXT_DELETE_RESTRICTED_PROFILE = "Delete restricted profile";
+ private static final String TITLE_RESTRICTIONS = "Security & restrictions";
+
+ private static final long SHORT_SLEEP_MS = 3000; // 3 seconds
+
+
+ @Before
+ public void setUp() {
+ mLauncherStrategy.open();
+ mSettingsHelper.open();
+ }
+
+ @After
+ public void tearDown() {
+ mSettingsHelper.exit();
+ forceRemoveRestrictedProfile();
+ }
+
+ /**
+ * Objective: Able to create a Restricted Profile and delete
+ */
+ @Test
+ public void testCreateRestrictedProfileAndDelete() {
+ // Create the Restricted Profile with the PIN code
+ Assert.assertTrue(mSettingsHelper.clickSetting(TITLE_RESTRICTIONS));
+ Assert.assertTrue(mSettingsHelper.clickSetting(TEXT_CREATE_RESTRICTED_PROFILE));
+ Assert.assertTrue(mSettingsHelper.setNewPinCode(PIN_CODE));
+ Assert.assertTrue(mSettingsHelper.reenterPinCode(PIN_CODE));
+
+ // Pick a few apps in Allowed apps
+ Assert.assertTrue(mSettingsHelper.waitForOpenGuidedSetting("Allowed apps", SHORT_SLEEP_MS));
+ String title = mSettingsHelper.getCurrentFocusedSettingTitle();
+ String titlePrev = "";
+ while (!title.equals(titlePrev)) {
+ // Verify that all apps but "Location" are disabled by default
+ if ("Location".equals(title)) {
+ Assert.assertTrue(mSettingsHelper.isSwitchBarOn(title));
+ } else {
+ Assert.assertTrue(mSettingsHelper.isSwitchBarOff(title));
+ }
+ mDPadHelper.pressDPad(Direction.DOWN);
+ titlePrev = title;
+ title = mSettingsHelper.getCurrentFocusedSettingTitle();
+ }
+
+ final String[] WHITELISTED_APPS = {"YouTube"};
+ for (String appName : WHITELISTED_APPS) {
+ if (mSettingsHelper.hasSettingByTitle(appName)) {
+ mDPadHelper.pressDPadCenter();
+ Assert.assertTrue(mSettingsHelper.isSwitchBarOn(appName));
+ }
+ }
+
+ // Then, go back and delete the profile.
+ mSettingsHelper.goBackGuidedSettings(1);
+ SystemClock.sleep(SHORT_SLEEP_MS); // Wait a little until it creates the profile
+ mSettingsHelper.exit();
+ deleteRestrictedProfileFromLauncher();
+ Assert.assertFalse(hasRestrictedUser(mContext));
+ }
+
+
+ /**
+ * Objective: Verify that entering wrong password 5 times keeps user waiting
+ * for 60 seconds for retry
+ */
+ @Ignore
+ @Test
+ public void testEnterWrongPassword5Times() {
+
+ }
+
+ private void deleteRestrictedProfileFromLauncher() {
+ if (!hasRestrictedUser(mContext)) {
+ Log.d(TAG, "No-op if no restricted profile created");
+ return;
+ }
+ mLauncherStrategy.selectRestrictedProfile();
+ mSettingsHelper.clickSetting(TEXT_DELETE_RESTRICTED_PROFILE);
+ mSettingsHelper.enterPinCode(PIN_CODE);
+ SystemClock.sleep(SHORT_SLEEP_MS);
+ if (TEXT_DELETE_RESTRICTED_PROFILE.equals(
+ mSettingsHelper.getCurrentFocusedSettingTitle())) {
+ throw new IllegalStateException("Failed to delete the Restricted Profile");
+ }
+ }
+
+ // Force remove a restricted user in the tearDown. Avoid calling this method in tests
+ // because it is different from the way user uses.
+ // TODO Move this to CommandHelper if necessary
+ private void forceRemoveRestrictedProfile() {
+ if (!hasRestrictedUser(mContext)) {
+ Log.d(TAG, "No-op if no restricted profile created");
+ return;
+ }
+ // Retrieve the ID of a restricted user from the pm command.
+ // Example :
+ // Users:
+ // UserInfo{0:Owner:13} running
+ // UserInfo{18:Restricted Profile:8}
+ String output = mCmdHelper.executeShellCommand("pm list users");
+ final Pattern USERS_REGEX = Pattern.compile("UserInfo\\{(\\d+):Restricted Profile:");
+ Matcher matcher = USERS_REGEX.matcher(output);
+ int userId = 0;
+ if (matcher.find()) {
+ userId = Integer.parseInt(matcher.group(1));
+ Log.i(TAG, String.format("The ID of restricted user is %d", userId));
+ }
+
+ if (userId > 0) {
+ mCmdHelper.executeShellCommand(String.format("pm remove-user %d", userId));
+ }
+ }
+}
diff --git a/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/sysui/HomeScreenTests.java b/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/sysui/HomeScreenTests.java
new file mode 100644
index 0000000..a72fd72
--- /dev/null
+++ b/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/sysui/HomeScreenTests.java
@@ -0,0 +1,66 @@
+/*
+ * 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.test.functional.tv.sysui;
+
+import android.test.functional.tv.common.SysUiTestBase;
+import android.util.Log;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Functional verification tests for leanback launcher.
+ *
+ * adb shell am instrument -w -r \
+ * -e class android.test.functional.tv.sysui.HomeScreenTests \
+ * android.test.functional.tv.sysui/android.support.test.runner.AndroidJUnitRunner
+ */
+public class HomeScreenTests extends SysUiTestBase {
+
+ private static final String TAG = HomeScreenTests.class.getSimpleName();
+
+ @Before
+ public void setUp() {
+ mLauncherStrategy.open();
+ }
+
+ /**
+ * Objective: Verify the rows on Home screen
+ * - Search orbs & Clock widget
+ * - Recommendations row (Google + 3rd party apps)
+ * - Apps row
+ * - Games row (optional: In case of installed the game apps)
+ * - Settings & Network
+ */
+ @Test
+ public void testSelectHomeScreenRows() {
+ Log.d(TAG, "testSelectHomeScreenRows");
+ Assert.assertNotNull("Failed to select the Recommendations row",
+ mLauncherStrategy.selectNotificationRow());
+ Assert.assertNotNull("Failed to select the Search orbs",
+ mLauncherStrategy.selectSearchRow());
+ Assert.assertNotNull("Failed to find the app widget",
+ mLauncherStrategy.hasAppWidgetSelector());
+ Assert.assertNotNull("Failed to select the Apps row",
+ mLauncherStrategy.selectAppsRow());
+
+ Assert.assertNotNull("Failed to select the Settings row",
+ mLauncherStrategy.selectSettingsRow());
+ }
+}
+
diff --git a/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/sysui/RecentActivityTests.java b/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/sysui/RecentActivityTests.java
new file mode 100644
index 0000000..451513f
--- /dev/null
+++ b/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/sysui/RecentActivityTests.java
@@ -0,0 +1,187 @@
+/*
+ * 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.test.functional.tv.sysui;
+
+import android.os.SystemClock;
+import android.support.test.uiautomator.Direction;
+import android.test.functional.tv.common.SysUiTestBase;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Functional verification tests for Recents on TV.
+ *
+ * adb shell am instrument -w -r \
+ * -e class android.test.functional.tv.sysui.RecentActivityTests \
+ * android.test.functional.tv.sysui/android.support.test.runner.AndroidJUnitRunner
+ */
+public class RecentActivityTests extends SysUiTestBase {
+
+ private static final String TAG = RecentActivityTests.class.getSimpleName();
+ private static final long SHORT_TIMEOUT_MS = 2000;
+ // TODO Use app helpers instead of constants once the helpers are moved in platform_testing
+ private static final String APP_NAME_PLAYSTORE = "Play Store";
+ private static final String PACKAGE_PLAYSTORE = "com.android.vending";
+ private static final String APP_NAME_PLAYMUSIC = "Play Music";
+ private static final String PACKAGE_PLAYMUSIC = "com.android.google.music";
+ private static String sYouTubeAppName;
+ private static String sYouTubePackage;
+
+
+ public RecentActivityTests() {
+ sYouTubeAppName = mYouTubeHelper.getLauncherName();
+ sYouTubePackage = mYouTubeHelper.getPackage();
+ }
+
+ @Before
+ public void setUp() {
+ // clear all recent items before test run
+ openAndClearAllRecents(SHORT_TIMEOUT_MS);
+ mLauncherStrategy.open();
+ }
+
+ @After
+ public void tearDown() {
+ mRecentsHelper.exit();
+ }
+
+ /**
+ * Objective: Able to bring up Recent tasks, dismiss them, and exit.
+ */
+ @Test
+ public void testAddDismissRecents() {
+ // Open two apps
+ mLauncherStrategy.launch(sYouTubeAppName, sYouTubePackage);
+ mLauncherStrategy.launch(APP_NAME_PLAYSTORE, PACKAGE_PLAYSTORE);
+ mLauncherStrategy.open();
+
+ mRecentsHelper.open(SHORT_TIMEOUT_MS);
+ // Dismiss the last open application, which is Play Store in this case
+ mRecentsHelper.dismissTask();
+ // Verify that the Play Store app is gone, and YouTube app is still in Recents.
+ Assert.assertFalse("The task should be gone after dismissed in Recents",
+ mRecentsHelper.selectTask(APP_NAME_PLAYSTORE));
+ Assert.assertTrue(mRecentsHelper.selectTask(sYouTubeAppName));
+ // Verify that it exits by pressing a Home key
+ mRecentsHelper.exit();
+ Assert.assertFalse(mRecentsHelper.isAppInForeground());
+ }
+
+ /**
+ * Objective: Open an app in Recents
+ */
+ @Test
+ public void testOpenAppOnRecents() {
+ // Open an app
+ mLauncherStrategy.launch(sYouTubeAppName, sYouTubePackage);
+ mLauncherStrategy.open();
+
+ // Reopen the app in Recents
+ mRecentsHelper.open(SHORT_TIMEOUT_MS);
+ Assert.assertTrue(mRecentsHelper.selectTask(sYouTubeAppName));
+ mDPadHelper.pressDPadCenter();
+ SystemClock.sleep(SHORT_TIMEOUT_MS);
+ // Verify that the application is open from Recents
+ Assert.assertTrue(mYouTubeHelper.isAppInForeground());
+ }
+
+ /**
+ * Objective: "No recent items" is presented when no app has been launched
+ */
+ @Test
+ public void testNoRecentItems() {
+ mRecentsHelper.open(SHORT_TIMEOUT_MS);
+ Assert.assertTrue("'No recent items' message is presented when no app has been launched",
+ mRecentsHelper.hasNoRecentItems());
+ }
+
+ /**
+ * Objective: Focus should be on the second right when it enters Recent from an app activity
+ * Otherwise, the focus is on the right end (the latest item).
+ */
+ @Test
+ public void testFocusOnMostRecent() {
+ // Open two apps
+ mLauncherStrategy.launch(APP_NAME_PLAYSTORE, PACKAGE_PLAYSTORE);
+ mLauncherStrategy.launch(sYouTubeAppName, sYouTubePackage);
+
+ // Verify that the focus should be on the right end when opening Recents on the Home screen
+ mLauncherStrategy.open();
+ mRecentsHelper.open(SHORT_TIMEOUT_MS);
+ Assert.assertEquals("Focus should be on the right end when opening Recents on " +
+ "the Home screen", mRecentsHelper.getFocusedTaskName(), sYouTubeAppName);
+
+ // Verify that the focus should be on the second right when opening Recents on app activity
+ mDPadHelper.pressDPadCenter();
+ SystemClock.sleep(SHORT_TIMEOUT_MS);
+ mRecentsHelper.open(SHORT_TIMEOUT_MS);
+ Assert.assertEquals("Focus should be on the second right when opening Recents on " +
+ "app activity", mRecentsHelper.getFocusedTaskName(), APP_NAME_PLAYSTORE);
+ }
+
+ /**
+ * Objective: The most Recent task goes to the right
+ */
+ @Test
+ public void testOrderMostRecentToRight() {
+ mLauncherStrategy.launch(sYouTubeAppName, sYouTubePackage);
+ mLauncherStrategy.launch(APP_NAME_PLAYSTORE, PACKAGE_PLAYSTORE);
+ mLauncherStrategy.launch(APP_NAME_PLAYMUSIC, PACKAGE_PLAYMUSIC);
+ mLauncherStrategy.open();
+
+ // Verify that the previously open task goes to the left
+ mRecentsHelper.open(SHORT_TIMEOUT_MS);
+ Assert.assertEquals(mRecentsHelper.getFocusedTaskName(), APP_NAME_PLAYMUSIC);
+ mDPadHelper.pressDPad(Direction.LEFT);
+ Assert.assertEquals(mRecentsHelper.getFocusedTaskName(), APP_NAME_PLAYSTORE);
+ mDPadHelper.pressDPad(Direction.LEFT);
+ Assert.assertEquals(mRecentsHelper.getFocusedTaskName(), sYouTubeAppName);
+ }
+
+ /**
+ * Objective: Only one Recent task is allowed for an each app on Recent activity.
+ */
+ @Test
+ public void testAllowOnlyOneRecentPerApp() {
+ // Open an app
+ mLauncherStrategy.launch(sYouTubeAppName, sYouTubePackage);
+ mRecentsHelper.open(SHORT_TIMEOUT_MS);
+ Assert.assertTrue(mRecentsHelper.selectTask(sYouTubeAppName));
+ mRecentsHelper.exit();
+
+ // Reopen the same app
+ mLauncherStrategy.launch(sYouTubeAppName, sYouTubePackage);
+ mRecentsHelper.open(SHORT_TIMEOUT_MS);
+ Assert.assertTrue("Allow only one task per each app in Recents",
+ mRecentsHelper.getTaskCountOnScreen() == 1);
+ }
+
+ private void openAndClearAllRecents(long timeoutMs) {
+ try {
+ mRecentsHelper.open(timeoutMs);
+ mRecentsHelper.clearAll();
+ mRecentsHelper.exit();
+ } catch (Exception e) {
+ // Ignore
+ Log.w(TAG, "Failed to clear all in Recents. " + e.getMessage());
+ }
+ }
+}
diff --git a/tests/functional/tv/YouTubeTests/Android.mk b/tests/functional/tv/YouTubeTests/Android.mk
new file mode 100644
index 0000000..ecf13ce
--- /dev/null
+++ b/tests/functional/tv/YouTubeTests/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+# ------------------------------------------------
+# build a test apk for YouTube functional testing
+
+LOCAL_PACKAGE_NAME := YouTubeFuncTests
+LOCAL_MODULE_TAGS := tests
+LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator leanback-app-helpers android-support-test
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+include $(BUILD_PACKAGE)
+
diff --git a/tests/functional/tv/YouTubeTests/AndroidManifest.xml b/tests/functional/tv/YouTubeTests/AndroidManifest.xml
new file mode 100644
index 0000000..1c3a2ed
--- /dev/null
+++ b/tests/functional/tv/YouTubeTests/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.test.functional.tv.youtube">
+
+ <uses-sdk android:minSdkVersion="21"
+ android:targetSdkVersion="24"/>
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.test.functional.tv.youtube"
+ android:label="TV YouTube Functional Tests" />
+
+</manifest>
+
+
diff --git a/tests/functional/tv/YouTubeTests/src/android/test/functional/tv/youtube/YouTubeTests.java b/tests/functional/tv/YouTubeTests/src/android/test/functional/tv/youtube/YouTubeTests.java
new file mode 100644
index 0000000..196e4d5
--- /dev/null
+++ b/tests/functional/tv/YouTubeTests/src/android/test/functional/tv/youtube/YouTubeTests.java
@@ -0,0 +1,146 @@
+/*
+ * 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.test.functional.tv.youtube;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.platform.test.helpers.tv.YouTubeHelperImpl;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.launcherhelper.ILeanbackLauncherStrategy;
+import android.support.test.launcherhelper.LauncherStrategyFactory;
+import android.support.test.launcherhelper.LeanbackLauncherStrategy;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Functional verification tests for YouTube on TV.
+ *
+ * adb shell am instrument -w -r \
+ * -e class android.test.functional.tv.youtube.YouTubeTests \
+ * android.test.functional.tv.youtube/android.support.test.runner.AndroidJUnitRunner
+ */
+@RunWith(AndroidJUnit4.class)
+public class YouTubeTests {
+
+ private static final String TAG = YouTubeTests.class.getSimpleName();
+ private static final String DEFAULT_SEARCH_QUERY = "never gonna give you up";
+ private static final long DEFAULT_SEARCH_PLAY_DURATION_MS = 30 * 1000; // 30 seconds
+
+ private UiDevice mDevice;
+ private Instrumentation mInstrumentation;
+ private Context mContext;
+ private Bundle mArguments;
+
+ private LeanbackLauncherStrategy mLauncherStrategy;
+ private YouTubeHelperImpl mYouTubeHelper;
+
+
+ public YouTubeTests() {
+ initialize(InstrumentationRegistry.getInstrumentation());
+ }
+
+ private void initialize(Instrumentation instrumentation) {
+ // Initialize instances of testing support library
+ mInstrumentation = instrumentation;
+ mContext = getInstrumentation().getContext();
+ mDevice = UiDevice.getInstance(getInstrumentation());
+ mArguments = InstrumentationRegistry.getArguments();
+
+ // Initialize instances of leanback and app helpers
+ ILeanbackLauncherStrategy launcherStrategy = LauncherStrategyFactory.getInstance(
+ mDevice).getLeanbackLauncherStrategy();
+ if (launcherStrategy instanceof LeanbackLauncherStrategy) {
+ mLauncherStrategy = (LeanbackLauncherStrategy) launcherStrategy;
+ }
+ mYouTubeHelper = new YouTubeHelperImpl(getInstrumentation());
+ }
+
+ protected Instrumentation getInstrumentation() {
+ return mInstrumentation;
+ }
+
+ @Before
+ public void setUp() {
+ mLauncherStrategy.open();
+ }
+
+ @After
+ public void tearDown() {
+ mYouTubeHelper.exit();
+ }
+
+
+ /**
+ * Objective: Able to play the first video on Home section.
+ */
+ @Test
+ public void testPlayVideoAtHome() {
+ mYouTubeHelper.open();
+ mYouTubeHelper.openHome();
+
+ Log.i(TAG, "found a video: " + mYouTubeHelper.getFocusedVideoTitleText());
+ long durationMs = mYouTubeHelper.getFocusedVideoDuration();
+ if (durationMs > DEFAULT_SEARCH_PLAY_DURATION_MS) {
+ durationMs = DEFAULT_SEARCH_PLAY_DURATION_MS;
+ }
+ Assert.assertTrue(mYouTubeHelper.playFocusedVideo(durationMs));
+ }
+
+ /**
+ * Objective: Able to search for videos and play.
+ */
+ @Test
+ public void testSearchVideoAndPlay() {
+ // Search for a video
+ mYouTubeHelper.open();
+ mYouTubeHelper.search(DEFAULT_SEARCH_QUERY);
+
+ long durationMs = mYouTubeHelper.getFocusedVideoDuration();
+ if (durationMs > DEFAULT_SEARCH_PLAY_DURATION_MS) {
+ durationMs = DEFAULT_SEARCH_PLAY_DURATION_MS;
+ }
+
+ // Select the first video in the search results and play
+ mYouTubeHelper.openFirstSearchResult();
+
+ // Play the video for a given period of time
+ SystemClock.sleep(durationMs);
+ Assert.assertTrue(mYouTubeHelper.isInVideoPlayback());
+ }
+
+ /**
+ * Objective: Able to launch YouTube video in the Notification row
+ */
+ @Test
+ public void testLaunchVideoInNotificationRow() {
+ Assert.assertTrue(mLauncherStrategy.launchNotification(mYouTubeHelper.getLauncherName()));
+
+ // Play the video for a given period of time
+ SystemClock.sleep(5000);
+ Assert.assertTrue(mYouTubeHelper.isInVideoPlayback());
+ }
+}
+