chromium/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java

// Copyright 2020 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.tabbed_mode;

import static androidx.test.espresso.matcher.ViewMatchers.assertThat;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;

import android.content.Context;
import android.view.ContextThemeWrapper;
import android.view.Menu;
import android.view.View;
import android.widget.PopupMenu;

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.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;

import org.chromium.base.ContextUtils;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.supplier.OneshotSupplierImpl;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ActivityTabProvider;
import org.chromium.chrome.browser.bookmarks.BookmarkModel;
import org.chromium.chrome.browser.bookmarks.PowerBookmarkUtils;
import org.chromium.chrome.browser.commerce.ShoppingServiceFactory;
import org.chromium.chrome.browser.enterprise.util.ManagedBrowserUtils;
import org.chromium.chrome.browser.enterprise.util.ManagedBrowserUtilsJni;
import org.chromium.chrome.browser.feed.FeedFeatures;
import org.chromium.chrome.browser.feed.webfeed.WebFeedBridge;
import org.chromium.chrome.browser.feed.webfeed.WebFeedBridgeJni;
import org.chromium.chrome.browser.feed.webfeed.WebFeedSnackbarController;
import org.chromium.chrome.browser.incognito.reauth.IncognitoReauthController;
import org.chromium.chrome.browser.layouts.LayoutStateProvider;
import org.chromium.chrome.browser.layouts.LayoutType;
import org.chromium.chrome.browser.multiwindow.MultiWindowModeStateDispatcher;
import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
import org.chromium.chrome.browser.offlinepages.OfflinePageUtils;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.profiles.ProfileManager;
import org.chromium.chrome.browser.readaloud.ReadAloudController;
import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
import org.chromium.chrome.browser.signin.services.SigninManager;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelFilter;
import org.chromium.chrome.browser.tabmodel.TabModelFilterProvider;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.toolbar.ToolbarManager;
import org.chromium.chrome.browser.ui.appmenu.AppMenuDelegate;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.components.browser_ui.accessibility.PageZoomCoordinator;
import org.chromium.components.browser_ui.site_settings.WebsitePreferenceBridge;
import org.chromium.components.browser_ui.site_settings.WebsitePreferenceBridgeJni;
import org.chromium.components.commerce.core.ShoppingService;
import org.chromium.components.power_bookmarks.PowerBookmarkMeta;
import org.chromium.components.prefs.PrefService;
import org.chromium.components.signin.identitymanager.IdentityManager;
import org.chromium.components.webapps.AppBannerManager;
import org.chromium.components.webapps.AppBannerManagerJni;
import org.chromium.content_public.browser.NavigationController;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.url.JUnitTestGURLs;

import java.util.ArrayList;
import java.util.List;

/** Unit tests for {@link TabbedAppMenuPropertiesDelegate}. */
@RunWith(BaseRobolectricTestRunner.class)
public class TabbedAppMenuPropertiesDelegateUnitTest {
    // Constants defining flags that determines multi-window menu items visibility.
    private static final boolean TAB_M = true; // multiple tabs
    private static final boolean TAB_S = false;
    private static final boolean WIN_M = true; // in multi-window mode
    private static final boolean WIN_S = false;
    private static final boolean INST_M = true; // in multi-instance mode
    private static final boolean INST_S = false;
    private static final boolean TABLET = true;
    private static final boolean PHONE = false;
    private static final boolean API_YES = true; // multi-window API supported
    private static final boolean API_NO = false;
    private static final boolean MOVE_OTHER_YES =
            true; // multi-window move to other window supported
    private static final boolean MOVE_OTHER_NO = false;

    private static final boolean NEW_YES = true; // show 'new window'
    private static final boolean NEW_NO = false;
    private static final boolean MOVE_YES = true; // show 'move to other window'
    private static final boolean MOVE_NO = false;
    private static final Boolean X____ = null; // do not care

