chromium/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/BaseButtonDataProvider.java

// Copyright 2022 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.toolbar;

import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.View.OnClickListener;

import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;

import org.chromium.base.FeatureList;
import org.chromium.base.ObserverList;
import org.chromium.base.supplier.Supplier;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarButtonVariant;
import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarFeatures;
import org.chromium.chrome.browser.user_education.IPHCommandBuilder;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogManagerObserver;
import org.chromium.ui.modelutil.PropertyModel;

/** Base class for button data providers used on the adaptive toolbar. */
public abstract class BaseButtonDataProvider implements ButtonDataProvider, OnClickListener {
    protected final ButtonDataImpl mButtonData;
    protected final Supplier<Tab> mActiveTabSupplier;

    private final ObserverList<ButtonDataObserver> mObservers = new ObserverList<>();
    private final ModalDialogManager mModalDialogManager;
    private ModalDialogManagerObserver mModalDialogObserver;

    private boolean mShouldShowOnIncognitoTabs;

    /**
     * Creates a new instance of {@code BaseButtonDataProvider}.
     * @param activeTabSupplier Supplier for the current active tab.
     * @param modalDialogManager Modal dialog manager, used to disable the button when a dialog is
     *         visible. Can be null to disable this behavior.
     * @param buttonDrawable Drawable for the button icon.
     * @param contentDescription String for the button's content description.
     * @param supportsTinting Whether the button's icon should be tinted.
     * @param iphCommandBuilder An IPH command builder instance to show when the button is
     *         displayed, can be null.
     * @param adaptiveButtonVariant Enum value of {@link AdaptiveToolbarButtonVariant}, used for
     *         metrics.
     */
    public BaseButtonDataProvider(
            Supplier<Tab> activeTabSupplier,
            @Nullable ModalDialogManager modalDialogManager,
            Drawable buttonDrawable,
            String contentDescription,
            @StringRes int actionChipLabelResId,
            boolean supportsTinting,
            @Nullable IPHCommandBuilder iphCommandBuilder,
            @AdaptiveToolbarButtonVariant int adaptiveButtonVariant,
            @StringRes int tooltipTextResId,
            boolean showHoverHighlight) {
        mActiveTabSupplier = activeTabSupplier;
        mModalDialogManager = modalDialogManager;
        if (mModalDialogManager != null) {
            mModalDialogObserver =
                    new ModalDialogManagerObserver() {
                        @Override
                        public void onDialogAdded(PropertyModel model) {
                            mButtonData.setEnabled(false);
                            notifyObservers(mButtonData.canShow());
                        }

                        @Override
                        public void onLastDialogDismissed() {
                            mButtonData.setEnabled(true);
                            notifyObservers(mButtonData.canShow());
                        }
                    };
            mModalDialogManager.addObserver(mModalDialogObserver);
        }

        if (!AdaptiveToolbarFeatures.isDynamicAction(adaptiveButtonVariant)) {
            assert actionChipLabelResId == Resources.ID_NULL
                    : "Action chip should only be used on dynamic actions";
        }

        mButtonData =
                new ButtonDataImpl(
                        /* canShow= */ false,
                        buttonDrawable,
                        /* onClickListener= */ this,
                        contentDescription,
                        actionChipLabelResId,
                        supportsTinting,
                        /* iphCommandBuilder= */ iphCommandBuilder,
                        /* isEnabled= */ true,
                        adaptiveButtonVariant,
                        tooltipTextResId,
                        showHoverHighlight);
    }

    /**
     * Checks if the button should be shown for the current tab. Called every time {@code get()} is
     * invoked.
     * @param tab Current tab.
     * @return whether the button should be shown for the current tab.
     */
    @CallSuper
    protected boolean shouldShowButton(Tab tab) {
        if (tab == null) return false;

        if (tab.isIncognito() && !mShouldShowOnIncognitoTabs) return false;

        return true;
    }

    protected void notifyObservers(boolean hint) {
        for (ButtonDataObserver observer : mObservers) {
            observer.buttonDataChanged(hint);
        }
    }

    /**
     * Sets the button's {@link IPHCommandBuilder} if needed, called every time {@code get()} is
     * invoked.
     * @param tab Current tab.
     */
    private void maybeSetIphCommandBuilder(Tab tab) {
        if (mButtonData.getButtonSpec().getIPHCommandBuilder() != null
                || tab == null
                || !FeatureList.isInitialized()
                || !AdaptiveToolbarFeatures.isCustomizationEnabled()
                || AdaptiveToolbarFeatures.shouldShowActionChip(
                        mButtonData.getButtonSpec().getButtonVariant())) {
            return;
        }

        mButtonData.updateIPHCommandBuilder(getIphCommandBuilder(tab));
    }

    /** Sets whether the button should be shown on incognito tabs, default is false. */
    protected void setShouldShowOnIncognitoTabs(boolean shouldShowOnIncognitoTabs) {
        mShouldShowOnIncognitoTabs = shouldShowOnIncognitoTabs;
    }

    /**
     * Gets an {@link IPHCommandBuilder} builder instance to use on this button. Only called when
     * native is initialized and when there's no IPHCommandBuilder set.
     * @param tab Current tab.
     * @return An {@link org.chromium.chrome.browser.user_education.IPHCommand} instance to set on
     *         this button, or null if no IPH should be used.
     */
    protected IPHCommandBuilder getIphCommandBuilder(Tab tab) {
        return null;
    }

    /** ButtonDataProvider implementation. */
    @Override
    public void addObserver(ButtonDataObserver obs) {
        mObservers.addObserver(obs);
    }

    @Override
    public void removeObserver(ButtonDataObserver obs) {
        mObservers.removeObserver(obs);
    }

    @Override
    public ButtonData get(Tab tab) {
        mButtonData.setCanShow(shouldShowButton(tab));
        maybeSetIphCommandBuilder(tab);

        return mButtonData;
    }

    @Override
    @CallSuper
    public void destroy() {
        mObservers.clear();
        if (mModalDialogManager != null) {
            mModalDialogManager.removeObserver(mModalDialogObserver);
        }
    }

    /* OnClickListener implementation. */
    @Override
    public abstract void onClick(View view);
}