chromium/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/VoiceToolbarButtonController.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.toolbar;

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

import org.chromium.base.FeatureList;
import org.chromium.base.metrics.RecordUserAction;
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.components.browser_ui.widget.highlight.ViewHighlighter.HighlightParams;
import org.chromium.components.browser_ui.widget.highlight.ViewHighlighter.HighlightShape;
import org.chromium.components.embedder_support.util.UrlUtilities;
import org.chromium.components.feature_engagement.EventConstants;
import org.chromium.components.feature_engagement.FeatureConstants;
import org.chromium.components.feature_engagement.Tracker;
import org.chromium.ui.modaldialog.ModalDialogManager;

/**
 * Handles displaying the voice search button on toolbar depending on several conditions (e.g.
 * device width, whether NTP is shown, whether voice is enabled).
 *
 * <p>TODO(crbug.com/40729195): Move this to ../voice/ along with VoiceRecognitionHandler and the
 * assistant support.
 */
public class VoiceToolbarButtonController extends BaseButtonDataProvider {
    private final Supplier<Tracker> mTrackerSupplier;

    private final VoiceSearchDelegate mVoiceSearchDelegate;

    /** Delegate interface for interacting with voice search. */
    public interface VoiceSearchDelegate {
        /**
         * @return True if voice search is enabled for the current session.
         */
        boolean isVoiceSearchEnabled();

        /** Starts a voice search interaction. */
        void startVoiceRecognition();
    }

    /**
     * Creates a VoiceToolbarButtonController object.
     *
     * @param context The context for retrieving string resources.
     * @param buttonDrawable Drawable for the voice button.
     * @param activeTabSupplier Provides the currently displayed {@link Tab}.
     * @param trackerSupplier  Supplier for the current profile tracker.
     * @param modalDialogManager Dispatcher for modal lifecycle events
     * @param voiceSearchDelegate Provides interaction with voice search.
     */
    public VoiceToolbarButtonController(
            Context context,
            Drawable buttonDrawable,
            Supplier<Tab> activeTabSupplier,
            Supplier<Tracker> trackerSupplier,
            ModalDialogManager modalDialogManager,
            VoiceSearchDelegate voiceSearchDelegate) {
        super(
                activeTabSupplier,
                modalDialogManager,
                buttonDrawable,
                context.getString(R.string.accessibility_toolbar_btn_mic),
                /* actionChipLabelResId= */ Resources.ID_NULL,
                /* supportsTinting= */ true,
                /* iphCommandBuilder= */ null,
                AdaptiveToolbarButtonVariant.VOICE,
                /* tooltipTextResId= */ R.string.adaptive_toolbar_button_preference_voice_search,
                /* showHoverHighlight= */ true);
        mTrackerSupplier = trackerSupplier;
        mVoiceSearchDelegate = voiceSearchDelegate;
    }

    @Override
    public void onClick(View view) {
        RecordUserAction.record("MobileTopToolbarVoiceButton");
        mVoiceSearchDelegate.startVoiceRecognition();

        if (mTrackerSupplier.hasValue()) {
            mTrackerSupplier
                    .get()
                    .notifyEvent(EventConstants.ADAPTIVE_TOOLBAR_CUSTOMIZATION_VOICE_SEARCH_OPENED);
        }
    }

    /** Triggers checking and possibly updating the mic visibility */
    public void updateMicButtonState() {
        mButtonData.setCanShow(shouldShowButton(mActiveTabSupplier.get()));
        notifyObservers(mButtonData.canShow());
    }

    /**
     * Returns an IPH for this button. Only called once native is initialized and when {@code
     * AdaptiveToolbarFeatures.isCustomizationEnabled()} is true.
     * @param tab Current tab.
     */
    @Override
    protected IPHCommandBuilder getIphCommandBuilder(Tab tab) {
        HighlightParams params = new HighlightParams(HighlightShape.CIRCLE);
        params.setBoundsRespectPadding(true);
        IPHCommandBuilder iphCommandBuilder =
                new IPHCommandBuilder(
                                tab.getContext().getResources(),
                                FeatureConstants
                                        .ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_VOICE_SEARCH_FEATURE,
                                /* stringId= */ R.string.adaptive_toolbar_button_voice_search_iph,
                                /* accessibilityStringId= */ R.string
                                        .adaptive_toolbar_button_voice_search_iph)
                        .setHighlightParams(params);

        return iphCommandBuilder;
    }

    @Override
    protected boolean shouldShowButton(Tab tab) {
        if (!super.shouldShowButton(tab)) return false;

        return mVoiceSearchDelegate.isVoiceSearchEnabled()
                && UrlUtilities.isHttpOrHttps(tab.getUrl());
    }

    /** Returns whether the feature flags allow showing the mic icon in the toolbar. */
    public static boolean isToolbarMicEnabled() {
        if (!FeatureList.isInitialized()) return false;
        return AdaptiveToolbarFeatures.isCustomizationEnabled();
    }
}