chromium/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelBaseTest.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.chrome.browser.compositor.bottombar;

import android.app.Activity;
import android.content.Context;
import android.view.ViewGroup;

import androidx.annotation.Nullable;
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.LargeTest;
import androidx.test.filters.MediumTest;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import org.chromium.base.ThreadUtils;
import org.chromium.base.test.BaseActivityTestRule;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.PanelState;
import org.chromium.chrome.browser.compositor.layouts.LayoutManagerImpl;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.ui.base.ActivityWindowAndroid;
import org.chromium.ui.base.IntentRequestTracker;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.test.util.BlankUiTestActivity;

/** Tests logic in the OverlayPanelBase. */
@RunWith(ChromeJUnit4ClassRunner.class)
@Batch(Batch.PER_CLASS)
public class OverlayPanelBaseTest {
    private static final float UPWARD_VELOCITY = -1.0f;
    private static final float DOWNWARD_VELOCITY = 1.0f;

    private static final float MOCK_PEEKED_HEIGHT = 200.0f;
    private static final float MOCK_EXPANDED_HEIGHT = 400.0f;
    private static final float MOCK_MAXIMIZED_HEIGHT = 600.0f;

    private static final int MOCK_TOOLBAR_HEIGHT = 100;

    @ClassRule
    public static BaseActivityTestRule<BlankUiTestActivity> activityTestRule =
            new BaseActivityTestRule<>(BlankUiTestActivity.class);

    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();

    @Mock private LayoutManagerImpl mLayoutManager;
    @Mock private BrowserControlsStateProvider mBrowserControlsStateProvider;
    @Mock private ViewGroup mCompositorViewHolder;
    @Mock private Profile mProfile;
    @Mock private Tab mTab;

    Activity mActivity;
    ActivityWindowAndroid mWindowAndroid;
    MockOverlayPanel mNoExpandPanel;
    MockOverlayPanel mExpandPanel;

    /** Mock OverlayPanel. */
    private static class MockOverlayPanel extends OverlayPanel {
        public MockOverlayPanel(
                Context context,
                LayoutManagerImpl layoutManager,
                OverlayPanelManager manager,
                BrowserControlsStateProvider browserControlsStateProvider,
                WindowAndroid windowAndroid,
                Profile profile,
                ViewGroup compositorViewHolder,
                Tab tab) {
            super(
                    context,
                    layoutManager,
                    manager,
                    browserControlsStateProvider,
                    windowAndroid,
                    profile,
                    compositorViewHolder,
                    MOCK_TOOLBAR_HEIGHT,
                    () -> tab);
        }

        /** Expose protected super method as public. */
        @Override
        public @PanelState int findNearestPanelStateFromHeight(
                float desiredHeight, float velocity) {
            return super.findNearestPanelStateFromHeight(desiredHeight, velocity);
        }

        /** Override to return arbitrary test heights. */
        @Override
        public float getPanelHeightFromState(@Nullable @PanelState Integer state) {
            switch (state) {
                case PanelState.PEEKED:
                    return MOCK_PEEKED_HEIGHT;
                case PanelState.EXPANDED:
                    return MOCK_EXPANDED_HEIGHT;
                case PanelState.MAXIMIZED:
                    return MOCK_MAXIMIZED_HEIGHT;
                default:
                    return 0.0f;
            }
        }
    }

    /** A MockOverlayPanel that does not support the EXPANDED panel state. */
    private static class NoExpandMockOverlayPanel extends MockOverlayPanel {
        public NoExpandMockOverlayPanel(
                Context context,
                LayoutManagerImpl layoutManager,
                OverlayPanelManager manager,
                BrowserControlsStateProvider browserControlsStateProvider,
                WindowAndroid windowAndroid,
                Profile profile,
                ViewGroup compositorViewHolder,
                Tab tab) {
            super(
                    context,
                    layoutManager,
                    manager,
                    browserControlsStateProvider,
                    windowAndroid,
                    profile,
                    compositorViewHolder,
                    tab);
        }

        @Override
        public float getThresholdToNextState() {
            return 0.3f;
        }
    }

    @BeforeClass
    public static void setupSuite() {
        activityTestRule.launchActivity(null);
    }

