chromium/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryIPHUtils.java

// Copyright 2019 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.keyboard_accessory.bar_component;

import android.content.Context;
import android.view.View;

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

import org.chromium.chrome.browser.keyboard_accessory.R;
import org.chromium.chrome.browser.util.ChromeAccessibilityUtil;
import org.chromium.components.browser_ui.widget.textbubble.TextBubble;
import org.chromium.components.feature_engagement.EventConstants;
import org.chromium.components.feature_engagement.FeatureConstants;
import org.chromium.components.feature_engagement.Tracker;
import org.chromium.components.feature_engagement.TriggerState;
import org.chromium.ui.widget.RectProvider;
import org.chromium.ui.widget.ViewRectProvider;

/**
 * This class is a collection of helper functions that are used to coordinate the IPH use in the
 * keyboard accessory bar. It sets up and triggers relevant IPH libraries and is not supposed to
 * keep any state or perform any logic.
 */
class KeyboardAccessoryIPHUtils {
    /**
     * Emits a filling event that matches the given feature. Noop if no tracker is available yet.
     *
     * @param tracker The {@link Tracker} associated with the current session.
     * @param feature The feature to emit a filling event for. Fails if no event to emit.
     */
    static void emitFillingEvent(Tracker tracker, String feature) {
        if (!tracker.isInitialized()) return;
        switch (feature) {
            case FeatureConstants.KEYBOARD_ACCESSORY_ADDRESS_FILL_FEATURE:
                tracker.notifyEvent(EventConstants.KEYBOARD_ACCESSORY_ADDRESS_AUTOFILLED);
                return;
            case FeatureConstants.KEYBOARD_ACCESSORY_PASSWORD_FILLING_FEATURE:
                tracker.notifyEvent(EventConstants.KEYBOARD_ACCESSORY_PASSWORD_AUTOFILLED);
                return;
            case FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_FILLING_FEATURE:
            case FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_OFFER_FEATURE:
            case FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_VIRTUAL_CARD_DISABLED_FEATURE:
            case FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_VIRTUAL_CARD_FEATURE:
            case FeatureConstants.KEYBOARD_ACCESSORY_VIRTUAL_CARD_CVC_FILL_FEATURE:
                tracker.notifyEvent(EventConstants.KEYBOARD_ACCESSORY_PAYMENT_AUTOFILLED);
                return;
            case FeatureConstants.KEYBOARD_ACCESSORY_EXTERNAL_ACCOUNT_PROFILE_FEATURE:
                // Noop as the event is triggered in native AutofillKeyboardAccessoryControllerImpl.
                return;
            case FeatureConstants.KEYBOARD_ACCESSORY_PLUS_ADDRESS_CREATE_SUGGESTION:
                tracker.notifyEvent(
                        EventConstants.KEYBOARD_ACCESSORY_PLUS_ADDRESS_CREATE_SUGGESTION);
                return;
        }
        assert false : "No filling event emitted for feature: " + feature;
    }

    /**
     * Emits a scrolling event recording user's familiarity. Noop if no tracker is available yet.
     *
     * @param tracker The {@link Tracker} associated with the current session.
     */
    static void emitScrollingEvent(Tracker tracker) {
        if (!tracker.isInitialized()) return;
        tracker.notifyEvent(EventConstants.KEYBOARD_ACCESSORY_BAR_SWIPED);
    }

    /**
     * Used to check that filling IPH has priority over IPH that only supports filling, like the IPH
     * promoting the swipeability of the suggestions.
     *
     * @param tracker The {@link Tracker} associated with the current session.
     * @return True iff any IPH prompting to use a chip was shown before.
     */
    static boolean hasShownAnyAutofillIphBefore(Tracker tracker) {
        if (!tracker.isInitialized()) return false;
        return tracker.getTriggerState(FeatureConstants.KEYBOARD_ACCESSORY_ADDRESS_FILL_FEATURE)
                        == TriggerState.HAS_BEEN_DISPLAYED
                || tracker.getTriggerState(
                                FeatureConstants.KEYBOARD_ACCESSORY_PASSWORD_FILLING_FEATURE)
                        == TriggerState.HAS_BEEN_DISPLAYED
                || tracker.getTriggerState(
                                FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_FILLING_FEATURE)
                        == TriggerState.HAS_BEEN_DISPLAYED;
    }

    /**
     * Shows a help bubble pointing to the given rect. It contains an appropriate text for the given
     * feature. The help bubble will not be shown if the {@link Tracker} doesn't allow it anymore.
     * This may happen for example: if it was shown too often, too many IPH were triggered this
     * session or other config restrictions apply.
     *
     * @param tracker The {@link Tracker} associated with the current session.
     * @param feature A String identifying the IPH feature and its appropriate help text.
     * @param rectProvider The {@link RectProvider} providing bounds to which the bubble will point.
     * @param context Context to draw resources from.
     * @param rootView The {@link View} used to determine the maximal dimensions for the bubble.
     * @return The boolean value indicating whether the IPH has been shown.
     */
    static boolean showHelpBubble(
            Tracker tracker,
            String feature,
            RectProvider rectProvider,
            Context context,
            View rootView) {
        TextBubble helpBubble =
                createBubble(tracker, feature, rectProvider, context, rootView, null);
        if (helpBubble != null) helpBubble.show();
        return helpBubble != null;
    }

