chromium/chrome/browser/tab_group/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroup.java

// Copyright 2024 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.tasks.tab_groups;

import androidx.annotation.NonNull;

import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabList;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;

/**
 * This class is a representation of a group of tabs. It knows the last selected tab within the
 * group.
 *
 * <p>Note that this class is scoped to the "tab_groups" package. It is used internally by {@link
 * TabGroupModelFilter} and any lookups for tab groups should go through that filter.
 */
class TabGroup {
    static final int INVALID_ROOT_ID = -1;
    static final int INVALID_POSITION_IN_GROUP = -1;

    private final LinkedHashSet<Integer> mTabIds = new LinkedHashSet<>();
    private final int mRootId;

    private int mLastShownTabId = Tab.INVALID_TAB_ID;

    /**
     * @param rootId The root identifier of all tabs in the tab group.
     */
    TabGroup(int rootId) {
        mRootId = rootId;
    }

    /**
     * Adds a tab to the tab group.
     *
     * @param tabId The ID of the tab to add to the group.
     * @param tabList The list of all tabs in the model containing the tab with tabId. This is used
     *     to sort the tab group to match the order in the tab list.
     */
    void addTab(int tabId, @NonNull TabList tabList) {
        assert tabId != Tab.INVALID_TAB_ID;

        mTabIds.add(tabId);
        if (mLastShownTabId == Tab.INVALID_TAB_ID) setLastShownTabId(tabId);
        if (size() > 1) sortByTabListOrder(tabList);
    }

    /**
     * Removes a tab from the tab group.
     *
     * @param tabId The ID of the tab to remove from the group.
     */
    void removeTab(int tabId) {
        assert mTabIds.contains(tabId);
        if (mLastShownTabId == tabId) {
            int nextIdToShow = nextTabIdToShow(tabId);
            if (nextIdToShow != Tab.INVALID_TAB_ID) setLastShownTabId(nextIdToShow);
        }
        mTabIds.remove(tabId);
    }

    /**
     * Reorders the tab to be at the end of the tab group if this group contains the tab.
     *
     * @param tabId The tab ID to move to the end of the group.
     */
    void moveToEndInGroup(int tabId) {
        if (!mTabIds.contains(tabId)) return;
        mTabIds.remove(tabId);
        mTabIds.add(tabId);
    }

    /** Returns whether the tab group contains a tab with the specified tab ID. */
    boolean contains(int tabId) {
        return mTabIds.contains(tabId);
    }

    /** Returns the size of the tab group. */
    int size() {
        return mTabIds.size();
    }

    /**
     * Returns the list of tab IDs for the tab group in the order the tabs appear in the {@link
     * TabList} they come from.
     */
    List<Integer> getTabIdList() {
        return Collections.unmodifiableList(new ArrayList<>(mTabIds));
    }

    /** Returns the tab ID of the tab within the group that was last selected. */
    int getLastShownTabId() {
        return mLastShownTabId;
    }

    /** Sets the tab ID that was last selected from the group. */
    void setLastShownTabId(int tabId) {
        assert mTabIds.contains(tabId);
        mLastShownTabId = tabId;
    }

    /** Returns the ID of the first tab in the group. */
    int getTabIdOfFirstTab() {
        return mTabIds.stream().findFirst().get();
    }

    /** Returns the ID of the last tab in the group. */
    int getTabIdOfLastTab() {
        return mTabIds.stream().skip(mTabIds.size() - 1).findFirst().get();
    }

    /**
     * Returns the position of a tab in the group.
     *
     * @param tab The tab whose position is to be determined.
     */
    int getPositionOfTab(Tab tab) {
        int index = 0;
        for (int tabId : mTabIds) {
            if (tab.getId() == tabId) {
                return index;
            }
            index++;
        }
        return INVALID_POSITION_IN_GROUP;
    }

    private int nextTabIdToShow(int tabId) {
        if (mTabIds.size() == 1 || !mTabIds.contains(tabId)) return Tab.INVALID_TAB_ID;
        List<Integer> ids = getTabIdList();
        int position = ids.indexOf(tabId);
        if (position == 0) return ids.get(position + 1);
        return ids.get(position - 1);
    }

    private void sortByTabListOrder(@NonNull TabList tabList) {
        for (int i = 0; i < tabList.getCount(); i++) {
            moveToEndInGroup(tabList.getTabAt(i).getId());
        }
    }
}