chromium/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelJniBridge.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 androidx.annotation.VisibleForTesting;

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

import org.chromium.chrome.browser.flags.ActivityType;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabLaunchType;
import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.common.ResourceRequestBody;
import org.chromium.url.GURL;
import org.chromium.url.Origin;

/** Bridges between the C++ and Java {@link TabModel} interfaces. */
public abstract class TabModelJniBridge implements TabModel {
    private final Profile mProfile;

    /** The type of the Activity for which this tab model works. */
    private final @ActivityType int mActivityType;

    /** Whether the model is for archvied tabs. */
    private final boolean mIsArchivedTabModel;

    /** Native TabModelJniBridge pointer, which will be set by {@link #initializeNative()}. */
    private long mNativeTabModelJniBridge;

    /**
     * @param profile The profile this TabModel belongs to.
     * @param activityType The type of activity this TabModel was created in.
     * @param isArchivedTabModel Whether this tab model is for archived tabs. When true, excludes
     *     the model from broadcasting sync updates.
     */
    public TabModelJniBridge(
            @NonNull Profile profile, @ActivityType int activityType, boolean isArchivedTabModel) {
        mProfile = profile;
        mActivityType = activityType;
        mIsArchivedTabModel = isArchivedTabModel;
    }

    /** Initializes the native-side counterpart to this class. */
    protected void initializeNative(Profile profile) {
        assert mNativeTabModelJniBridge == 0;
        mNativeTabModelJniBridge =
                TabModelJniBridgeJni.get()
                        .init(TabModelJniBridge.this, profile, mActivityType, mIsArchivedTabModel);
    }

    /** Returns whether the native-side pointer has been initialized. */
    public boolean isNativeInitialized() {
        return mNativeTabModelJniBridge != 0;
    }

    @Override
    public void destroy() {
        if (isNativeInitialized()) {
            // This will invalidate all other native references to this object in child classes.
            TabModelJniBridgeJni.get().destroy(mNativeTabModelJniBridge, TabModelJniBridge.this);
            mNativeTabModelJniBridge = 0;
        }
    }

    @Override
    public boolean isIncognito() {
        return mProfile.isOffTheRecord();
    }

    @Override
    public boolean isOffTheRecord() {
        return mProfile.isOffTheRecord();
    }

    @Override
    public boolean isIncognitoBranded() {
        return mProfile.isIncognitoBranded();
    }

    @Override
    public Profile getProfile() {
        return mProfile;
    }

    /** Broadcast a native-side notification that all tabs are now loaded from storage. */
    public void broadcastSessionRestoreComplete() {
        assert isNativeInitialized();
        TabModelJniBridgeJni.get()
                .broadcastSessionRestoreComplete(mNativeTabModelJniBridge, TabModelJniBridge.this);
    }

    /**
     * Called by subclasses when a Tab is added to the TabModel.
     * @param tab Tab being added to the model.
     */
    protected void tabAddedToModel(Tab tab) {
        if (isNativeInitialized()) {
            TabModelJniBridgeJni.get()
                    .tabAddedToModel(mNativeTabModelJniBridge, TabModelJniBridge.this, tab);
        }
    }

    /**
     * Sets the TabModel's index.
     *
     * @param index Index of the Tab to select.
     */
    @CalledByNative
    private void setIndex(int index) {
        TabModelUtils.setIndex(this, index);
    }

    @Override
    @CalledByNative
    public abstract Tab getTabAt(int index);

    /**
     * Closes the Tab at a particular index.
     * @param index Index of the tab to close.
     * @return Whether the was successfully closed.
     */
    @CalledByNative
    protected abstract boolean closeTabAt(int index);

    /**
     * Returns a tab creator for this {@link TabModel}.
     *
     * Please note that, the {@link TabCreator} and {@TabModelImpl} are separate instances for
     * {@link ChromeTabbedActivity} and {@link CustomTabActivity} across both regular and Incognito
     * modes which allows us to pass the boolean directly.
     *
     * @param incognito A boolean to indicate whether to return IncognitoTabCreator or
     *         RegularTabCreator.
     */
    protected abstract TabCreator getTabCreator(boolean incognito);

    /**
     * Creates a Tab with the given WebContents.
     * @param parent      The parent tab that creates the new tab.
     * @param profile     The profile for which to create the new tab.
     * @param webContents A {@link WebContents} object.
     * @param select      Select the created tab.
     * @return Whether or not the Tab was successfully created.
     */
    @CalledByNative
    protected abstract boolean createTabWithWebContents(
            Tab parent, Profile profile, WebContents webContents, boolean select);

    @CalledByNative
    protected abstract void openNewTab(
            Tab parent,
            GURL url,
            @Nullable Origin initiatorOrigin,
            @JniType("std::string") String extraHeaders,
            ResourceRequestBody postData,
            int disposition,
            boolean persistParentage,
            boolean isRendererInitiated);

    /**
     * Creates a Tab with the given WebContents for DevTools.
     * @param url URL to show.
     */
    @CalledByNative
    protected Tab createNewTabForDevTools(GURL url) {
        return getTabCreator(/* incognito= */ false)
                .createNewTab(new LoadUrlParams(url), TabLaunchType.FROM_CHROME_UI, null);
    }

    /** Returns whether supplied Tab instance has been grouped together with other Tabs. */
    @CalledByNative
    @VisibleForTesting
    static boolean isTabInTabGroup(@NonNull Tab tab) {
        final TabModelFilter filter = TabModelUtils.getTabModelFilterByTab(tab);
        if (filter == null) return false;

        assert filter instanceof TabGroupModelFilter;
        final TabGroupModelFilter groupingFilter = (TabGroupModelFilter) filter;

        return groupingFilter.isTabInTabGroup(tab);
    }

    @Override
    @CalledByNative
    public abstract int getCount();

    @Override
    @CalledByNative
    public abstract int index();

    /** Returns whether or not a sync session is currently being restored. */
    @CalledByNative
    protected abstract boolean isSessionRestoreInProgress();

    @CalledByNative
    @Override
    public abstract boolean isActiveModel();

    @Override
    public abstract void setActive(boolean active);

    @Override
    @CalledByNative
    public abstract int getTabCountNavigatedInTimeWindow(long beginTimeMs, long endTimeMs);

    @Override
    @CalledByNative
    public abstract void closeTabsNavigatedInTimeWindow(long beginTimeMs, long endTimeMs);

    @NativeMethods
    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
    public interface Natives {
        long init(
                TabModelJniBridge caller,
                @JniType("Profile*") Profile profile,
                @ActivityType int activityType,
                boolean trackInNativeModelList);

        void broadcastSessionRestoreComplete(
                long nativeTabModelJniBridge, TabModelJniBridge caller);

        void destroy(long nativeTabModelJniBridge, TabModelJniBridge caller);

        void tabAddedToModel(long nativeTabModelJniBridge, TabModelJniBridge caller, Tab tab);
    }
}