chromium/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelUtils.java

// Copyright 2014 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.tabmodel;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.chromium.base.Callback;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabSelectionType;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.base.WindowAndroid;

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

/**
 * A set of convenience methods used for interacting with {@link TabList}s and {@link TabModel}s.
 */
public class TabModelUtils {
    private TabModelUtils() {}

    /**
     * @param model The {@link TabModel} to act on.
     * @param index The index of the {@link Tab} to close.
     * @return {@code true} if the {@link Tab} was found.
     */
    public static boolean closeTabByIndex(TabModel model, int index) {
        Tab tab = model.getTabAt(index);
        if (tab == null) return false;

        return model.closeTabs(TabClosureParams.closeTab(tab).allowUndo(false).build());
    }

    /**
     * @param model The {@link TabModel} to act on.
     * @param tabId The id of the {@link Tab} to close.
     * @param allowUndo Whether or not this closure is allowed to be undone.
     * @return {@code true} if the {@link Tab} was found.
     */
    public static boolean closeTabById(TabModel model, int tabId, boolean allowUndo) {
        Tab tab = model.getTabById(tabId);
        if (tab == null || tab.isClosing()) return false;

        return model.closeTabs(TabClosureParams.closeTab(tab).allowUndo(allowUndo).build());
    }

    /**
     * @param model The {@link TabModel} to act on.
     * @return {@code true} if the {@link Tab} was found.
     */
    public static boolean closeCurrentTab(TabModel model) {
        Tab tab = TabModelUtils.getCurrentTab(model);
        if (tab == null) return false;

        return model.closeTabs(TabClosureParams.closeTab(tab).allowUndo(false).build());
    }

    /**
     * Find the index of the {@link Tab} with the specified id.
     *
     * @param model The {@link TabModel} to act on.
     * @param tabId The id of the {@link Tab} to find.
     * @return Specified {@link Tab} index or {@link TabList#INVALID_TAB_INDEX} if the {@link Tab}
     *     is not found
     */
    public static int getTabIndexById(TabList model, int tabId) {
        int count = model.getCount();

        for (int i = 0; i < count; i++) {
            Tab tab = model.getTabAt(i);
            assert tab != null : "getTabAt() shouldn't return a null Tab from TabModel.";
            if (tab != null && tab.getId() == tabId) return i;
        }

        return TabModel.INVALID_TAB_INDEX;
    }

    /**
     * Find the {@link Tab} index whose URL matches the specified URL.
     *
     * @param model The {@link TabModel} to act on.
     * @param url The URL to search for.
     * @return Specified {@link Tab} or {@code null} if the {@link Tab} is not found
     */
    public static int getTabIndexByUrl(TabList model, String url) {
        int count = model.getCount();

        for (int i = 0; i < count; i++) {
            if (model.getTabAt(i).getUrl().getSpec().contentEquals(url)) return i;
        }

        return TabModel.INVALID_TAB_INDEX;
    }

    /**
     * Get the currently selected {@link Tab} id.
     * @param model The {@link TabModel} to act on.
     * @return      The id of the currently selected {@link Tab}.
     */
    public static int getCurrentTabId(TabList model) {
        Tab tab = getCurrentTab(model);
        if (tab == null) return Tab.INVALID_TAB_ID;

        return tab.getId();
    }

    /**
     * Get the currently selected {@link Tab}.
     * @param model The {@link TabModel} to act on.
     * @returns     The current {@link Tab} or {@code null} if no {@link Tab} is selected
     */
    public static Tab getCurrentTab(TabList model) {
        int index = model.index();
        if (index == TabModel.INVALID_TAB_INDEX) return null;

        return model.getTabAt(index);
    }

    /**
     * @param model The {@link TabModel} to act on.
     * @return      The currently active {@link WebContents}, or {@code null} if no {@link Tab}
     *              is selected or the selected {@link Tab} has no current {@link WebContents}.
     */
    public static WebContents getCurrentWebContents(TabList model) {
        Tab tab = getCurrentTab(model);
        if (tab == null) return null;

        return tab.getWebContents();
    }

    /**
     * Selects a tab by its ID in the tab model selector.
     *
     * @param selector The {@link TabModelSelector} to act on.
     * @param tabId The tab ID to select.
     * @param type {@link TabSelectionType} how the tab selection was initiated.
     */
    public static void selectTabById(
            @NonNull TabModelSelector selector, int tabId, @TabSelectionType int tabSelectionType) {
        if (tabId == Tab.INVALID_TAB_ID) return;

        TabModel model = selector.getModelForTabId(tabId);
        if (model == null) return;

        model.setIndex(getTabIndexById(model, tabId), tabSelectionType);
    }

