chromium/chrome/browser/single_tab/android/java/src/org/chromium/chrome/browser/single_tab/SingleTabSwitcherCoordinator.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.single_tab;

import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

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

import org.chromium.base.Callback;
import org.chromium.chrome.browser.magic_stack.ModuleDelegate;
import org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType;
import org.chromium.chrome.browser.magic_stack.ModuleProvider;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.chrome.browser.tab_ui.TabContentManager;
import org.chromium.chrome.browser.tab_ui.TabListFaviconProvider;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.components.browser_ui.widget.displaystyle.UiConfig;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;

/** Coordinator of the single tab tab switcher. */
public class SingleTabSwitcherCoordinator implements ModuleProvider {

    private final SingleTabSwitcherOnNtpMediator mMediatorOnNtp;
    private final TabListFaviconProvider mTabListFaviconProvider;
    private TabObserver mLastActiveTabObserver;
    private Tab mLastActiveTab;

    /** Null if created by {@link org.chromium.chrome.browser.magic_stack.ModuleProviderBuilder} */
    @Nullable private final ViewGroup mContainer;

    @Nullable private final Runnable mSnapshotParentViewRunnable;
    @Nullable private final ModuleDelegate mModuleDelegate;

    public SingleTabSwitcherCoordinator(
            @NonNull Activity activity,
            @NonNull ViewGroup container,
            @NonNull TabModelSelector tabModelSelector,
            boolean isTablet,
            Tab mostRecentTab,
            @Nullable Callback<Integer> singleTabCardClickedCallback,
            @Nullable Runnable seeMoreLinkClickedCallback,
            @Nullable Runnable snapshotParentViewRunnable,
            @Nullable TabContentManager tabContentManager,
            @Nullable UiConfig uiConfig,
            @Nullable ModuleDelegate moduleDelegate) {
        mLastActiveTab = mostRecentTab;
        mSnapshotParentViewRunnable = snapshotParentViewRunnable;
        PropertyModel propertyModel = new PropertyModel(SingleTabViewProperties.ALL_KEYS);
        mContainer = container;
        mModuleDelegate = moduleDelegate;

        if (moduleDelegate == null) {
            SingleTabView singleTabView =
                    (SingleTabView)
                            LayoutInflater.from(activity)
                                    .inflate(getModuleLayoutId(), container, false);
            mContainer.addView(singleTabView);
            PropertyModelChangeProcessor.create(
                    propertyModel, singleTabView, SingleTabViewBinder::bind);
        }
        mTabListFaviconProvider =
                new TabListFaviconProvider(
                        activity,
                        false,
                        org.chromium.chrome.browser.tab_ui.R.dimen
                                .favicon_corner_radius_for_single_tab_switcher);
        mMediatorOnNtp =
                new SingleTabSwitcherOnNtpMediator(
                        activity,
                        propertyModel,
                        tabModelSelector,
                        mTabListFaviconProvider,
                        mostRecentTab,
                        singleTabCardClickedCallback,
                        seeMoreLinkClickedCallback,
                        tabContentManager,
                        isTablet ? uiConfig : null,
                        isTablet,
                        moduleDelegate);

        if (mLastActiveTab != null) {
            beginObserving();
        }
    }

    private void beginObserving() {
        if (mLastActiveTab == null) return;

        mLastActiveTabObserver =
                new EmptyTabObserver() {
                    @Override
                    public void onClosingStateChanged(Tab tab, boolean closing) {
                        if (closing) {
                            updateTrackingTab(null);
                            setVisibility(false);
                            mLastActiveTab.removeObserver(mLastActiveTabObserver);
                            mLastActiveTab = null;
                            mLastActiveTabObserver = null;
                            if (mSnapshotParentViewRunnable != null) {
                                mSnapshotParentViewRunnable.run();
                            }
                            if (mModuleDelegate != null) {
                                mModuleDelegate.removeModule(ModuleType.SINGLE_TAB);
                            }
                        }
                    }
                };
        mLastActiveTab.addObserver(mLastActiveTabObserver);
    }

    /**
     * @see SingleTabSwitcherOnNtpMediator#setVisibility.
     */
    void setVisibility(boolean isVisible) {
        mMediatorOnNtp.setVisibility(isVisible);
        if (mContainer != null) {
            mContainer.setVisibility(isVisible ? View.VISIBLE : View.GONE);
        }
    }

    /**
     * Shows the single tab module and updates the Tab to track. It is possible to hide the single
     * Tab module if the new tracking Tab is invalid: e.g., a NTP.
     *
     * @param mostRecentTab The most recent Tab to track.
     */
    public void show(Tab mostRecentTab) {
        show(true, mostRecentTab);
    }

    /** Hides the single tab module. */
    public void hide() {
        setVisibility(false);
    }

    /**
     * Shows the single tab module.
     *
     * @param shouldUpdateTab Whether to update the tracking Tab of the single Tab module.
     * @param mostRecentTab The most recent Tab to track.
     */
    private void show(boolean shouldUpdateTab, Tab mostRecentTab) {
        boolean hasTabToTrack = true;
        if (shouldUpdateTab) {
            hasTabToTrack = updateTrackingTab(mostRecentTab);
        }
        setVisibility(hasTabToTrack);
    }

    /**
     * Update the most recent tab to track in the single tab card.
     *
     * @param tabToTrack The tab to track as the most recent tab.
     * @return Whether has a Tab to track. Returns false if the Tab to track is set as null.
     */
    public boolean updateTrackingTab(Tab tabToTrack) {
        boolean hasTabToTrack = mMediatorOnNtp.setTab(tabToTrack);
        if (hasTabToTrack && mLastActiveTab == null) {
            mLastActiveTab = tabToTrack;
            beginObserving();
        }
        return hasTabToTrack;
    }

    /** Returns the layout resource id for the single tab card. */
    public static int getModuleLayoutId() {
        return R.layout.single_tab_module_layout;
    }

    public void destroy() {
        if (mLastActiveTab != null) {
            mLastActiveTab.removeObserver(mLastActiveTabObserver);
            mLastActiveTab = null;
            mLastActiveTabObserver = null;
        }
        if (mMediatorOnNtp != null) {
            mMediatorOnNtp.destroy();
        }
    }

    // ModuleProvider implementation.

    @Override
    public void showModule() {
        show(false, null);
    }

    @Override
    public void hideModule() {
        mMediatorOnNtp.destroy();
    }

    @Override
    public int getModuleType() {
        return ModuleType.SINGLE_TAB;
    }

    @Override
    public void onContextMenuCreated() {}

    @Override
    public String getModuleContextMenuHideText(Context context) {
        return context.getResources()
                .getQuantityString(R.plurals.home_modules_context_menu_hide_tab, 1);
    }

    @VisibleForTesting
    public boolean isVisible() {
        if (mMediatorOnNtp == null) return false;

        return mMediatorOnNtp.isVisible();
    }
}