    @Rule public JniMocker jniMocker = new JniMocker();
    @Mock private ActivityTabProvider mActivityTabProvider;
    @Mock private Tab mTab;
    @Mock private WebContents mWebContents;
    @Mock private NavigationController mNavigationController;
    @Mock private MultiWindowModeStateDispatcher mMultiWindowModeStateDispatcher;
    @Mock private TabModelSelector mTabModelSelector;
    @Mock private TabModel mTabModel;
    @Mock private ToolbarManager mToolbarManager;
    @Mock private View mDecorView;
    @Mock private LayoutStateProvider mLayoutStateProvider;
    @Mock private ManagedBrowserUtils.Natives mManagedBrowserUtilsJniMock;
    @Mock private Profile mProfile;
    @Mock private AppMenuDelegate mAppMenuDelegate;
    @Mock private WebFeedSnackbarController.FeedLauncher mFeedLauncher;
    @Mock private ModalDialogManager mDialogManager;
    @Mock private SnackbarManager mSnackbarManager;
    @Mock private OfflinePageUtils.Internal mOfflinePageUtils;
    @Mock private SigninManager mSigninManager;
    @Mock private IdentityManager mIdentityManager;
    @Mock private IdentityServicesProvider mIdentityService;
    @Mock private TabModelFilterProvider mTabModelFilterProvider;
    @Mock private TabModelFilter mTabModelFilter;
    @Mock public WebsitePreferenceBridge.Natives mWebsitePreferenceBridgeJniMock;
    @Mock private IncognitoReauthController mIncognitoReauthControllerMock;
    @Mock private ShoppingService mShoppingService;
    @Mock private AppBannerManager.Natives mAppBannerManagerJniMock;
    @Mock private ReadAloudController mReadAloudController;
    @Mock private PrefService mPrefService;
    @Mock private WebFeedBridge.Natives mWebFeedBridgeJniMock;

    private OneshotSupplierImpl<LayoutStateProvider> mLayoutStateProviderSupplier =
            new OneshotSupplierImpl<>();
    private OneshotSupplierImpl<IncognitoReauthController> mIncognitoReauthControllerSupplier =
            new OneshotSupplierImpl<>();
    private ObservableSupplierImpl<BookmarkModel> mBookmarkModelSupplier =
            new ObservableSupplierImpl<>();
    private ObservableSupplierImpl<ReadAloudController> mReadAloudControllerSupplier =
            new ObservableSupplierImpl<>();

    private TabbedAppMenuPropertiesDelegate mTabbedAppMenuPropertiesDelegate;

    // Boolean flags to test multi-window menu visibility for various combinations.
    private boolean mIsMultiInstance;
    private boolean mIsMultiWindow;
    private boolean mIsTabletScreen;
    private boolean mIsMultiWindowApiSupported;
    private boolean mIsMoveToOtherWindowSupported;

