chromium/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudIPHController.java

// Copyright 2024 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.readaloud;

import android.app.Activity;
import android.os.Handler;
import android.os.Looper;
import android.view.View;

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

import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.base.supplier.Supplier;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.ui.appmenu.AppMenuHandler;
import org.chromium.chrome.browser.user_education.IPHCommandBuilder;
import org.chromium.chrome.browser.user_education.UserEducationHelper;
import org.chromium.components.feature_engagement.FeatureConstants;

/**
 * Controller to manage when and how we show ReadAloud in-product-help messages to users in the app
 * menu and the CCT app menu.
 */
public class ReadAloudIPHController {
    private final UserEducationHelper mUserEducationHelper;
    private final AppMenuHandler mAppMenuHandler;
    private final View mToolbarMenuButton;
    private final ObservableSupplier<ReadAloudController> mReadAloudControllerSupplier;
    @Nullable private ObservableSupplier<String> mReadAloudReadabilitySupplier;
    private final Supplier<Tab> mCurrentTabSupplier;
    private boolean mShowAppMenuTextBubble;
    private final Runnable mReadabilityUpdateListener = this::maybeShowReadAloudAppMenuIPH;

    /**
     * Constructor.
     *
     * @param activity The current activity.
     * @param profile The current Profile.
     * @param toolbarMenuButton The toolbar menu button to which IPH will be anchored.
     * @param appMenuHandler The app menu handler
     * @param tabSupplier The tab supplier
     * @param readAloudControllerSupplier Supplies the readaloud controller
     * @param showAppMenuTextBubble If the app menu text bubble should be shown. Should not be shown
     *     for custom tabs
     */
    public ReadAloudIPHController(
            Activity activity,
            Profile profile,
            View toolbarMenuButton,
            AppMenuHandler appMenuHandler,
            ObservableSupplier<Tab> tabSupplier,
            ObservableSupplier<ReadAloudController> readAloudControllerSupplier,
            boolean showAppMenuTextBubble) {
        this(
                activity,
                toolbarMenuButton,
                appMenuHandler,
                new UserEducationHelper(activity, profile, new Handler(Looper.getMainLooper())),
                tabSupplier,
                readAloudControllerSupplier,
                showAppMenuTextBubble);
    }

    ReadAloudIPHController(
            Activity activity,
            View toolbarMenuButton,
            AppMenuHandler appMenuHandler,
            UserEducationHelper userEducationHelper,
            ObservableSupplier<Tab> tabSupplier,
            ObservableSupplier<ReadAloudController> readAloudControllerSupplier,
            boolean showAppMenuTextBubble) {
        mToolbarMenuButton = toolbarMenuButton;
        mAppMenuHandler = appMenuHandler;
        mUserEducationHelper = userEducationHelper;
        mCurrentTabSupplier = tabSupplier;
        mReadAloudControllerSupplier = readAloudControllerSupplier;
        mReadAloudControllerSupplier.addObserver(this::readAloudControllerReady);
        mShowAppMenuTextBubble = showAppMenuTextBubble;
    }

    /**
     * If the current tab is readable, requests to show a "Listen to this page" IPH for the app menu
     * and turns on the highlight for the ReadAloud item in the menu. Depends on the
     * IPHMenuButtonHighlightCCTEnabled flag and if showing the app menu text bubble to decide
     * whether to show the app menu button highlight.
     *
     * @param url URL the readability check returns
     */
    public void maybeShowReadAloudAppMenuIPH() {
        if (shouldShowIPH()) {
            boolean isHighlightEnabled =
                    mShowAppMenuTextBubble
                            ? true
                            : ReadAloudFeatures.isIPHMenuButtonHighlightCCTEnabled();
            mUserEducationHelper.requestShowIPH(
                    new IPHCommandBuilder(
                                    mToolbarMenuButton.getContext().getResources(),
                                    FeatureConstants.READ_ALOUD_APP_MENU_FEATURE,
                                    R.string.menu_listen_to_this_page_iph,
                                    R.string.menu_listen_to_this_page_iph)
                            .setAnchorView(mToolbarMenuButton)
                            .setShowTextBubble(mShowAppMenuTextBubble)
                            .setOnShowCallback(
                                    () ->
                                            turnOnHighlightForMenuItem(
                                                    R.id.readaloud_menu_id, isHighlightEnabled))
                            .setOnDismissCallback(this::turnOffHighlightForMenuItem)
                            .build());
        }
    }

    private void turnOnHighlightForMenuItem(int highlightMenuItemId, boolean highlightMenuButton) {
        mAppMenuHandler.setMenuHighlight(highlightMenuItemId, highlightMenuButton);
    }

    private void turnOffHighlightForMenuItem() {
        mAppMenuHandler.clearMenuHighlight();
    }

    private boolean shouldShowIPH() {
        if (mCurrentTabSupplier.get() == null
                || !mCurrentTabSupplier.get().getUrl().isValid()
                || mReadAloudControllerSupplier.get() == null) {
            return false;
        }
        return mReadAloudControllerSupplier.get().isReadable(mCurrentTabSupplier.get());
    }

    void readAloudControllerReady(@Nullable ReadAloudController readAloudController) {
        if (readAloudController != null) {
            readAloudController.addReadabilityUpdateListener(mReadabilityUpdateListener);
        }
    }

    public void destroy() {
        if (mReadAloudControllerSupplier.get() != null) {
            mReadAloudControllerSupplier
                    .get()
                    .removeReadabilityUpdateListener(mReadabilityUpdateListener);
        }
    }

    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
    void setShowAppMenuTextBubble(boolean showAppMenuTextBubble) {
        mShowAppMenuTextBubble = showAppMenuTextBubble;
    }
}