chromium/content/public/android/javatests/src/org/chromium/content/browser/VideoFullscreenOrientationLockTest.java

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.content.browser;

import android.content.pm.ActivityInfo;
import android.graphics.Rect;

import androidx.test.filters.MediumTest;

import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.CriteriaNotSatisfiedException;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.Restriction;
import org.chromium.content_public.browser.test.ContentJUnit4ClassRunner;
import org.chromium.content_public.browser.test.util.DOMUtils;
import org.chromium.content_public.browser.test.util.JavaScriptUtils;
import org.chromium.content_shell_apk.ContentShellActivityTestRule;
import org.chromium.media.MediaSwitches;
import org.chromium.ui.test.util.UiRestriction;

import java.util.concurrent.TimeoutException;

/**
 * Integration tests for the feature that auto locks the orientation when a video goes fullscreen.
 * See also chrome layer org.chromium.chrome.browser.VideoFullscreenOrientationLockChromeTest
 */
@RunWith(ContentJUnit4ClassRunner.class)
@CommandLineFlags.Add({MediaSwitches.AUTOPLAY_NO_GESTURE_REQUIRED_POLICY})
public class VideoFullscreenOrientationLockTest {
    @Rule
    public ContentShellActivityTestRule mActivityTestRule = new ContentShellActivityTestRule();

    private static final String TEST_URL = "content/test/data/media/video-player.html";
    private static final String VIDEO_ID = "video";

    private void waitForContentsFullscreenState(boolean fullscreenValue) {
        CriteriaHelper.pollInstrumentationThread(
                () -> {
                    try {
                        Criteria.checkThat(
                                DOMUtils.isFullscreen(mActivityTestRule.getWebContents()),
                                Matchers.is(fullscreenValue));
                    } catch (TimeoutException ex) {
                        throw new CriteriaNotSatisfiedException(ex);
                    }
                });
    }

    private boolean isScreenOrientationLocked() {
        return mActivityTestRule.getActivity().getRequestedOrientation()
                != ActivityInfo.SCREEN_ORIENTATION_USER;
    }

    private boolean isScreenOrientationLandscape() throws TimeoutException {
        StringBuilder sb = new StringBuilder();
        sb.append("(function() {");
        sb.append("  return  screen.orientation.type.startsWith('landscape');");
        sb.append("})();");

        return JavaScriptUtils.executeJavaScriptAndWaitForResult(
                        mActivityTestRule.getWebContents(), sb.toString())
                .equals("true");
    }

    private void waitUntilLockedToLandscape() {
        CriteriaHelper.pollInstrumentationThread(
                () -> {
                    try {
                        Criteria.checkThat(isScreenOrientationLocked(), Matchers.is(true));
                        Criteria.checkThat(isScreenOrientationLandscape(), Matchers.is(true));
                    } catch (TimeoutException e) {
                        throw new CriteriaNotSatisfiedException(e);
                    }
                });
    }

    private void waitUntilUnlocked() {
        CriteriaHelper.pollInstrumentationThread(
                () -> Criteria.checkThat(isScreenOrientationLocked(), Matchers.is(false)));
    }

    // TODO(mlamouri): move these constants and bounds  methods to a dedicated helper file for
    // media tests.
    private static final int TIMELINE_HEIGHT = 24;
    private static final int BUTTON_PANEL_HEIGHT = 48;
    private static final int BUTTON_WIDTH = 48;

    private Rect buttonPanelBounds(Rect videoRect) {
        int left = videoRect.left;
        int right = videoRect.right;
        int bottom = videoRect.bottom - TIMELINE_HEIGHT;
        int top = bottom - BUTTON_PANEL_HEIGHT;
        return new Rect(left, top, right, bottom);
    }

    private Rect fullscreenButtonBounds(Rect videoRect) {
        Rect panel = buttonPanelBounds(videoRect);

        // In these tests, we have no overflow items, so the fullscreen button is the rightmost
        // button in the panel.
        int right = panel.right;
        int left = right - BUTTON_WIDTH;
        return new Rect(left, panel.top, right, panel.bottom);
    }

    private boolean clickFullscreenButton() throws TimeoutException {
        return DOMUtils.clickRect(
                mActivityTestRule.getWebContents(),
                fullscreenButtonBounds(
                        DOMUtils.getNodeBounds(mActivityTestRule.getWebContents(), VIDEO_ID)));
    }

    @Before
    public void setUp() {
        mActivityTestRule.launchContentShellWithUrlSync(TEST_URL);
    }