    // Used to ensure all the combinations are tested.
    private boolean[] mFlagCombinations = new boolean[1 << 5];

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);

        mLayoutStateProviderSupplier.set(mLayoutStateProvider);
        mIncognitoReauthControllerSupplier.set(mIncognitoReauthControllerMock);
        mReadAloudControllerSupplier.set(mReadAloudController);
        when(mTab.getWebContents()).thenReturn(mWebContents);
        when(mTab.getProfile()).thenReturn(mProfile);
        when(mWebContents.getNavigationController()).thenReturn(mNavigationController);
        when(mNavigationController.getUseDesktopUserAgent()).thenReturn(false);
        when(mTabModelSelector.getCurrentModel()).thenReturn(mTabModel);
        when(mTabModelSelector.getModel(false)).thenReturn((mTabModel));
        when(mTabModel.isIncognito()).thenReturn(false);
        when(mTabModelSelector.getTabModelFilterProvider()).thenReturn(mTabModelFilterProvider);
        when(mTabModelFilterProvider.getCurrentTabModelFilter()).thenReturn(mTabModelFilter);
        when(mTabModelFilter.getTabModel()).thenReturn(mTabModel);
        when(mTabModel.getProfile()).thenReturn(mProfile);
        jniMocker.mock(ManagedBrowserUtilsJni.TEST_HOOKS, mManagedBrowserUtilsJniMock);
        ProfileManager.setLastUsedProfileForTesting(mProfile);
        jniMocker.mock(WebsitePreferenceBridgeJni.TEST_HOOKS, mWebsitePreferenceBridgeJniMock);
        OfflinePageUtils.setInstanceForTesting(mOfflinePageUtils);
        when(mIdentityService.getSigninManager(any(Profile.class))).thenReturn(mSigninManager);
        when(mSigninManager.getIdentityManager()).thenReturn(mIdentityManager);
        IdentityServicesProvider.setInstanceForTests(mIdentityService);
        PageZoomCoordinator.setShouldShowMenuItemForTesting(false);
        FeedFeatures.setFakePrefsForTest(mPrefService);
        jniMocker.mock(AppBannerManagerJni.TEST_HOOKS, mAppBannerManagerJniMock);
        Mockito.when(mAppBannerManagerJniMock.getInstallableWebAppManifestId(any()))
                .thenReturn(null);
        jniMocker.mock(WebFeedBridgeJni.TEST_HOOKS, mWebFeedBridgeJniMock);
        when(mWebFeedBridgeJniMock.isWebFeedEnabled()).thenReturn(true);

        Context context =
                new ContextThemeWrapper(
                        ContextUtils.getApplicationContext(), R.style.Theme_BrowserUI_DayNight);
        PowerBookmarkUtils.setPriceTrackingEligibleForTesting(false);
        PowerBookmarkUtils.setPowerBookmarkMetaForTesting(PowerBookmarkMeta.newBuilder().build());
        mTabbedAppMenuPropertiesDelegate =
                Mockito.spy(
                        new TabbedAppMenuPropertiesDelegate(
                                context,
                                mActivityTabProvider,
                                mMultiWindowModeStateDispatcher,
                                mTabModelSelector,
                                mToolbarManager,
                                mDecorView,
                                mAppMenuDelegate,
                                mLayoutStateProviderSupplier,
                                mBookmarkModelSupplier,
                                mFeedLauncher,
                                mDialogManager,
                                mSnackbarManager,
                                mIncognitoReauthControllerSupplier,
                                mReadAloudControllerSupplier));
        ChromeSharedPreferences.getInstance()
                .removeKeysWithPrefix(ChromePreferenceKeys.MULTI_INSTANCE_URL);
        ChromeSharedPreferences.getInstance()
                .removeKeysWithPrefix(ChromePreferenceKeys.MULTI_INSTANCE_TAB_COUNT);

        ShoppingServiceFactory.setShoppingServiceForTesting(mShoppingService);
    }

    @Test
    @Config(qualifiers = "sw320dp")
    public void testPageMenuItems_Phone_RegularPage_enterprise_user() {
        setUpMocksForPageMenu();

        Menu menu = createTestMenu();
        mTabbedAppMenuPropertiesDelegate.prepareMenu(menu, null);

        Integer[] expectedItems = {
            R.id.icon_row_menu_id,
            R.id.new_tab_menu_id,
            R.id.new_incognito_tab_menu_id,
            R.id.divider_line_id,
            R.id.open_history_menu_id,
            R.id.quick_delete_menu_id,
            R.id.quick_delete_divider_line_id,
            R.id.downloads_menu_id,
            R.id.all_bookmarks_menu_id,
            R.id.recent_tabs_menu_id,
            R.id.divider_line_id,
            R.id.translate_id,
            R.id.share_row_menu_id,
            R.id.find_in_page_id,
            R.id.universal_install,
            R.id.request_desktop_site_row_menu_id,
            R.id.auto_dark_web_contents_row_menu_id,
            R.id.divider_line_id,
            R.id.preferences_id,
            R.id.help_id,
            R.id.managed_by_divider_line_id,
            R.id.managed_by_menu_id
        };
        assertMenuItemsAreEqual(menu, expectedItems);
    }

    @Test
    public void testPageMenuItems_multiWindowMenu_featureEnabled() {
        setUpMocksForPageMenu();

        // Single window
        //
        // No API support (i.e. cannot enter multi-window through menu), do not show 'New Window'
        testWindowMenu(WIN_S, X____, X____, API_NO, X____, NEW_NO, X____);

        // No 'New Window' on phone.
        testWindowMenu(WIN_S, INST_S, PHONE, X____, MOVE_OTHER_NO, NEW_NO, MOVE_NO);

        // Show 'New Window' only on tablet and API is supported.
        testWindowMenu(WIN_S, INST_S, TABLET, API_YES, X____, NEW_YES, MOVE_NO);

        //
        // Multi-window
        //
        // Move to other window supported, show 'Move to other window'
        testWindowMenu(WIN_M, INST_M, X____, X____, MOVE_OTHER_YES, X____, MOVE_YES);

        // Move to other window not supported, hide 'Move to other window'
        testWindowMenu(WIN_M, INST_M, X____, X____, MOVE_OTHER_NO, X____, MOVE_NO);

        // Single instance -> Show 'New window'
        testWindowMenu(WIN_M, INST_S, X____, X____, X____, NEW_YES, MOVE_NO);

        assertTestedAllCombinations();
    }

    @Test
    public void testPageMenuItems_instanceSwitcher_newWindow() {
        setUpMocksForPageMenu();
        doReturn(true)
                .when(mTabbedAppMenuPropertiesDelegate)
                .instanceSwitcherWithMultiInstanceEnabled();

        createInstance(0, "https://url0");

        // On phone, we do not show 'New Window'.
        mIsTabletScreen = false;
        Menu menu = createMenuForMultiWindow();
        assertFalse(isMenuVisible(menu, R.id.new_window_menu_id));

        // Multi-window mode, with a single instance (no adjacent instance running) makes
        // the menu visible.
        doReturn(false).when(mMultiWindowModeStateDispatcher).isChromeRunningInAdjacentWindow();
        mIsMultiWindow = true;

        menu = createMenuForMultiWindow();
        assertTrue(isMenuVisible(menu, R.id.new_window_menu_id));

        // On tablet, we show 'New Window' by default.
        mIsTabletScreen = true;
        mIsMultiWindow = false;
        menu = createMenuForMultiWindow();
        assertTrue(isMenuVisible(menu, R.id.new_window_menu_id));

        for (int i = 0; i < MultiWindowUtils.getMaxInstances(); ++i) {
            createInstance(i, "https://url" + i);
        }

        Menu menu2 = createMenuForMultiWindow();
        assertFalse(isMenuVisible(menu2, R.id.new_window_menu_id));
    }

    @Test
    public void testPageMenuItems_instanceSwitcher_moveTabToOtherWindow() {
        setUpMocksForPageMenu();
        doReturn(true)
                .when(mTabbedAppMenuPropertiesDelegate)
                .instanceSwitcherWithMultiInstanceEnabled();
        mIsMoveToOtherWindowSupported = true;

        Menu menu = createMenuForMultiWindow();
        assertTrue(isMenuVisible(menu, R.id.move_to_other_window_menu_id));
    }

    @Test
    public void testPageMenuItems_instanceSwitcher_manageAllWindow() {
        setUpMocksForPageMenu();
        doReturn(true)
                .when(mTabbedAppMenuPropertiesDelegate)
                .instanceSwitcherWithMultiInstanceEnabled();

        createInstance(0, "https://url0");

        Menu menu = createMenuForMultiWindow();
        assertFalse(isMenuVisible(menu, R.id.manage_all_windows_menu_id));

        createInstance(1, "https://url1");

        Menu menu2 = createMenuForMultiWindow();
        assertTrue(isMenuVisible(menu2, R.id.manage_all_windows_menu_id));
    }

    @Test
    public void testPageMenuItems_universalInstall() {
        setUpMocksForPageMenu();
        Menu menu = createMenuForMultiWindow();
        assertTrue(isMenuVisible(menu, R.id.universal_install));
        assertFalse(isMenuVisible(menu, R.id.open_webapk_id));
    }

    @Test
    public void getFooterResourceId_incognito_doesNotReturnWebFeedMenuItem() {
        setUpMocksForWebFeedFooter();
        when(mTab.isIncognito()).thenReturn(true);

        assertNotEquals(
                "Footer Resource ID should not be web_feed_main_menu_item.",
                R.layout.web_feed_main_menu_item,
                mTabbedAppMenuPropertiesDelegate.getFooterResourceId());
    }

    @Test
    public void getFooterResourceId_offlinePage_doesNotReturnWebFeedMenuItem() {
        setUpMocksForWebFeedFooter();
        when(mOfflinePageUtils.isOfflinePage(mTab)).thenReturn(true);

        assertNotEquals(
                "Footer Resource ID should not be web_feed_main_menu_item.",
                R.layout.web_feed_main_menu_item,
                mTabbedAppMenuPropertiesDelegate.getFooterResourceId());
    }

    @Test
    public void getFooterResourceId_nonHttpUrl_doesNotReturnWebFeedMenuItem() {
        setUpMocksForWebFeedFooter();
        when(mTab.getOriginalUrl()).thenReturn(JUnitTestGURLs.NTP_URL);

        assertNotEquals(
                "Footer Resource ID should not be web_feed_main_menu_item.",
                R.layout.web_feed_main_menu_item,
                mTabbedAppMenuPropertiesDelegate.getFooterResourceId());
    }

    @Test
    public void getFooterResourceId_signedOutUser_doesNotReturnWebFeedMenuItem() {
        setUpMocksForWebFeedFooter();
        when(mIdentityManager.hasPrimaryAccount(anyInt())).thenReturn(false);

        assertNotEquals(
                "Footer Resource ID should not be web_feed_main_menu_item.",
                R.layout.web_feed_main_menu_item,
                mTabbedAppMenuPropertiesDelegate.getFooterResourceId());
    }

    @Test
    public void getFooterResourceId_httpsUrl_returnsWebFeedMenuItem() {
        setUpMocksForWebFeedFooter();

        assertEquals(
                "Footer Resource ID should be web_feed_main_menu_item.",
                R.layout.web_feed_main_menu_item,
                mTabbedAppMenuPropertiesDelegate.getFooterResourceId());
    }

    @Test
    public void getFooterResourceId_dseOff_doesNotReturnWebFeedMenuItem() {
        setUpMocksForWebFeedFooter();
        when(mIdentityManager.hasPrimaryAccount(anyInt())).thenReturn(true);
        when(mPrefService.getBoolean(Pref.ENABLE_SNIPPETS_BY_DSE)).thenReturn(false);

        assertNotEquals(
                "Footer Resource ID should not be web_feed_main_menu_item.",
                R.layout.web_feed_main_menu_item,
                mTabbedAppMenuPropertiesDelegate.getFooterResourceId());
    }

    @Test
    public void getFooterResourceId_dseOn_returnsWebFeedMenuItem() {
        setUpMocksForWebFeedFooter();
        when(mIdentityManager.hasPrimaryAccount(anyInt())).thenReturn(true);

        assertEquals(
                "Footer Resource ID should be web_feed_main_menu_item.",
                R.layout.web_feed_main_menu_item,
                mTabbedAppMenuPropertiesDelegate.getFooterResourceId());
    }

    @Test
    public void getFooterResourceId_signedOutUser_dseOn_doesNotReturnWebFeedMenuItem() {
        setUpMocksForWebFeedFooter();
        when(mIdentityManager.hasPrimaryAccount(anyInt())).thenReturn(false);

        assertNotEquals(
                "Footer Resource ID should not be web_feed_main_menu_item.",
                R.layout.web_feed_main_menu_item,
                mTabbedAppMenuPropertiesDelegate.getFooterResourceId());
    }

    private void setUpMocksForWebFeedFooter() {
        when(mActivityTabProvider.get()).thenReturn(mTab);
        when(mTab.isIncognito()).thenReturn(false);
        when(mTab.getOriginalUrl()).thenReturn(JUnitTestGURLs.EXAMPLE_URL);
        when(mOfflinePageUtils.isOfflinePage(mTab)).thenReturn(false);
        when(mIdentityManager.hasPrimaryAccount(anyInt())).thenReturn(true);
        when(mPrefService.getBoolean(Pref.ENABLE_SNIPPETS_BY_DSE)).thenReturn(true);
    }

    private void setUpMocksForPageMenu() {
        when(mActivityTabProvider.get()).thenReturn(mTab);
        when(mLayoutStateProvider.isLayoutVisible(LayoutType.TAB_SWITCHER)).thenReturn(false);
        doReturn(false)
                .when(mTabbedAppMenuPropertiesDelegate)
                .shouldCheckBookmarkStar(any(Tab.class));
        doReturn(false)
                .when(mTabbedAppMenuPropertiesDelegate)
                .shouldEnableDownloadPage(any(Tab.class));
        doReturn(false)
                .when(mTabbedAppMenuPropertiesDelegate)
                .shouldShowReaderModePrefs(any(Tab.class));
        doReturn(true).when(mTabbedAppMenuPropertiesDelegate).isTabletSizeScreen();
        doReturn(true).when(mTabbedAppMenuPropertiesDelegate).isAutoDarkWebContentsEnabled();

        setUpIncognitoMocks();
        when(mTab.getUrl()).thenReturn(JUnitTestGURLs.SEARCH_URL);
        when(mTab.isNativePage()).thenReturn(false);
        doReturn(false)
                .when(mTabbedAppMenuPropertiesDelegate)
                .shouldShowPaintPreview(anyBoolean(), any(Tab.class), anyBoolean());
        doReturn(true)
                .when(mTabbedAppMenuPropertiesDelegate)
                .shouldShowTranslateMenuItem(any(Tab.class));
        when(mManagedBrowserUtilsJniMock.isBrowserManaged(any())).thenReturn(true);
    }

    private void setUpIncognitoMocks() {
        doReturn(true).when(mTabbedAppMenuPropertiesDelegate).isIncognitoEnabled();
        doReturn(false).when(mIncognitoReauthControllerMock).isIncognitoReauthPending();
    }

    private Menu createTestMenu() {
        // mMultiWindowModeStateDispatcher.isOpenInOtherWindowSupported() is determined by
        // isInMultiWindowMode() and isInMultiDisplayMode(). Set that condition here.
        boolean openInOtherWindow =
                mMultiWindowModeStateDispatcher.isInMultiWindowMode()
                        || mMultiWindowModeStateDispatcher.isInMultiDisplayMode();
        doReturn(openInOtherWindow)
                .when(mMultiWindowModeStateDispatcher)
                .isOpenInOtherWindowSupported();

        PopupMenu tempMenu = new PopupMenu(ContextUtils.getApplicationContext(), mDecorView);
        tempMenu.inflate(R.menu.main_menu);
        return tempMenu.getMenu();
    }

    private Menu createMenuForMultiWindow() {
        doReturn(mIsMultiWindow).when(mMultiWindowModeStateDispatcher).isInMultiWindowMode();
        doReturn(mIsMultiWindowApiSupported)
                .when(mMultiWindowModeStateDispatcher)
                .canEnterMultiWindowMode();
        doReturn(mIsMultiInstance).when(mMultiWindowModeStateDispatcher).isMultiInstanceRunning();
        doReturn(mIsTabletScreen).when(mTabbedAppMenuPropertiesDelegate).isTabletSizeScreen();
        doReturn(MultiWindowUtils.getInstanceCount())
                .when(mMultiWindowModeStateDispatcher)
                .getInstanceCount();
        doReturn(mIsMoveToOtherWindowSupported)
                .when(mMultiWindowModeStateDispatcher)
                .isMoveToOtherWindowSupported(mTabModelSelector);
        Menu menu = createTestMenu();
        mTabbedAppMenuPropertiesDelegate.prepareMenu(menu, null);
        return menu;
    }

    private void testWindowMenu(
            Boolean multiWindow,
            Boolean multiInstance,
            Boolean tablet,
            Boolean apiSupported,
            Boolean moveToOtherWindowSupported,
            Boolean showNewWindow,
            Boolean showMoveWindow) {
        for (int i = 0; i < (1 << 5); ++i) {
            boolean bitMultiWindow = (i & 1) == 1;
            boolean bitMultiInstance = ((i >> 1) & 1) == 1;
            boolean bitTabletScreen = ((i >> 2) & 1) == 1;
            boolean bitApiSupported = ((i >> 3) & 1) == 1;
            boolean bitMoveToOtherWindowSupported = ((i >> 4) & 1) == 1;

            if ((multiWindow == null || bitMultiWindow == multiWindow)
                    && (multiInstance == null || bitMultiInstance == multiInstance)
                    && (tablet == null || bitTabletScreen == tablet)
                    && (apiSupported == null || bitApiSupported == apiSupported)
                    && (moveToOtherWindowSupported == null
                            || bitMoveToOtherWindowSupported == moveToOtherWindowSupported)) {
                mIsMultiWindow = bitMultiWindow;
                mIsMultiInstance = bitMultiInstance;
                mIsTabletScreen = bitTabletScreen;
                mIsMultiWindowApiSupported = bitApiSupported;
                mIsMoveToOtherWindowSupported = bitMoveToOtherWindowSupported;

                // Ignore invalid combination.
                if ((!bitMultiWindow && bitMultiInstance)
                        || (!bitMultiInstance && bitMoveToOtherWindowSupported)) continue;

                mFlagCombinations[i] = true;
                Menu menu = createMenuForMultiWindow();
                if (showNewWindow != null) {
                    if (showNewWindow) {
                        assertTrue(getFlags(), isMenuVisible(menu, R.id.new_window_menu_id));
                    } else {
                        assertFalse(getFlags(), isMenuVisible(menu, R.id.new_window_menu_id));
                    }
                }
                if (showMoveWindow != null) {
                    if (showMoveWindow) {
                        assertTrue(
                                getFlags(), isMenuVisible(menu, R.id.move_to_other_window_menu_id));
                    } else {
                        assertFalse(
                                getFlags(), isMenuVisible(menu, R.id.move_to_other_window_menu_id));
                    }
                }
            }
        }
    }

    private void assertTestedAllCombinations() {
        for (int i = 0; i < (1 << 5); ++i) {
            boolean bitMultiWindow = (i & 1) == 1;
            boolean bitMultiInstance = ((i >> 1) & 1) == 1;
            boolean bitTabletScreen = ((i >> 2) & 1) == 1;
            boolean bitApiSupported = ((i >> 3) & 1) == 1;
            boolean bitMoveToOtherWindowSupported = ((i >> 4) & 1) == 1;

            // Ignore invalid combination.
            if ((!bitMultiWindow && bitMultiInstance)
                    || (!bitMultiInstance && bitMoveToOtherWindowSupported)) continue;

            assertTrue(
                    "Not tested: "
                            + getFlags(
                                    bitMultiWindow,
                                    bitMultiInstance,
                                    bitTabletScreen,
                                    bitApiSupported,
                                    bitMoveToOtherWindowSupported),
                    mFlagCombinations[i]);
        }
    }

    private String getFlags(
            boolean multiWindow,
            boolean multiInstance,
            boolean tablet,
            boolean apiSupported,
            boolean moveToOtherWindowSupported) {
        return "("
                + (multiWindow ? "WIN_M" : "WIN_S")
                + ", "
                + (multiInstance ? "INST_M" : "INST_S")
                + ", "
                + (tablet ? "TABLET" : "PHONE")
                + ", "
                + (apiSupported ? "API_YES" : "API_NO")
                + ", "
                + (moveToOtherWindowSupported ? "MOVE_OTHER_YES" : "MOVE_OTHER_NO")
                + ")";
    }

    private String getFlags() {
        return getFlags(
                mIsMultiWindow,
                mIsMultiInstance,
                mIsTabletScreen,
                mIsMultiWindowApiSupported,
                mIsMoveToOtherWindowSupported);
    }

    private void assertMenuItemsAreEqual(Menu menu, Integer... expectedItems) {
        List<Integer> actualItems = new ArrayList<>();
        for (int i = 0; i < menu.size(); i++) {
            if (menu.getItem(i).isVisible()) {
                actualItems.add(menu.getItem(i).getItemId());
            }
        }

        assertThat(
                "Populated menu items were:" + getMenuTitles(menu),
                actualItems,
                Matchers.containsInAnyOrder(expectedItems));
    }

    private boolean isMenuVisible(Menu menu, int itemId) {
        boolean found = false;
        for (int i = 0; i < menu.size(); i++) {
            if (menu.getItem(i).isVisible() && menu.getItem(i).getItemId() == itemId) {
                found = true;
                break;
            }
        }
        return found;
    }

    private void assertMenuContains(Menu menu, int itemId) {
        Assert.assertTrue(
                "Item should must be contained in the menu: " + itemId,
                isMenuVisible(menu, itemId));
    }

    private void assertMenuDoesNotContain(Menu menu, int itemId) {
        Assert.assertFalse(
                "Item should must not be contained in the menu: " + itemId,
                isMenuVisible(menu, itemId));
    }

    private String getMenuTitles(Menu menu) {
        StringBuilder items = new StringBuilder();
        for (int i = 0; i < menu.size(); i++) {
            if (menu.getItem(i).isVisible()) {
                items.append("\n").append(menu.getItem(i).getTitle());
            }
        }
        return items.toString();
    }

    private static void createInstance(int index, String url) {
        String urlKey = ChromePreferenceKeys.MULTI_INSTANCE_URL.createKey(String.valueOf(index));
        ChromeSharedPreferences.getInstance().writeString(urlKey, url);
        String tabCountKey =
                ChromePreferenceKeys.MULTI_INSTANCE_TAB_COUNT.createKey(String.valueOf(index));
        ChromeSharedPreferences.getInstance().writeInt(tabCountKey, 1);
        String accessTimeKey =
                ChromePreferenceKeys.MULTI_INSTANCE_LAST_ACCESSED_TIME.createKey(
                        String.valueOf(index));
        ChromeSharedPreferences.getInstance().writeLong(accessTimeKey, System.currentTimeMillis());
    }
}