    /**
     * Shows a help bubble pointing to the given view. It contains an appropriate text for the given
     * feature. The help bubble will not be shown if the {@link Tracker} doesn't allow it anymore.
     * This may happen for example: if it was shown too often, too many IPH were triggered this
     * session or other config restrictions apply.
     *
     * @param tracker The {@link Tracker} associated with the current session.
     * @param feature A String identifying the IPH feature and its appropriate help text.
     * @param rectProvider The {@link RectProvider} providing bounds to which the bubble will point.
     * @param context Context to draw resources from.
     * @param rootView The {@link View} used to determine the maximal dimensions for the bubble.
     * @param helpText String that should be displayed within the IPH bubble.
     * @return The boolean value indicating whether the IPH has been shown.
     */
    static boolean showHelpBubble(
            Tracker tracker,
            String feature,
            RectProvider rectProvider,
            Context context,
            View rootView,
            @Nullable String helpText) {
        TextBubble helpBubble =
                createBubble(tracker, feature, rectProvider, context, rootView, helpText);
        if (helpBubble != null) helpBubble.show();
        return helpBubble != null;
    }

    /**
     * Shows a help bubble pointing to the given view. It contains an appropriate text for the given
     * feature. The help bubble will not be shown if the {@link Tracker} doesn't allow it anymore.
     * This may happen for example: if it was shown too often, too many IPH were triggered this
     * session or other config restrictions apply.
     *
     * @param tracker The {@link Tracker} associated with the current session.
     * @param feature A String identifying the IPH feature and its appropriate help text.
     * @param view The {@link View} providing context and the Rect to which the bubble will point.
     * @param rootView The {@link View} used to determine the maximal dimensions for the bubble.
     * @param helpText String that should be displayed within the IPH bubble.
     * @return The boolean value indicating whether the IPH has been shown.
     */
    static boolean showHelpBubble(
            Tracker tracker, String feature, View view, View rootView, @Nullable String helpText) {
        TextBubble helpBubble =
                createBubble(
                        tracker,
                        feature,
                        new ViewRectProvider(view),
                        view.getContext(),
                        rootView,
                        helpText);
        if (helpBubble == null) return false;
        // To emphasize which chip is pointed to, set selected to true for the built-in highlight.
        // Prefer ViewHighlighter for views without a LayerDrawable background.
        view.setSelected(true);
        helpBubble.addOnDismissListener(
                () -> {
                    view.setSelected(false);
                });
        helpBubble.show();
        return true;
    }

    private static TextBubble createBubble(
            Tracker tracker,
            String feature,
            RectProvider rectProvider,
            Context context,
            View rootView,
            @Nullable String helpText) {
        if (!tracker.isInitialized()) return null;
        if (!tracker.shouldTriggerHelpUI(feature)) return null; // This call records the IPH intent.
        TextBubble helpBubble;
        // If the help text is provided, then use it directly to generate the text bubble.
        if (helpText != null && !helpText.isEmpty()) {
            helpBubble =
                    new TextBubble(
                            context,
                            rootView,
                            helpText,
                            helpText,
                            /* showArrow= */ true,
                            rectProvider,
                            ChromeAccessibilityUtil.get().isAccessibilityEnabled());
        } else {
            @StringRes int helpTextResourceId = getHelpTextForFeature(feature);
            helpBubble =
                    new TextBubble(
                            context,
                            rootView,
                            helpTextResourceId,
                            helpTextResourceId,
                            rectProvider,
                            ChromeAccessibilityUtil.get().isAccessibilityEnabled());
        }
        helpBubble.setDismissOnTouchInteraction(true);
        helpBubble.addOnDismissListener(
                () -> {
                    tracker.dismissed(feature);
                });
        return helpBubble;
    }

    /**
     * Returns an appropriate help text for the given feature or crashes if there is none.
     *
     * @param feature A String identifying the feature.
     * @return The translated help text for the user education element.
     */
    private static @StringRes int getHelpTextForFeature(@FeatureConstants String feature) {
        switch (feature) {
            case FeatureConstants.KEYBOARD_ACCESSORY_ADDRESS_FILL_FEATURE:
            case FeatureConstants.KEYBOARD_ACCESSORY_PASSWORD_FILLING_FEATURE:
            case FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_FILLING_FEATURE:
                return R.string.iph_keyboard_accessory_fill_with_chrome;
            case FeatureConstants.KEYBOARD_ACCESSORY_BAR_SWIPING_FEATURE:
                return R.string.iph_keyboard_accessory_swipe_for_more;
            case FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_VIRTUAL_CARD_FEATURE:
                return R.string.iph_keyboard_accessory_payment_virtual_cards;
            case FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_VIRTUAL_CARD_DISABLED_FEATURE:
                return R.string.iph_keyboard_accessory_payment_virtual_cards_disabled;
            case FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_OFFER_FEATURE:
                return R.string.iph_keyboard_accessory_payment_offer;
            case FeatureConstants.KEYBOARD_ACCESSORY_EXTERNAL_ACCOUNT_PROFILE_FEATURE:
                return org.chromium.chrome.R.string
                        .autofill_iph_external_account_profile_suggestion;
            case FeatureConstants.KEYBOARD_ACCESSORY_VIRTUAL_CARD_CVC_FILL_FEATURE:
                return R.string.iph_keyboard_accessory_virtual_card_cvc_fill_feature;
        }
        assert false : "Unknown help text for feature: " + feature;
        return 0;
    }

    private KeyboardAccessoryIPHUtils() {}
}