chromium/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTestUtils.java

// Copyright 2018 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.chrome.browser.fullscreen;

import android.os.SystemClock;

import org.hamcrest.Matchers;
import org.junit.Assert;

import org.chromium.base.BuildInfo;
import org.chromium.base.ThreadUtils;
import org.chromium.base.task.PostTask;
import org.chromium.base.task.TaskTraits;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
import org.chromium.chrome.browser.browser_controls.BrowserStateBrowserControlsVisibilityDelegate;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.content_public.browser.RenderCoordinates;
import org.chromium.content_public.browser.test.util.TouchCommon;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/** Utility methods for testing the {@link BrowserControlsManager}. */
public class FullscreenManagerTestUtils {
    /**
     * Scrolls the underlying web page to show or hide the browser controls.
     *
     * @param testRule The test rule for the currently running test.
     * @param show Whether the browser controls should be shown.
     */
    public static void scrollBrowserControls(ChromeActivityTestRule testRule, boolean show) {
        BrowserControlsStateProvider browserControlsStateProvider =
                testRule.getActivity().getBrowserControlsManager();
        int browserControlsHeight = browserControlsStateProvider.getTopControlsHeight();

        waitForPageToBeScrollable(testRule.getActivity().getActivityTab());

        float dragX = 50f;
        // Use a larger scroll range than the height of the browser controls to ensure we overcome
        // the delay in a scroll start being sent.
        float dragStartY = browserControlsHeight * 3;
        float dragEndY = dragStartY - browserControlsHeight * 2;
        int expectedPosition = -browserControlsHeight;

        // The top back button toolbar will still be shown on automotive, even in fullscreen mode.
        if (show && !BuildInfo.getInstance().isAutomotive) {
            expectedPosition = 0;
            float tempDragStartY = dragStartY;
            dragStartY = dragEndY;
            dragEndY = tempDragStartY;
        }
        long downTime = SystemClock.uptimeMillis();
        TouchCommon.performDrag(
                testRule.getActivity(), dragX, dragX, dragStartY, dragEndY, 100, downTime);
        waitForBrowserControlsPosition(testRule, expectedPosition);
    }

    /**
     * Waits for the browser controls to reach the specified position.
     *
     * @param testRule The test rule for the currently running test.
     * @param position The desired top controls offset.
     */
    public static void waitForBrowserControlsPosition(
            ChromeActivityTestRule testRule, int position) {
        final BrowserControlsStateProvider browserControlsStateProvider =
                testRule.getActivity().getBrowserControlsManager();
        CriteriaHelper.pollUiThread(
                () -> {
                    Criteria.checkThat(
                            browserControlsStateProvider.getTopControlOffset(),
                            Matchers.is(position));
                });
    }

    /**
     * Waits for the base page to be scrollable.
     *
     * @param tab The current activity tab.
     */
    public static void waitForPageToBeScrollable(final Tab tab) {
        CriteriaHelper.pollUiThread(
                () -> {
                    Criteria.checkThat(
                            RenderCoordinates.fromWebContents(tab.getWebContents())
                                    .getContentHeightPixInt(),
                            Matchers.greaterThan(tab.getContentView().getHeight()));
                });
    }

    /**
     * Waits for the browser controls to be moveable by user gesture.
     *
     * <p>This function requires the browser controls to start fully visible. Then it ensures that
     * at some point the controls can be moved by user gesture. It will then fully cycle the top
     * controls to entirely hidden and back to fully shown.
     *
     * @param testRule The test rule for the currently running test.
     * @param tab The current activity tab.
     */
    public static void waitForBrowserControlsToBeMoveable(
            ChromeActivityTestRule testRule, final Tab tab) {
        waitForBrowserControlsPosition(testRule, 0);

        final CallbackHelper contentMovedCallback = new CallbackHelper();
        final BrowserControlsStateProvider browserControlsStateProvider =
                testRule.getActivity().getBrowserControlsManager();
        final float initialVisibleContentOffset =
                browserControlsStateProvider.getTopVisibleContentOffset();

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    browserControlsStateProvider.addObserver(
                            new BrowserControlsStateProvider.Observer() {
                                @Override
                                public void onControlsOffsetChanged(
                                        int topOffset,
                                        int topControlsMinHeightOffset,
                                        int bottomOffset,
                                        int bottomControlsMinHeightOffset,
                                        boolean needsAnimate,
                                        boolean isVisibilityForced) {
                                    if (browserControlsStateProvider.getTopVisibleContentOffset()
                                            != initialVisibleContentOffset) {
                                        contentMovedCallback.notifyCalled();
                                        browserControlsStateProvider.removeObserver(this);
                                    }
                                }
                            });
                });

        float dragX = 50f;
        float dragStartY = tab.getView().getHeight() - 50f;

        for (int i = 0; i < 10; i++) {
            float dragEndY = dragStartY - browserControlsStateProvider.getTopControlsHeight();

            long downTime = SystemClock.uptimeMillis();
            TouchCommon.performDrag(
                    testRule.getActivity(), dragX, dragX, dragStartY, dragEndY, 100, downTime);

            try {
                contentMovedCallback.waitForCallback(0, 1, 500, TimeUnit.MILLISECONDS);
                scrollBrowserControls(testRule, false);
                scrollBrowserControls(testRule, true);
                return;
            } catch (TimeoutException e) {
                // Ignore and retry
            }
        }

        Assert.fail("Visible content never moved as expected.");
    }

    /** Disable any browser visibility overrides for testing. */
    public static void disableBrowserOverrides() {
        ThreadUtils.runOnUiThreadBlocking(
                () -> BrowserStateBrowserControlsVisibilityDelegate.disableForTesting());
    }

    public static void fling(ChromeActivityTestRule testRule, final int vx, final int vy) {
        PostTask.runOrPostTask(
                TaskTraits.UI_DEFAULT,
                () -> {
                    testRule.getWebContents()
                            .getEventForwarder()
                            .startFling(
                                    SystemClock.uptimeMillis(),
                                    vx,
                                    vy,
                                    /* synthetic_scroll= */ false,
                                    /* prevent_boosting= */ false);
                });
    }
}