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());
+    }
+}
+