Merge "TV PIP functional tests" into nyc-mr1-dev
diff --git a/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/sysui/PipActivityTests.java b/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/sysui/PipActivityTests.java
new file mode 100644
index 0000000..18975f7
--- /dev/null
+++ b/tests/functional/tv/TvSysUiTests/src/android/test/functional/tv/sysui/PipActivityTests.java
@@ -0,0 +1,406 @@
+/*
+ * 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 static android.view.KeyEvent.KEYCODE_HOME;
+import static android.view.KeyEvent.KEYCODE_MEDIA_PAUSE;
+import static android.view.KeyEvent.KEYCODE_MEDIA_PLAY;
+
+import android.media.session.PlaybackState;
+import android.os.SystemClock;
+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.UiWatchers;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * Functional verification tests for Picture-in-picture feature. This test requires to install
+ * the demo apk of Android TV Leanback Support Library (com.example.android.tvleanback).
+ *
+ * adb shell am instrument -w -r \
+ * -e class android.test.functional.tv.sysui.PipActivityTests \
+ * android.test.functional.tv.sysui/android.support.test.runner.AndroidJUnitRunner
+ */
+public class PipActivityTests extends SysUiTestBase {
+
+ private static final String TAG = PipActivityTests.class.getSimpleName();
+ private static final String WATCHER_ONBOARDING = "PipOnboardingWatcher";
+ private static final String PACKAGE_TVLEANBACK = "com.example.android.tvleanback";
+ private static final String ACTIVITY_PLAYBACKOVERLAY =
+ "com.example.android.tvleanback.ui.PlaybackOverlayActivity";
+ private static final long SHORT_SLEEP_MS = 3000;
+ private static final long LONG_SLEEP_MS = 10000;
+ private static final long SHORT_TIMEOUT_MS = 2000;
+
+ private boolean shouldStopPipPlayback = true;
+
+ private UiWatchers mWatchers;
+
+
+ @Before
+ public void setUp() {
+ shouldStopPipPlayback = true;
+ // Register the watcher to dismiss the onboarding activity
+ mWatchers = new UiWatchers(getInstrumentation());
+ mWatchers.registerDismissWatcher(WATCHER_ONBOARDING,
+ By.res("com.android.systemui", "pip_onboarding"),
+ By.res("com.android.systemui", "button").text("GOT IT"));
+ // clear all recent items before test run
+ openAndClearAllRecents(SHORT_TIMEOUT_MS);
+ mLauncherStrategy.open();
+ }
+
+ @After
+ public void tearDown() {
+ mWatchers.unregisterDismissWatcher(WATCHER_ONBOARDING);
+ if (shouldStopPipPlayback) {
+ stopPipPlayback();
+ }
+ }
+
+ /**
+ * Objective: Verify that PIP window is open by pressing a button given that
+ * the application supports the feature.
+ */
+ @Test
+ public void testOpenPipWindow() {
+ startPipPlayback("Google+", "Instant Upload");
+ mLeanbackDemoHelper.openMediaControlsAndClickPipButton();
+ // TODO making hasTooltipShown less flaky
+ if (!mLeanbackDemoHelper.hasTooltipShown()) {
+ Log.d(TAG, "The tooltip text is not detected when opening PIP window. Test is flaky?");
+ }
+ Assert.assertTrue(isDemoActivityInPip());
+ }
+
+ /**
+ * Objective: Verify that PIP window is open by pressing KEYCODE_WINDOW.
+ */
+ @Test
+ public void testOpenPipWindowOnKeySent() throws InterruptedException {
+ startPipPlayback("Google+", "Instant Upload");
+ SystemClock.sleep(SHORT_SLEEP_MS);
+
+ // Press the PIP key
+ mDPadHelper.pressPipKey();
+ SystemClock.sleep(SHORT_SLEEP_MS);
+ Assert.assertTrue(isDemoActivityInPip());
+ }
+
+ /**
+ * Objective: Verify that PIP window is located in center and focused when PIP overlay
+ * receives the KEYCODE_WINDOW key.
+ */
+ @Test
+ public void testMovePipMenuToCenterOnKeySent() throws InterruptedException {
+ startPipPlayback("Google+", "Instant Upload");
+ SystemClock.sleep(SHORT_SLEEP_MS);
+
+ // Press the PIP key
+ mDPadHelper.pressPipKey();
+ SystemClock.sleep(SHORT_SLEEP_MS);
+ Assert.assertTrue(isDemoActivityInPip());
+ // Press the PIP key code again to move PIP window to center
+ mDPadHelper.pressPipKey();
+ SystemClock.sleep(SHORT_SLEEP_MS);
+ Assert.assertTrue("The PIP menu should be shown in center",
+ mPipHelper.isPipStateMenu(PACKAGE_TVLEANBACK, ACTIVITY_PLAYBACKOVERLAY));
+ }
+
+ /**
+ * Objective: Able to move PIP window to Recents by pressing the Home key
+ */
+ @Test
+ public void testMovePipToRecentsFocused() {
+ startPipPlayback("Google+", "Instant Upload");
+ mLeanbackDemoHelper.openMediaControlsAndClickPipButton();
+ SystemClock.sleep(SHORT_SLEEP_MS);
+ if (!isDemoActivityInPip()) {
+ throw new IllegalStateException("PIP playback required for this test");
+ }
+
+ // Long press HOME key to move PIP to Recents
+ mDPadHelper.longPressKeyCode(KEYCODE_HOME);
+ SystemClock.sleep(LONG_SLEEP_MS);
+ Assert.assertTrue("The PIP should be shown in Recents and focused",
+ mPipHelper.isPipStateRecentsFocused(PACKAGE_TVLEANBACK, ACTIVITY_PLAYBACKOVERLAY));
+ }
+
+ /**
+ * Objective: Able to control media playback by media keys (play/pause)
+ */
+ @Test
+ public void testPipPlaybackStateByMediaKeys() {
+ startPipPlayback("Google+", "Instant Upload");
+ mLeanbackDemoHelper.openMediaControlsAndClickPipButton();
+ SystemClock.sleep(SHORT_SLEEP_MS);
+
+ // Press the PAUSE key
+ mDPadHelper.pressKeyCode(KEYCODE_MEDIA_PAUSE);
+ SystemClock.sleep(SHORT_SLEEP_MS);
+ Assert.assertTrue("The PAUSE key should pause PIP playback",
+ mPipHelper.getPlaybackState(PACKAGE_TVLEANBACK) == PlaybackState.STATE_PAUSED);
+
+ // Press the PLAY key
+ mDPadHelper.pressKeyCode(KEYCODE_MEDIA_PLAY);
+ SystemClock.sleep(SHORT_SLEEP_MS);
+ Assert.assertTrue("The PLAY key should resume PIP playback",
+ mPipHelper.getPlaybackState(PACKAGE_TVLEANBACK) == PlaybackState.STATE_PLAYING);
+ }
+
+ /**
+ * Objective: Verify that the PIP controls is functional in Recents.
+ * - Able to control media playback by UI buttons in Recents
+ * - Able to send PIP window to the full screen in Recents
+ * - Able to close PIP window in Recents
+ */
+ @Test
+ public void testPipControlsInRecents() {
+ // Start PIP and move it to Recents
+ startPipPlayback("Google+", "Instant Upload");
+ mLeanbackDemoHelper.openMediaControlsAndClickPipButton();
+ SystemClock.sleep(SHORT_SLEEP_MS);
+ Assert.assertTrue(isDemoActivityInPip());
+ mDPadHelper.longPressKeyCode(KEYCODE_HOME);
+ SystemClock.sleep(SHORT_SLEEP_MS);
+
+ // Control media playback by UI buttons in Recents. Pause first.
+ mPipHelper.togglePipMediaControls();
+ Assert.assertTrue(
+ mPipHelper.getPlaybackState(PACKAGE_TVLEANBACK) == PlaybackState.STATE_PAUSED);
+ SystemClock.sleep(SHORT_SLEEP_MS);
+ mPipHelper.togglePipMediaControls();
+ Assert.assertTrue(
+ mPipHelper.getPlaybackState(PACKAGE_TVLEANBACK) == PlaybackState.STATE_PLAYING);
+ SystemClock.sleep(SHORT_SLEEP_MS);
+
+ // Move PIP to full screen
+ mPipHelper.selectPipToFullScreenButton();
+ SystemClock.sleep(SHORT_SLEEP_MS);
+ Assert.assertTrue("The activity should be in full screen", isDemoActivityInFullScreen());
+
+ // Going back to PIP in Recents
+ mLeanbackDemoHelper.openMediaControlsAndClickPipButton();
+ Assert.assertTrue(isDemoActivityInPip());
+ SystemClock.sleep(SHORT_SLEEP_MS);
+ mDPadHelper.longPressKeyCode(KEYCODE_HOME);
+ Assert.assertTrue(isDemoActivityInPip());
+
+ // Close PIP in Recents
+ mPipHelper.selectPipCloseButton();
+ Assert.assertFalse("The PIP should be closed", isDemoActivityInPip());
+ }
+
+ /**
+ * Objective: Verify that Recents is functional with PIP on screen.
+ * - Select/Deselect PIP window by going up/down
+ * - Dismiss an app in Recents
+ * - Select an app in Recents
+ */
+ @Test
+ public void testRecentsBehaviorWithPipOn() {
+ // Clear all in Recents
+ mRecentsHelper.open(SHORT_TIMEOUT_MS);
+ mRecentsHelper.clearAll();
+ mRecentsHelper.exit();
+
+ // Open two apps - Play store, YouTube
+ final String APP_NAME_PLAYSTORE = "Play Store";
+ final String PACKAGE_PLAYSTORE = "com.android.vending";
+ mLauncherStrategy.open();
+ mLauncherStrategy.launch(APP_NAME_PLAYSTORE, PACKAGE_PLAYSTORE);
+ mLauncherStrategy.launch(mYouTubeHelper.getLauncherName(), mYouTubeHelper.getPackage());
+
+ // Open PIP in Recents
+ mLauncherStrategy.open();
+ startPipPlayback("Google+", "Instant Upload");
+ mLeanbackDemoHelper.openMediaControlsAndClickPipButton();
+ SystemClock.sleep(SHORT_SLEEP_MS);
+ Assert.assertTrue(isDemoActivityInPip());
+ mDPadHelper.longPressKeyCode(KEYCODE_HOME);
+ SystemClock.sleep(SHORT_SLEEP_MS);
+
+ // Going up and down between PIP and Recents
+ mDPadHelper.pressDPad(Direction.DOWN);
+ Assert.assertTrue(
+ mPipHelper.isPipStateRecents(PACKAGE_TVLEANBACK, ACTIVITY_PLAYBACKOVERLAY));
+ mDPadHelper.pressDPad(Direction.UP);
+ Assert.assertTrue(
+ mPipHelper.isPipStateRecentsFocused(PACKAGE_TVLEANBACK, ACTIVITY_PLAYBACKOVERLAY));
+
+ // Focus on tasks in Recents, move left and dismiss the app - Play store
+ mDPadHelper.pressDPad(Direction.DOWN);
+ mDPadHelper.pressDPad(Direction.LEFT);
+ mRecentsHelper.dismissTask();
+ SystemClock.sleep(SHORT_SLEEP_MS);
+ Assert.assertEquals("The task should be gone after dismissed in Recents",
+ mRecentsHelper.getTaskCountOnScreen(), 2);
+
+ // Open another app - YouTube
+ mDPadHelper.pressDPadCenter();
+ SystemClock.sleep(SHORT_SLEEP_MS);
+ // Verify that both YouTube is open and PIP overlay is presented.
+ Assert.assertTrue(mYouTubeHelper.isAppInForeground());
+ Assert.assertTrue(isDemoActivityInPip());
+ }
+
+ /**
+ * Objective: Able to send PIP window to the full screen from Now Playing card.
+ */
+ @Test
+ public void testPipToFullScreenOnNowPlaying() {
+ startPipPlayback("Google+", "Instant Upload");
+ mLeanbackDemoHelper.openMediaControlsAndClickPipButton();
+ SystemClock.sleep(SHORT_SLEEP_MS);
+ if (!isDemoActivityInPip()) {
+ throw new IllegalStateException("PIP playback required for this test");
+ }
+
+ // Back to Home screen, find the leftmost Now Playing card
+ mLauncherStrategy.open();
+ final int MAX_ATTEMPTS = 5;
+ // Note that Now Playing card seems to make it hard for UiAutomator to detect idle state.
+ // It takes about a minute to complete this test.
+ mDPadHelper.pressDPad(Direction.LEFT, MAX_ATTEMPTS);
+ mDPadHelper.pressDPadCenter();
+ SystemClock.sleep(SHORT_SLEEP_MS);
+ Assert.assertTrue("The activity should be in full screen", isDemoActivityInFullScreen());
+ }
+
+ /**
+ * Objective: Verify that PIP playback won't be interrupted by global search.
+ */
+ @Test
+ public void testVoiceSearchWithPipOn() {
+ startPipPlayback("Google+", "Instant Upload");
+ mLeanbackDemoHelper.openMediaControlsAndClickPipButton();
+ SystemClock.sleep(SHORT_SLEEP_MS);
+ if (!isDemoActivityInPip()) {
+ throw new IllegalStateException("PIP playback required for this test");
+ }
+
+ // Launch TV search app with a query
+ mSearchHelper.launchActivityAndQuery(mSearchHelper.KEYBOARD_SEARCH, "android tv");
+ // Ensure that PIP video keeps playing
+ Assert.assertTrue("The PIP video should keep playing.",
+ mPipHelper.getPlaybackState(PACKAGE_TVLEANBACK) == PlaybackState.STATE_PLAYING);
+ }
+
+ /**
+ * Objective: Verify that the Settings moves PIP to the left of the side panel.
+ */
+ @Test
+ public void testMovePipToSettingsBound() {
+ startPipPlayback("Google+", "Instant Upload");
+ mLeanbackDemoHelper.openMediaControlsAndClickPipButton();
+ SystemClock.sleep(SHORT_SLEEP_MS);
+ if (!isDemoActivityInPip()) {
+ throw new IllegalStateException("PIP playback required for this test");
+ }
+
+ // Open Settings
+ mSettingsHelper.open();
+ SystemClock.sleep(SHORT_SLEEP_MS);
+ Assert.assertTrue("The PIP should be shown on the left of Settings",
+ mPipHelper.isPipStateSettings(PACKAGE_TVLEANBACK, ACTIVITY_PLAYBACKOVERLAY));
+ }
+
+ /**
+ * Objective: Verify that the video playback from other apps dismisses the PIP playback.
+ */
+ @Test
+ public void testPlayOtherVideoWhilePipPlaying() {
+ startPipPlayback("Google+", "Instant Upload");
+ mLeanbackDemoHelper.openMediaControlsAndClickPipButton();
+ SystemClock.sleep(SHORT_SLEEP_MS);
+ if (!isDemoActivityInPip()) {
+ throw new IllegalStateException("PIP playback is required for this test");
+ }
+
+ // Play other video from YouTube for 5 seconds
+ // Do not use open() since UiAutomator is likely to be stuck to find UI elements while
+ // playing PIP on the background.
+ mYouTubeHelper.launchActivity();
+ if (!mYouTubeHelper.waitForOpen(SHORT_SLEEP_MS)) {
+ throw new IllegalStateException("YouTube should be open for this test");
+ }
+ mYouTubeHelper.openHome();
+ final int PLAYBACK_DURATION_MS = 5000;
+ mYouTubeHelper.playFocusedVideo(PLAYBACK_DURATION_MS);
+
+ // Verify that PIP playback stopped when another video starts
+ int playbackState = mPipHelper.getPlaybackState(PACKAGE_TVLEANBACK);
+ Assert.assertTrue("The PIP should be either stopped or gone when another video starts.",
+ playbackState == PlaybackState.STATE_STOPPED ||
+ playbackState == PlaybackState.STATE_NONE);
+ mYouTubeHelper.exit();
+ }
+
+ /**
+ * Objective: Verify that the onboarding screen appears on the first launch of PIP window.
+ */
+ @Ignore("This requires 'setprop debug.tv.pip_force_onboarding true'")
+ @Test
+ public void testPipOnboardSeenOnFirstLaunch() {
+ // TODO
+ }
+
+ private void startPipPlayback(String sectionName, String videoName) {
+ mLeanbackDemoHelper.open();
+ mLeanbackDemoHelper.selectVideoInRowContent(sectionName, videoName);
+ mLeanbackDemoHelper.selectWatchTrailer();
+ }
+
+ private void stopPipPlayback() {
+ // Close the current playback in PIP
+ mPipHelper.executeCommandPipToFullscreen(PACKAGE_TVLEANBACK, ACTIVITY_PLAYBACKOVERLAY,
+ false);
+ // Press the BACK key to stop the playback
+ final int MAX_DEPTH = 2;
+ for (int i = 0; i < MAX_DEPTH; ++i) {
+ SystemClock.sleep(SHORT_SLEEP_MS);
+ mDevice.pressBack();
+ }
+ }
+
+ private boolean isDemoActivityInPip() {
+ return mPipHelper.isPipOnScreen(ACTIVITY_PLAYBACKOVERLAY);
+ }
+
+ private boolean isDemoActivityInFullScreen() {
+ return mPipHelper.isInFullscreen(ACTIVITY_PLAYBACKOVERLAY);
+ }
+
+ 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());
+ }
+ }
+}
+