chromium/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelManagerTest.java

// Copyright 2015 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 android.widget.LinearLayout;

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.CallbackHelper;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.StateChangeReason;
import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelManager.OverlayPanelManagerObserver;
import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelManager.PanelPriority;
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.resources.dynamics.DynamicResourceLoader;
import org.chromium.ui.test.util.BlankUiTestActivity;

import java.util.concurrent.TimeoutException;

/** Class responsible for testing the OverlayPanelManager. */
@RunWith(ChromeJUnit4ClassRunner.class)
@Batch(Batch.PER_CLASS)
public class OverlayPanelManagerTest {
    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
    // --------------------------------------------------------------------------------------------

    /** Mocks the ContextualSearchPanel, so it doesn't create WebContents. */
    private static class MockOverlayPanel extends OverlayPanel {
        private @PanelPriority int mPriority;
        private boolean mCanBeSuppressed;
        private ViewGroup mContainerView;
        private DynamicResourceLoader mResourceLoader;

        public MockOverlayPanel(
                Context context,
                LayoutManagerImpl layoutManager,
                OverlayPanelManager manager,
                BrowserControlsStateProvider browserControlsStateProvider,
                WindowAndroid windowAndroid,
                Profile profile,
                ViewGroup compositorViewHolder,
                Tab tab,
                @PanelPriority int priority,
                boolean canBeSuppressed) {
            super(
                    context,
                    layoutManager,
                    manager,
                    browserControlsStateProvider,
                    windowAndroid,
                    profile,
                    compositorViewHolder,
                    MOCK_TOOLBAR_HEIGHT,
                    () -> tab);
            mPriority = priority;
            mCanBeSuppressed = canBeSuppressed;
        }

        @Override
        public void setContainerView(ViewGroup container) {
            super.setContainerView(container);
            mContainerView = container;
        }

        @Override
        public ViewGroup getContainerView() {
            return mContainerView;
        }

        @Override
        public void setDynamicResourceLoader(DynamicResourceLoader loader) {
            super.setDynamicResourceLoader(loader);
            mResourceLoader = loader;
        }

        public DynamicResourceLoader getDynamicResourceLoader() {
            return mResourceLoader;
        }

        @Override
        public OverlayPanelContent createNewOverlayPanelContent() {
            return new MockOverlayPanelContent();
        }

        @Override
        public @PanelPriority int getPriority() {
            return mPriority;
        }

        @Override
        public boolean canBeSuppressed() {
            return mCanBeSuppressed;
        }

        @Override
        protected void animatePanelTo(float height, long duration) {
            // Do not create animations for tests.
        }

        @Override
        public void closePanel(@StateChangeReason int reason, boolean animate) {
            // Immediately call onClosed rather than wait for animation to finish.
            onClosed(reason);
        }

        /** Override creation and destruction of the WebContents as they rely on native methods. */
        private static class MockOverlayPanelContent extends OverlayPanelContent {
            public MockOverlayPanelContent() {
                super(null, null, null, null, 0, null, null, null);
            }

            @Override
            public void removeLastHistoryEntry(String url, long timeInMs) {}
        }
    }

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

    @Before
    public void setupTest() {
        mWindowAndroid =
                ThreadUtils.runOnUiThreadBlocking(
                        () -> {
                            mActivity = activityTestRule.getActivity();
                            return new ActivityWindowAndroid(
                                    mActivity,
                                    /* listenToActivityState= */ true,
                                    IntentRequestTracker.createFromActivity(mActivity));
                        });
    }

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

    // --------------------------------------------------------------------------------------------
    // Test Suite
    // --------------------------------------------------------------------------------------------

    @Test
    @MediumTest
    @Feature({"OverlayPanel"})
    @UiThreadTest
    public void testPanelRequestingShow() {
        OverlayPanelManager panelManager = new OverlayPanelManager();
        OverlayPanel panel =
                new MockOverlayPanel(
                        mActivity,
                        mLayoutManager,
                        panelManager,
                        mBrowserControlsStateProvider,
                        mWindowAndroid,
                        mProfile,
                        mCompositorViewHolder,
                        mTab,
                        PanelPriority.MEDIUM,
                        false);

        panel.requestPanelShow(StateChangeReason.UNKNOWN);

        Assert.assertTrue(panelManager.getActivePanel() == panel);
    }

    @Test
    @LargeTest
    @Feature({"OverlayPanel"})
    @UiThreadTest
    public void testPanelClosed() {
        OverlayPanelManager panelManager = new OverlayPanelManager();
        OverlayPanel panel =
                new MockOverlayPanel(
                        mActivity,
                        mLayoutManager,
                        panelManager,
                        mBrowserControlsStateProvider,
                        mWindowAndroid,
                        mProfile,
                        mCompositorViewHolder,
                        mTab,
                        PanelPriority.MEDIUM,
                        false);

        panel.requestPanelShow(StateChangeReason.UNKNOWN);
        panel.closePanel(StateChangeReason.UNKNOWN, false);

        Assert.assertTrue(panelManager.getActivePanel() == null);
    }