    @Test
    @MediumTest
    @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE})
    public void testEnterExitFullscreenWithControlsButton() throws Exception {
        // Start playback to guarantee it's properly loaded.
        Assert.assertTrue(DOMUtils.isMediaPaused(mActivityTestRule.getWebContents(), VIDEO_ID));
        DOMUtils.playMedia(mActivityTestRule.getWebContents(), VIDEO_ID);
        DOMUtils.waitForMediaPlay(mActivityTestRule.getWebContents(), VIDEO_ID);

        // Simulate click on fullscreen button.
        Assert.assertTrue(clickFullscreenButton());
        waitForContentsFullscreenState(true);

        // Should be locked to landscape now, `waitUntilLockedToLandscape` will throw otherwise.
        waitUntilLockedToLandscape();

        // Leave fullscreen by clicking back on the button.
        // Use a loop to retry due to fullscreen re-layout.
        Exception lastException = null;
        for (int i = 0; i < 10; ++i) {
            Thread.sleep(100);
            if (!clickFullscreenButton()) {
                continue;
            }
            try {
                waitForContentsFullscreenState(false);
                waitUntilUnlocked();
                return;
            } catch (CriteriaHelper.TimeoutException e) {
                lastException = e;
            }
        }
        throw lastException;
    }

    @Test
    @MediumTest
    @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE})
    @DisabledTest(message = "https://crbug.com/1105614")
    public void testEnterExitFullscreenWithAPI() throws Exception {
        // Start playback to guarantee it's properly loaded.
        Assert.assertTrue(DOMUtils.isMediaPaused(mActivityTestRule.getWebContents(), VIDEO_ID));
        DOMUtils.playMedia(mActivityTestRule.getWebContents(), VIDEO_ID);
        DOMUtils.waitForMediaPlay(mActivityTestRule.getWebContents(), VIDEO_ID);

        // Trigger requestFullscreen() via a click on a button.
        Assert.assertTrue(DOMUtils.clickNode(mActivityTestRule.getWebContents(), "fullscreen"));
        waitForContentsFullscreenState(true);

        // Should be locked to landscape now, `waitUntilLockedToLandscape` will throw otherwise.
        waitUntilLockedToLandscape();

        // Leave fullscreen from API.
        DOMUtils.exitFullscreen(mActivityTestRule.getWebContents());
        waitForContentsFullscreenState(false);
        waitUntilUnlocked();
    }

    @Test
    @MediumTest
    @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE})
    @DisabledTest(message = "crbug.com/1228632")
    public void testExitFullscreenByRemovingVideo() throws Exception {
        // Start playback to guarantee it's properly loaded.
        Assert.assertTrue(DOMUtils.isMediaPaused(mActivityTestRule.getWebContents(), VIDEO_ID));
        DOMUtils.playMedia(mActivityTestRule.getWebContents(), VIDEO_ID);
        DOMUtils.waitForMediaPlay(mActivityTestRule.getWebContents(), VIDEO_ID);

        // Trigger requestFullscreen() via a click on a button.
        Assert.assertTrue(DOMUtils.clickNode(mActivityTestRule.getWebContents(), "fullscreen"));
        waitForContentsFullscreenState(true);

        // Should be locked to landscape now, `waitUntilLockedToLandscape` will throw otherwise.
        waitUntilLockedToLandscape();

        // Leave fullscreen by removing video element from page.
        JavaScriptUtils.executeJavaScriptAndWaitForResult(
                mActivityTestRule.getWebContents(), "document.body.innerHTML = '';");
        waitForContentsFullscreenState(false);
        waitUntilUnlocked();
    }

    @Test
    @MediumTest
    @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE})
    @DisabledTest(message = "crbug.com/1228632")
    public void testExitFullscreenWithNavigation() throws Exception {
        // Start playback to guarantee it's properly loaded.
        Assert.assertTrue(DOMUtils.isMediaPaused(mActivityTestRule.getWebContents(), VIDEO_ID));
        DOMUtils.playMedia(mActivityTestRule.getWebContents(), VIDEO_ID);
        DOMUtils.waitForMediaPlay(mActivityTestRule.getWebContents(), VIDEO_ID);

        // Trigger requestFullscreen() via a click on a button.
        Assert.assertTrue(DOMUtils.clickNode(mActivityTestRule.getWebContents(), "fullscreen"));
        waitForContentsFullscreenState(true);

        // Should be locked to landscape now, `waitUntilLockedToLandscape` will throw otherwise.
        waitUntilLockedToLandscape();

        // Leave fullscreen by navigating page.
        JavaScriptUtils.executeJavaScriptAndWaitForResult(
                mActivityTestRule.getWebContents(), "location.reload();");
        waitForContentsFullscreenState(false);
        waitUntilUnlocked();
    }
}