    /**
     * A helper method that automatically passes {@link TabSelectionType#FROM_USER} as the selection
     * type to {@link TabModel#setIndex(int, TabSelectionType)}.
     *
     * @param model The {@link TabModel} to act on.
     * @param index The index of the {@link Tab} to select.
     */
    public static void setIndex(TabModel model, int index) {
        setIndex(model, index, TabSelectionType.FROM_USER);
    }

    /**
     * A helper method that allows specifying a {@link TabSelectionType} type to {@link
     * TabModel#setIndex(int, TabSelectionType)}.
     *
     * @param model The {@link TabModel} to act on.
     * @param index The index of the {@link Tab} to select.
     * @param type {@link TabSelectionType} how the tab selection was initiated.
     */
    public static void setIndex(TabModel model, int index, @TabSelectionType int type) {
        model.setIndex(index, type);
    }

    /**
     * Returns the most recently visited Tab in the specified TabList that is not {@code tabId}.
     *
     * @param model The {@link TabModel} to act on.
     * @param tabId The ID of the {@link Tab} to skip or {@link Tab.INVALID_TAB_ID}.
     * @return the most recently visited Tab or null if none can be found.
     */
    public static Tab getMostRecentTab(TabList model, int tabId) {
        Tab mostRecentTab = null;
        long mostRecentTabTime = 0;
        for (int i = 0; i < model.getCount(); i++) {
            final Tab currentTab = model.getTabAt(i);
            if (currentTab.getId() == tabId || currentTab.isClosing()) continue;

            final long currentTime = currentTab.getTimestampMillis();
            // TODO(b/301642179) Consider using Optional on Tab interface for getTimestampMillis()
            // to signal that the timestamp is unknown.
            if (currentTime != Tab.INVALID_TIMESTAMP && mostRecentTabTime < currentTime) {
                mostRecentTabTime = currentTime;
                mostRecentTab = currentTab;
            }
        }
        return mostRecentTab;
    }

    /**
     * Executes an {@link Callback} when {@link TabModelSelector#isTabStateInitialized()} becomes
     * true. This will happen immediately and synchronously if the tab state is already initialized.
     *
     * @param tabModelSelector The {@link TabModelSelector} to act on.
     * @param callback The callback to be run once tab state is initialized, receiving a
     *     tabModelSelector.
     */
    public static void runOnTabStateInitialized(
            @NonNull TabModelSelector tabModelSelector,
            @NonNull Callback<TabModelSelector> callback) {
        if (tabModelSelector.isTabStateInitialized()) {
            callback.onResult(tabModelSelector);
        } else {
            TabModelSelectorObserver observer =
                    new TabModelSelectorObserver() {
                        @Override
                        public void onTabStateInitialized() {
                            tabModelSelector.removeObserver(this);
                            callback.onResult(tabModelSelector);
                        }

                        @Override
                        public void onDestroyed() {
                            tabModelSelector.removeObserver(this);
                        }
                    };

            tabModelSelector.addObserver(observer);
        }
    }

    /**
     * @param tab The {@link Tab} to find the {@link TabModelFilter} for.
     * @return the associated {@link TabModelFilter} if found.
     */
    public static TabModelFilter getTabModelFilterByTab(@NonNull Tab tab) {
        final WindowAndroid windowAndroid = tab.getWindowAndroid();
        if (windowAndroid == null) return null;

        final ObservableSupplier<TabModelSelector> supplier =
                TabModelSelectorSupplier.from(windowAndroid);
        if (supplier == null) return null;

        final TabModelSelector selector = supplier.get();
        if (selector == null) return null;

        return selector.getTabModelFilterProvider().getTabModelFilter(tab.isIncognito());
    }

    /** Converts a {@link TabList} to a {@link List<Tab>}. A null input returns an empty list. */
    public static @Nullable List<Tab> convertTabListToListOfTabs(@Nullable TabList tabList) {
        ArrayList<Tab> list = new ArrayList<>();
        if (tabList == null) return list;

        for (int i = 0; i < tabList.getCount(); i++) {
            list.add(tabList.getTabAt(i));
        }
        return list;
    }

    /**
     * Converts a {@link TabList} to a {@link List<Integer>} tab ids. A null input returns an empty
     * list.
     */
    public static @Nullable List<Integer> convertTabListToListOfTabIds(@Nullable TabList tabList) {
        ArrayList<Integer> list = new ArrayList<>();
        if (tabList == null) return list;

        for (int i = 0; i < tabList.getCount(); i++) {
            list.add(tabList.getTabAt(i).getId());
        }
        return list;
    }
}