    @Test
    @MediumTest
    @Feature({"OverlayPanel"})
    @UiThreadTest
    public void testHighPrioritySuppressingLowPriority() {
        OverlayPanelManager panelManager = new OverlayPanelManager();
        OverlayPanel lowPriorityPanel =
                new MockOverlayPanel(
                        mActivity,
                        mLayoutManager,
                        panelManager,
                        mBrowserControlsStateProvider,
                        mWindowAndroid,
                        mProfile,
                        mCompositorViewHolder,
                        mTab,
                        PanelPriority.LOW,
                        false);
        OverlayPanel highPriorityPanel =
                new MockOverlayPanel(
                        mActivity,
                        mLayoutManager,
                        panelManager,
                        mBrowserControlsStateProvider,
                        mWindowAndroid,
                        mProfile,
                        mCompositorViewHolder,
                        mTab,
                        PanelPriority.HIGH,
                        false);

        lowPriorityPanel.requestPanelShow(StateChangeReason.UNKNOWN);
        highPriorityPanel.requestPanelShow(StateChangeReason.UNKNOWN);

        Assert.assertTrue(panelManager.getActivePanel() == highPriorityPanel);
    }

    @Test
    @MediumTest
    @Feature({"OverlayPanel"})
    @UiThreadTest
    public void testSuppressedPanelRestored() {
        OverlayPanelManager panelManager = new OverlayPanelManager();
        OverlayPanel lowPriorityPanel =
                new MockOverlayPanel(
                        mActivity,
                        mLayoutManager,
                        panelManager,
                        mBrowserControlsStateProvider,
                        mWindowAndroid,
                        mProfile,
                        mCompositorViewHolder,
                        mTab,
                        PanelPriority.LOW,
                        true);
        OverlayPanel highPriorityPanel =
                new MockOverlayPanel(
                        mActivity,
                        mLayoutManager,
                        panelManager,
                        mBrowserControlsStateProvider,
                        mWindowAndroid,
                        mProfile,
                        mCompositorViewHolder,
                        mTab,
                        PanelPriority.HIGH,
                        false);

        lowPriorityPanel.requestPanelShow(StateChangeReason.UNKNOWN);
        highPriorityPanel.requestPanelShow(StateChangeReason.UNKNOWN);
        highPriorityPanel.closePanel(StateChangeReason.UNKNOWN, false);

        Assert.assertTrue(panelManager.getActivePanel() == lowPriorityPanel);
    }

    @Test
    @MediumTest
    @Feature({"OverlayPanel"})
    @UiThreadTest
    public void testUnsuppressiblePanelNotRestored() {
        OverlayPanelManager panelManager = new OverlayPanelManager();
        OverlayPanel lowPriorityPanel =
                new MockOverlayPanel(
                        mActivity,
                        mLayoutManager,
                        panelManager,
                        mBrowserControlsStateProvider,
                        mWindowAndroid,
                        mProfile,
                        mCompositorViewHolder,
                        mTab,
                        PanelPriority.LOW,
                        false);
        OverlayPanel highPriorityPanel =
                new MockOverlayPanel(
                        mActivity,
                        mLayoutManager,
                        panelManager,
                        mBrowserControlsStateProvider,
                        mWindowAndroid,
                        mProfile,
                        mCompositorViewHolder,
                        mTab,
                        PanelPriority.HIGH,
                        false);

        lowPriorityPanel.requestPanelShow(StateChangeReason.UNKNOWN);
        highPriorityPanel.requestPanelShow(StateChangeReason.UNKNOWN);
        highPriorityPanel.closePanel(StateChangeReason.UNKNOWN, false);

        Assert.assertTrue(panelManager.getActivePanel() == null);
    }

