chromium/chrome/android/java/src/org/chromium/chrome/browser/ntp/RecentlyClosedBridge.java

// Copyright 2013 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.ntp;

import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;
import org.jni_zero.JniType;
import org.jni_zero.NativeMethods;

import org.chromium.base.Token;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelFilter;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;

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

/** This class allows Java code to get and clear the list of recently closed entries. */
@JNINamespace("recent_tabs")
public class RecentlyClosedBridge implements RecentlyClosedTabManager {
    private long mNativeBridge;
    private final TabModelSelector mTabModelSelector;

    @Nullable private Runnable mEntriesUpdatedRunnable;

    @CalledByNative
    private static void addTabToEntries(List<RecentlyClosedEntry> entries, RecentlyClosedTab tab) {
        entries.add(tab);
    }

    @CalledByNative
    private static void addGroupToEntries(
            List<RecentlyClosedEntry> entries,
            int id,
            long groupTimestamp,
            @JniType("std::u16string") String groupTitle,
            @JniType("std::vector") List<RecentlyClosedTab> tabs) {
        RecentlyClosedGroup group = new RecentlyClosedGroup(id, groupTimestamp, groupTitle);
        group.getTabs().addAll(tabs);
        entries.add(group);
    }

    @CalledByNative
    private static void addBulkEventToEntries(
            List<RecentlyClosedEntry> entries,
            int id,
            long eventTimestamp,
            @JniType("std::vector<std::optional<base::Token>>") Token[] tabGroupIds,
            @JniType("std::vector<const std::u16string*>") String[] groupTitles,
            @JniType("std::vector") List<RecentlyClosedTab> tabs) {
        RecentlyClosedBulkEvent event = new RecentlyClosedBulkEvent(id, eventTimestamp);

        assert tabGroupIds.length == groupTitles.length;
        for (int i = 0; i < tabGroupIds.length; i++) {
            event.getTabGroupIdToTitleMap().put(tabGroupIds[i], groupTitles[i]);
        }

        event.getTabs().addAll(tabs);
        entries.add(event);
    }

    @CalledByNative
    private void restoreTabGroup(
            TabModel tabModel,
            @JniType("std::string") String savedTabGroupId,
            @JniType("std::u16string") String title,
            int color,
            @JniType("std::vector") int[] tabIds) {
        if (tabIds.length == 0) return;

        assert mTabModelSelector.getModel(tabModel.isIncognito()) == tabModel;
        TabModelFilter filter =
                mTabModelSelector
                        .getTabModelFilterProvider()
                        .getTabModelFilter(tabModel.isIncognito());
        assert filter instanceof TabGroupModelFilter;
        TabGroupModelFilter groupFilter = (TabGroupModelFilter) filter;

        int rootId = tabIds[0];

        if (ChromeFeatureList.sTabGroupParityAndroid.isEnabled()) {
            groupFilter.setTabGroupColor(rootId, color);
        }

        // TODO(b/336589861): Use savedTabGroupId to reassociate this tab group with a sync entity.

        if (tabIds.length == 1) {
            groupFilter.createSingleTabGroup(tabIds[0], false);
        } else {
            for (int id : tabIds) {
                if (id == rootId) continue;

                groupFilter.mergeTabsToGroup(id, rootId);
            }
        }

        if (title == null || title.isEmpty()) return;

        groupFilter.setTabGroupTitle(rootId, title);
    }

    /**
     * Initializes this class with the given profile.
     *
     * @param profile The {@link Profile} whose recently closed tabs will be queried.
     * @param tabModelSelector The {@link TabModelSelector} to use to get {@link TabModelFilter}s.
     */
    public RecentlyClosedBridge(Profile profile, TabModelSelector tabModelSelector) {
        mNativeBridge = RecentlyClosedBridgeJni.get().init(RecentlyClosedBridge.this, profile);
        mTabModelSelector = tabModelSelector;
    }

    @Override
    public void destroy() {
        assert mNativeBridge != 0;
        RecentlyClosedBridgeJni.get().destroy(mNativeBridge);
        mNativeBridge = 0;
        mEntriesUpdatedRunnable = null;
    }

    @Override
    public void setEntriesUpdatedRunnable(@Nullable Runnable runnable) {
        mEntriesUpdatedRunnable = runnable;
    }

    @Override
    public List<RecentlyClosedEntry> getRecentlyClosedEntries(int maxEntryCount) {
        List<RecentlyClosedEntry> entries = new ArrayList<RecentlyClosedEntry>();
        boolean received =
                RecentlyClosedBridgeJni.get()
                        .getRecentlyClosedEntries(mNativeBridge, entries, maxEntryCount);
        return received ? entries : null;
    }

    @Override
    public boolean openRecentlyClosedTab(
            TabModel tabModel, RecentlyClosedTab recentTab, int windowOpenDisposition) {
        assert mTabModelSelector.getModel(tabModel.isIncognito()) == tabModel;
        return RecentlyClosedBridgeJni.get()
                .openRecentlyClosedTab(
                        mNativeBridge, tabModel, recentTab.getSessionId(), windowOpenDisposition);
    }

    @Override
    public boolean openRecentlyClosedEntry(TabModel tabModel, RecentlyClosedEntry recentEntry) {
        assert mTabModelSelector.getModel(tabModel.isIncognito()) == tabModel;
        return RecentlyClosedBridgeJni.get()
                .openRecentlyClosedEntry(mNativeBridge, tabModel, recentEntry.getSessionId());
    }

    @Override
    public void openMostRecentlyClosedEntry(TabModel tabModel) {
        assert mTabModelSelector.getModel(tabModel.isIncognito()) == tabModel;
        RecentlyClosedBridgeJni.get().openMostRecentlyClosedEntry(mNativeBridge, tabModel);
    }

    @Override
    public void clearRecentlyClosedEntries() {
        RecentlyClosedBridgeJni.get().clearRecentlyClosedEntries(mNativeBridge);
    }

    /** This method will be called every time the list of recently closed tabs is updated. */
    @CalledByNative
    private void onUpdated() {
        if (mEntriesUpdatedRunnable != null) mEntriesUpdatedRunnable.run();
    }

    @NativeMethods
    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
    public interface Natives {
        long init(RecentlyClosedBridge caller, @JniType("Profile*") Profile profile);

        void destroy(long nativeRecentlyClosedTabsBridge);

        boolean getRecentlyClosedEntries(
                long nativeRecentlyClosedTabsBridge,
                List<RecentlyClosedEntry> entries,
                int maxEntryCount);

        boolean openRecentlyClosedTab(
                long nativeRecentlyClosedTabsBridge,
                TabModel tabModel,
                int tabSessionId,
                int windowOpenDisposition);

        boolean openRecentlyClosedEntry(
                long nativeRecentlyClosedTabsBridge, TabModel tabModel, int sessionId);

        boolean openMostRecentlyClosedEntry(long nativeRecentlyClosedTabsBridge, TabModel tabModel);

        void clearRecentlyClosedEntries(long nativeRecentlyClosedTabsBridge);
    }
}