    @Before
    public void setupTest() {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mActivity = activityTestRule.getActivity();
                    mWindowAndroid =
                            new ActivityWindowAndroid(
                                    mActivity,
                                    /* listenToActivityState= */ true,
                                    IntentRequestTracker.createFromActivity(mActivity));
                    OverlayPanelManager panelManager = new OverlayPanelManager();
                    mExpandPanel =
                            new MockOverlayPanel(
                                    mActivity,
                                    mLayoutManager,
                                    panelManager,
                                    mBrowserControlsStateProvider,
                                    mWindowAndroid,
                                    mProfile,
                                    mCompositorViewHolder,
                                    mTab);
                    mNoExpandPanel =
                            new NoExpandMockOverlayPanel(
                                    mActivity,
                                    mLayoutManager,
                                    panelManager,
                                    mBrowserControlsStateProvider,
                                    mWindowAndroid,
                                    mProfile,
                                    mCompositorViewHolder,
                                    mTab);
                });
    }

    @After
    public void tearDown() {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mWindowAndroid.destroy();
                });
    }

    // Start OverlayPanelBase test suite.

    /**
     * Tests that a panel will move to the correct state based on current position and swipe
     * velocity.
     */
    @Test
    @MediumTest
    @Feature({"OverlayPanelBase"})
    @UiThreadTest
    public void testExpandingPanelMovesToCorrectState() {
        final float threshold = mExpandPanel.getThresholdToNextState();
        final float peekToExpHeight = MOCK_EXPANDED_HEIGHT - MOCK_PEEKED_HEIGHT;
        final float expToMaxHeight = MOCK_MAXIMIZED_HEIGHT - MOCK_EXPANDED_HEIGHT;

        // The boundry for moving to the next state will be different depending on the direction
        // of the swipe and the threshold. In the default case, the threshold is 0.5, meaning the
        // the panel must be half way to the next state in order to animate to it. In other cases
        // where the threshold is 0.3, for example, the boundry will be closer to the top when
        // swiping down and closer to the bottom when swiping up. Ultimately this means it will
        // take less effort to swipe to a different state.
        // NOTE(mdjones): Consider making these constants to exclude computation from these tests.
        final float peekToExpBound = threshold * peekToExpHeight + MOCK_PEEKED_HEIGHT;
        final float expToPeekBound = (1.0f - threshold) * peekToExpHeight + MOCK_PEEKED_HEIGHT;
        final float expToMaxBound = threshold * expToMaxHeight + MOCK_EXPANDED_HEIGHT;
        final float maxToExpBound = (1.0f - threshold) * expToMaxHeight + MOCK_EXPANDED_HEIGHT;

        // Between PEEKING and EXPANDED past the threshold in the up direction.
        @PanelState
        int nextState =
                mExpandPanel.findNearestPanelStateFromHeight(peekToExpBound + 1, UPWARD_VELOCITY);
        Assert.assertTrue(nextState == PanelState.EXPANDED);

        // Between PEEKING and EXPANDED before the threshold in the up direction.
        nextState =
                mExpandPanel.findNearestPanelStateFromHeight(peekToExpBound - 1, UPWARD_VELOCITY);
        Assert.assertTrue(nextState == PanelState.PEEKED);

        // Between PEEKING and EXPANDED before the threshold in the down direction.
        nextState =
                mExpandPanel.findNearestPanelStateFromHeight(expToPeekBound + 1, DOWNWARD_VELOCITY);
        Assert.assertTrue(nextState == PanelState.EXPANDED);

        // Between PEEKING and EXPANDED past the threshold in the down direction.
        nextState =
                mExpandPanel.findNearestPanelStateFromHeight(expToPeekBound - 1, DOWNWARD_VELOCITY);
        Assert.assertTrue(nextState == PanelState.PEEKED);

        // Between EXPANDED and MAXIMIZED past the threshold in the up direction.
        nextState =
                mExpandPanel.findNearestPanelStateFromHeight(expToMaxBound + 1, UPWARD_VELOCITY);
        Assert.assertTrue(nextState == PanelState.MAXIMIZED);

        // Between EXPANDED and MAXIMIZED before the threshold in the up direction.
        nextState =
                mExpandPanel.findNearestPanelStateFromHeight(expToMaxBound - 1, UPWARD_VELOCITY);
        Assert.assertTrue(nextState == PanelState.EXPANDED);

        // Between EXPANDED and MAXIMIZED past the threshold in the down direction.
        nextState =
                mExpandPanel.findNearestPanelStateFromHeight(maxToExpBound - 1, DOWNWARD_VELOCITY);
        Assert.assertTrue(nextState == PanelState.EXPANDED);

        // Between EXPANDED and MAXIMIZED before the threshold in the down direction.
        nextState =
                mExpandPanel.findNearestPanelStateFromHeight(maxToExpBound + 1, DOWNWARD_VELOCITY);
        Assert.assertTrue(nextState == PanelState.MAXIMIZED);
    }

    /** Tests that a panel will be closed if the desired height is negative. */
    @Test
    @MediumTest
    @Feature({"OverlayPanelBase"})
    @UiThreadTest
    public void testNegativeHeightClosesPanel() {
        final float belowPeek = MOCK_PEEKED_HEIGHT - 1000;

        @PanelState
        int nextState = mExpandPanel.findNearestPanelStateFromHeight(belowPeek, DOWNWARD_VELOCITY);
        Assert.assertTrue(nextState == PanelState.CLOSED);

        nextState = mNoExpandPanel.findNearestPanelStateFromHeight(belowPeek, DOWNWARD_VELOCITY);
        Assert.assertTrue(nextState == PanelState.CLOSED);

        // Make sure nothing bad happens if velocity is upward (this should never happen).
        nextState = mExpandPanel.findNearestPanelStateFromHeight(belowPeek, UPWARD_VELOCITY);
        Assert.assertTrue(nextState == PanelState.CLOSED);

        nextState = mNoExpandPanel.findNearestPanelStateFromHeight(belowPeek, UPWARD_VELOCITY);
        Assert.assertTrue(nextState == PanelState.CLOSED);
    }

    /** Tests that a panel is only maximized when desired height is far above the max. */
    @Test
    @LargeTest
    @Feature({"OverlayPanelBase"})
    @UiThreadTest
    public void testLargeDesiredHeightIsMaximized() {
        final float aboveMax = MOCK_MAXIMIZED_HEIGHT + 1000;

        @PanelState
        int nextState = mExpandPanel.findNearestPanelStateFromHeight(aboveMax, UPWARD_VELOCITY);
        Assert.assertTrue(nextState == PanelState.MAXIMIZED);

        nextState = mNoExpandPanel.findNearestPanelStateFromHeight(aboveMax, UPWARD_VELOCITY);
        Assert.assertTrue(nextState == PanelState.MAXIMIZED);

        // Make sure nothing bad happens if velocity is downward (this should never happen).
        nextState = mExpandPanel.findNearestPanelStateFromHeight(aboveMax, DOWNWARD_VELOCITY);
        Assert.assertTrue(nextState == PanelState.MAXIMIZED);

        nextState = mNoExpandPanel.findNearestPanelStateFromHeight(aboveMax, DOWNWARD_VELOCITY);
        Assert.assertTrue(nextState == PanelState.MAXIMIZED);
    }
}