    @Test
    @MediumTest
    @Feature({"OverlayPanel"})
    @UiThreadTest
    public void testSuppressedPanelClosedBeforeRestore() {
        OverlayPanelManager panelManager = new OverlayPanelManager();
        OverlayPanel lowPriorityPanel =
                new MockOverlayPanel(
                        mActivity,
                        mLayoutManager,
                        panelManager,
                        mBrowserControlsStateProvider,
                        mWindowAndroid,
                        mProfile,
                        mCompositorViewHolder,
                        mTab,
                        PanelPriority.LOW,
                        true);
        OverlayPanel highPriorityPanel =
                new MockOverlayPanel(
                        mActivity,
                        mLayoutManager,
                        panelManager,
                        mBrowserControlsStateProvider,
                        mWindowAndroid,
                        mProfile,
                        mCompositorViewHolder,
                        mTab,
                        PanelPriority.HIGH,
                        false);

        lowPriorityPanel.requestPanelShow(StateChangeReason.UNKNOWN);
        highPriorityPanel.requestPanelShow(StateChangeReason.UNKNOWN);
        lowPriorityPanel.closePanel(StateChangeReason.UNKNOWN, false);
        highPriorityPanel.closePanel(StateChangeReason.UNKNOWN, false);

        Assert.assertEquals(null, panelManager.getActivePanel());
        Assert.assertEquals(0, panelManager.getSuppressedQueueSize());
    }

    @Test
    @MediumTest
    @Feature({"OverlayPanel"})
    @UiThreadTest
    public void testSuppressedPanelPriority() {
        OverlayPanelManager panelManager = new OverlayPanelManager();
        OverlayPanel lowPriorityPanel =
                new MockOverlayPanel(
                        mActivity,
                        mLayoutManager,
                        panelManager,
                        mBrowserControlsStateProvider,
                        mWindowAndroid,
                        mProfile,
                        mCompositorViewHolder,
                        mTab,
                        PanelPriority.LOW,
                        true);
        OverlayPanel mediumPriorityPanel =
                new MockOverlayPanel(
                        mActivity,
                        mLayoutManager,
                        panelManager,
                        mBrowserControlsStateProvider,
                        mWindowAndroid,
                        mProfile,
                        mCompositorViewHolder,
                        mTab,
                        PanelPriority.MEDIUM,
                        true);
        OverlayPanel highPriorityPanel =
                new MockOverlayPanel(
                        mActivity,
                        mLayoutManager,
                        panelManager,
                        mBrowserControlsStateProvider,
                        mWindowAndroid,
                        mProfile,
                        mCompositorViewHolder,
                        mTab,
                        PanelPriority.HIGH,
                        false);

        // Only one panel is showing, should be medium priority.
        mediumPriorityPanel.requestPanelShow(StateChangeReason.UNKNOWN);
        Assert.assertEquals(panelManager.getActivePanel(), mediumPriorityPanel);

        // High priority should have taken preciedence.
        highPriorityPanel.requestPanelShow(StateChangeReason.UNKNOWN);
        Assert.assertEquals(panelManager.getActivePanel(), highPriorityPanel);

        // Low priority will be suppressed; high priority should still be showing.
        lowPriorityPanel.requestPanelShow(StateChangeReason.UNKNOWN);
        Assert.assertEquals(panelManager.getActivePanel(), highPriorityPanel);

        // Start closing panels.
        highPriorityPanel.closePanel(StateChangeReason.UNKNOWN, false);

        // After high priority is closed, the medium priority panel should be visible.
        Assert.assertEquals(panelManager.getActivePanel(), mediumPriorityPanel);
        mediumPriorityPanel.closePanel(StateChangeReason.UNKNOWN, false);

        // Finally the low priority panel should be showing.
        Assert.assertEquals(panelManager.getActivePanel(), lowPriorityPanel);
        lowPriorityPanel.closePanel(StateChangeReason.UNKNOWN, false);

        // All panels are closed now.
        Assert.assertEquals(null, panelManager.getActivePanel());
        Assert.assertEquals(0, panelManager.getSuppressedQueueSize());
    }

    @Test
    @MediumTest
    @Feature({"OverlayPanel"})
    @UiThreadTest
    public void testSuppressedPanelOrder() {
        OverlayPanelManager panelManager = new OverlayPanelManager();
        OverlayPanel lowPriorityPanel =
                new MockOverlayPanel(
                        mActivity,
                        mLayoutManager,
                        panelManager,
                        mBrowserControlsStateProvider,
                        mWindowAndroid,
                        mProfile,
                        mCompositorViewHolder,
                        mTab,
                        PanelPriority.LOW,
                        true);
        OverlayPanel mediumPriorityPanel =
                new MockOverlayPanel(
                        mActivity,
                        mLayoutManager,
                        panelManager,
                        mBrowserControlsStateProvider,
                        mWindowAndroid,
                        mProfile,
                        mCompositorViewHolder,
                        mTab,
                        PanelPriority.MEDIUM,
                        true);
        OverlayPanel highPriorityPanel =
                new MockOverlayPanel(
                        mActivity,
                        mLayoutManager,
                        panelManager,
                        mBrowserControlsStateProvider,
                        mWindowAndroid,
                        mProfile,
                        mCompositorViewHolder,
                        mTab,
                        PanelPriority.HIGH,
                        false);

        // Odd ordering for showing panels should still produce ordered suppression.
        highPriorityPanel.requestPanelShow(StateChangeReason.UNKNOWN);
        lowPriorityPanel.requestPanelShow(StateChangeReason.UNKNOWN);
        mediumPriorityPanel.requestPanelShow(StateChangeReason.UNKNOWN);

        Assert.assertEquals(2, panelManager.getSuppressedQueueSize());

        // Start closing panels.
        highPriorityPanel.closePanel(StateChangeReason.UNKNOWN, false);

        // After high priority is closed, the medium priority panel should be visible.
        Assert.assertEquals(panelManager.getActivePanel(), mediumPriorityPanel);
        mediumPriorityPanel.closePanel(StateChangeReason.UNKNOWN, false);

        // Finally the low priority panel should be showing.
        Assert.assertEquals(panelManager.getActivePanel(), lowPriorityPanel);
        lowPriorityPanel.closePanel(StateChangeReason.UNKNOWN, false);

        // All panels are closed now.
        Assert.assertTrue(panelManager.getActivePanel() == null);
        Assert.assertEquals(0, panelManager.getSuppressedQueueSize());
    }

    @Test
    @MediumTest
    @Feature({"OverlayPanel"})
    @UiThreadTest
    public void testLatePanelGetsNecessaryVars() {
        OverlayPanelManager panelManager = new OverlayPanelManager();
        MockOverlayPanel earlyPanel =
                new MockOverlayPanel(
                        mActivity,
                        mLayoutManager,
                        panelManager,
                        mBrowserControlsStateProvider,
                        mWindowAndroid,
                        mProfile,
                        mCompositorViewHolder,
                        mTab,
                        PanelPriority.MEDIUM,
                        true);

        // Set necessary vars before any other panels are registered in the manager.
        panelManager.setContainerView(new LinearLayout(mActivity));
        panelManager.setDynamicResourceLoader(new DynamicResourceLoader(0, null));

        MockOverlayPanel latePanel =
                new MockOverlayPanel(
                        mActivity,
                        mLayoutManager,
                        panelManager,
                        mBrowserControlsStateProvider,
                        mWindowAndroid,
                        mProfile,
                        mCompositorViewHolder,
                        mTab,
                        PanelPriority.MEDIUM,
                        true);

        Assert.assertTrue(earlyPanel.getContainerView() == latePanel.getContainerView());
        Assert.assertTrue(
                earlyPanel.getDynamicResourceLoader() == latePanel.getDynamicResourceLoader());
    }

    @Test
    @MediumTest
    @Feature({"OverlayPanel"})
    @UiThreadTest
    public void testManagerObserver() throws TimeoutException {
        final CallbackHelper shownHelper = new CallbackHelper();
        final CallbackHelper hiddenHelper = new CallbackHelper();

        OverlayPanelManager panelManager = new OverlayPanelManager();
        panelManager.addObserver(
                new OverlayPanelManagerObserver() {
                    @Override
                    public void onOverlayPanelShown() {
                        shownHelper.notifyCalled();
                    }

                    @Override
                    public void onOverlayPanelHidden() {
                        hiddenHelper.notifyCalled();
                    }
                });

        OverlayPanel lowPriorityPanel =
                new MockOverlayPanel(
                        mActivity,
                        mLayoutManager,
                        panelManager,
                        mBrowserControlsStateProvider,
                        mWindowAndroid,
                        mProfile,
                        mCompositorViewHolder,
                        mTab,
                        PanelPriority.LOW,
                        true);
        OverlayPanel highPriorityPanel =
                new MockOverlayPanel(
                        mActivity,
                        mLayoutManager,
                        panelManager,
                        mBrowserControlsStateProvider,
                        mWindowAndroid,
                        mProfile,
                        mCompositorViewHolder,
                        mTab,
                        PanelPriority.HIGH,
                        false);

        lowPriorityPanel.requestPanelShow(StateChangeReason.UNKNOWN);

        // Wait for the panel to be shown.
        shownHelper.waitForCallback(0);

        highPriorityPanel.requestPanelShow(StateChangeReason.UNKNOWN);

        // Wait for the low priority panel to be hidden and high priority panel to be shown.
        hiddenHelper.waitForCallback(0);
        shownHelper.waitForCallback(1);

        highPriorityPanel.closePanel(StateChangeReason.UNKNOWN, false);

        // Wait for the open panel to be closed and the supressed one to be re-shown.
        hiddenHelper.waitForCallback(1);
        shownHelper.waitForCallback(2);

        Assert.assertEquals(
                "Shown event should have been called three times.", 3, shownHelper.getCallCount());
        Assert.assertEquals(
                "Hidden event should have been called twice.", 2, hiddenHelper.getCallCount());
    }
}