chromium/chrome/browser/lens/java/src/org/chromium/chrome/browser/lens/LensMetrics.java

// Copyright 2021 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.lens;

import androidx.annotation.IntDef;

import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.RecordUserAction;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/** Static utility methods to support user action logging for Lens entry points. */
public class LensMetrics {
    public static final String AMBIENT_SEARCH_QUERY_HISTOGRAM = "Search.Ambient.Query";
    public static final String SEARCH_CAMERA_OPEN_HISTOGRAM = "Search.Image.Camera.Open";

    // Note: these values must match the LensSupportStatus enum in enums.xml.
    // Only add new values at the end, right before NUM_ENTRIES.
    @IntDef({
        LensSupportStatus.LENS_SEARCH_SUPPORTED,
        LensSupportStatus.NON_GOOGLE_SEARCH_ENGINE,
        LensSupportStatus.ACTIVITY_NOT_ACCESSIBLE,
        LensSupportStatus.OUT_OF_DATE,
        LensSupportStatus.SEARCH_BY_IMAGE_UNAVAILABLE,
        LensSupportStatus.LEGACY_OS,
        LensSupportStatus.INVALID_PACKAGE,
        LensSupportStatus.LENS_SHOP_SUPPORTED,
        LensSupportStatus.LENS_SHOP_AND_SEARCH_SUPPORTED,
        LensSupportStatus.CAMERA_NOT_AVAILABLE,
        LensSupportStatus.DISABLED_ON_LOW_END_DEVICE,
        LensSupportStatus.AGSA_VERSION_NOT_SUPPORTED,
        LensSupportStatus.DISABLED_ON_INCOGNITO,
        LensSupportStatus.DISABLED_ON_TABLET,
        LensSupportStatus.DISABLED_FOR_ENTERPRISE_USER
    })
    @Retention(RetentionPolicy.SOURCE)
    public static @interface LensSupportStatus {
        int LENS_SEARCH_SUPPORTED = 0;
        int NON_GOOGLE_SEARCH_ENGINE = 1;
        int ACTIVITY_NOT_ACCESSIBLE = 2;
        int OUT_OF_DATE = 3;
        int SEARCH_BY_IMAGE_UNAVAILABLE = 4;
        int LEGACY_OS = 5;
        int INVALID_PACKAGE = 6;
        int LENS_SHOP_SUPPORTED = 7;
        int LENS_SHOP_AND_SEARCH_SUPPORTED = 8;
        int CAMERA_NOT_AVAILABLE = 9;
        int DISABLED_ON_LOW_END_DEVICE = 10;
        int AGSA_VERSION_NOT_SUPPORTED = 11;
        int DISABLED_ON_INCOGNITO = 12;
        int DISABLED_ON_TABLET = 13;
        int DISABLED_FOR_ENTERPRISE_USER = 14;
        int NUM_ENTRIES = 15;
    }

    // Note: These values must match the AmbientSearchEntryPoint enum in enums.xml.
    // Only add new values at the end, right before NUM_ENTRIES.
    @IntDef({
        AmbientSearchEntryPoint.CONTEXT_MENU_SEARCH_IMAGE_WITH_GOOGLE_LENS,
        AmbientSearchEntryPoint.CONTEXT_MENU_SEARCH_IMAGE_WITH_WEB,
        AmbientSearchEntryPoint.CONTEXT_MENU_SEARCH_REGION_WITH_GOOGLE_LENS,
        AmbientSearchEntryPoint.CONTEXT_MENU_SEARCH_REGION_WITH_WEB,
        AmbientSearchEntryPoint.CONTEXT_MENU_SEARCH_WEB_FOR,
        AmbientSearchEntryPoint.OMNIBOX,
        AmbientSearchEntryPoint.NEW_TAB_PAGE,
        AmbientSearchEntryPoint.QUICK_ACTION_SEARCH_WIDGET,
        AmbientSearchEntryPoint.KEYBOARD,
        AmbientSearchEntryPoint.SPOTLIGHT,
        AmbientSearchEntryPoint.APP_ICON_LONG_PRESS,
        AmbientSearchEntryPoint.PLUS_BUTTON,
        AmbientSearchEntryPoint.WEB_SEARCH_BAR,
        AmbientSearchEntryPoint.COMPANION_REGION_SEARCH,
        AmbientSearchEntryPoint.TRANSLATE_ONEBOX,
        AmbientSearchEntryPoint.INTENTS,
        AmbientSearchEntryPoint.WEB_IMAGES_SEARCH_BAR,
        AmbientSearchEntryPoint.WHATS_NEW_PROMO,
        AmbientSearchEntryPoint.NUM_ENTRIES
    })
    @Retention(RetentionPolicy.SOURCE)
    public static @interface AmbientSearchEntryPoint {
        int CONTEXT_MENU_SEARCH_IMAGE_WITH_GOOGLE_LENS = 0;
        int CONTEXT_MENU_SEARCH_IMAGE_WITH_WEB = 1;
        int CONTEXT_MENU_SEARCH_REGION_WITH_GOOGLE_LENS = 2;
        int CONTEXT_MENU_SEARCH_REGION_WITH_WEB = 3;
        int CONTEXT_MENU_SEARCH_WEB_FOR = 4;
        int OMNIBOX = 5;
        int NEW_TAB_PAGE = 6;
        int QUICK_ACTION_SEARCH_WIDGET = 7;
        int KEYBOARD = 8;
        int SPOTLIGHT = 9;
        int APP_ICON_LONG_PRESS = 10;
        int PLUS_BUTTON = 11;
        int WEB_SEARCH_BAR = 12;
        int COMPANION_REGION_SEARCH = 13;
        int TRANSLATE_ONEBOX = 14;
        int INTENTS = 15;
        int WEB_IMAGES_SEARCH_BAR = 16;
        int WHATS_NEW_PROMO = 17;
        int NUM_ENTRIES = 18;
    }

    // Note: These values must match the CameraOpenEntryPoint enum in enums.xml.
    // Only add new values at the end, right before NUM_ENTRIES.
    @IntDef({
        CameraOpenEntryPoint.OMNIBOX,
        CameraOpenEntryPoint.NEW_TAB_PAGE,
        CameraOpenEntryPoint.QUICK_ACTION_SEARCH_WIDGET,
        CameraOpenEntryPoint.TASKS_SURFACE,
        CameraOpenEntryPoint.KEYBOARD,
        CameraOpenEntryPoint.SPOTLIGHT,
        CameraOpenEntryPoint.APP_ICON_LONG_PRESS,
        CameraOpenEntryPoint.PLUS_BUTTON,
        CameraOpenEntryPoint.WEB_SEARCH_BAR,
        CameraOpenEntryPoint.TRANSLATE_ONEBOX,
        CameraOpenEntryPoint.INTENTS,
        CameraOpenEntryPoint.WEB_IMAGES_SEARCH_BAR,
        CameraOpenEntryPoint.WHATS_NEW_PROMO,
        CameraOpenEntryPoint.GOOGLE_BOTTOM_BAR,
        CameraOpenEntryPoint.NUM_ENTRIES
    })
    @Retention(RetentionPolicy.SOURCE)
    public static @interface CameraOpenEntryPoint {
        int OMNIBOX = 0;
        int NEW_TAB_PAGE = 1;
        int QUICK_ACTION_SEARCH_WIDGET = 2;
        int TASKS_SURFACE = 3;
        int KEYBOARD = 4;
        int SPOTLIGHT = 5;
        int APP_ICON_LONG_PRESS = 6;
        int PLUS_BUTTON = 7;
        int WEB_SEARCH_BAR = 8;
        int TRANSLATE_ONEBOX = 9;
        int INTENTS = 10;
        int WEB_IMAGES_SEARCH_BAR = 11;
        int WHATS_NEW_PROMO = 12;
        int GOOGLE_BOTTOM_BAR = 13;
        int NUM_ENTRIES = 14;
    }

    /** Record an ambient search query along with the entry point that initiated. */
    public static void recordAmbientSearchQuery(@AmbientSearchEntryPoint int entryPoint) {
        RecordHistogram.recordEnumeratedHistogram(
                AMBIENT_SEARCH_QUERY_HISTOGRAM, entryPoint, AmbientSearchEntryPoint.NUM_ENTRIES);
    }

    /** Record an intent sent to Lens to open the viewfinder with the entry point used. */
    public static void recordCameraOpen(@CameraOpenEntryPoint int entryPoint) {
        RecordHistogram.recordEnumeratedHistogram(
                SEARCH_CAMERA_OPEN_HISTOGRAM, entryPoint, CameraOpenEntryPoint.NUM_ENTRIES);
    }

    /** Record Lens support status for a Lens entry point. */
    public static void recordLensSupportStatus(
            String histogramName, @LensSupportStatus int reason) {
        RecordHistogram.recordEnumeratedHistogram(
                histogramName, reason, LensSupportStatus.NUM_ENTRIES);
    }

    /**
     * @return The histogram name for a Lens entry point.
     */
    public static String getSupportStatusHistogramNameByEntryPoint(
            @LensEntryPoint int lensEntryPoint) {
        switch (lensEntryPoint) {
            case LensEntryPoint.NEW_TAB_PAGE:
            case LensEntryPoint.OMNIBOX:
            case LensEntryPoint.TASKS_SURFACE:
                // Combine the LensSupportStatus reporting into one histogram for omnibox
                // entry points.
                return "Lens.Omnibox.LensSupportStatus";
            case LensEntryPoint.CONTEXT_MENU_SEARCH_MENU_ITEM:
            case LensEntryPoint.CONTEXT_MENU_SHOP_MENU_ITEM:
                return "ContextMenu.LensSupportStatus";
            case LensEntryPoint.QUICK_ACTION_SEARCH_WIDGET:
                return "Lens.QuickActionSearchWidget.LensSupportStatus";
            case LensEntryPoint.GOOGLE_BOTTOM_BAR:
                return "CustomTabs.GoogleBottomBar.LensSupportStatus";
            case LensEntryPoint.CONTEXT_MENU_CHIP:
            default:
                assert false : "Method not implemented.";
        }
        return null;
    }

    /** Record the time spent between Lens started and Lens dismissed. */
    public static void recordTimeSpentInLens(String histogramName, long timeSpentInLensMs) {
        RecordHistogram.recordLongTimesHistogram(histogramName, timeSpentInLensMs);
    }

    /**
     * Record when the Lens entry point is shown.
     * @param lensEntryPoint The entry point to be recorded.
     * @param isShown Whether the entry point is shown.
     */
    public static void recordShown(@LensEntryPoint int lensEntryPoint, boolean isShown) {
        if (!isShown) {
            return;
        }
        String actionName = getShownActionName(lensEntryPoint);
        if (actionName != null) RecordUserAction.record(actionName);
    }

    /**
     * Record when the Lens entry point is clicked.
     * @param lensEntryPoint The entry point to be recorded.
     */
    public static void recordClicked(@LensEntryPoint int lensEntryPoint) {
        String actionName = getClickedActionName(lensEntryPoint);
        if (actionName != null) RecordUserAction.record(actionName);
    }

    /** Record when the Lens entry point is shown and omnibox is focused. */
    public static void recordOmniboxFocusedWhenLensShown() {
        RecordUserAction.record("MobileOmniboxFocusedLensShown");
    }

    private static String getShownActionName(@LensEntryPoint int lensEntryPoint) {
        switch (lensEntryPoint) {
            case LensEntryPoint.NEW_TAB_PAGE:
                return "NewTabPage.SearchBox.LensShown";
            case LensEntryPoint.OMNIBOX:
                return "MobileOmniboxLensShown";
            case LensEntryPoint.TASKS_SURFACE:
                return "TasksSurface.FakeBox.LensShown";
            case LensEntryPoint.CONTEXT_MENU_SEARCH_MENU_ITEM:
            case LensEntryPoint.CONTEXT_MENU_SHOP_MENU_ITEM:
            case LensEntryPoint.CONTEXT_MENU_CHIP:
            case LensEntryPoint.GOOGLE_BOTTOM_BAR:
            default:
                assert false : "Method not implemented.";
        }
        return null;
    }

    private static String getClickedActionName(@LensEntryPoint int lensEntryPoint) {
        switch (lensEntryPoint) {
            case LensEntryPoint.NEW_TAB_PAGE:
                return "NewTabPage.SearchBox.Lens";
            case LensEntryPoint.OMNIBOX:
                return "MobileOmniboxLens";
            case LensEntryPoint.TASKS_SURFACE:
                return "TasksSurface.FakeBox.Lens";
            case LensEntryPoint.CONTEXT_MENU_SEARCH_MENU_ITEM:
            case LensEntryPoint.CONTEXT_MENU_SHOP_MENU_ITEM:
            case LensEntryPoint.CONTEXT_MENU_CHIP:
            case LensEntryPoint.GOOGLE_BOTTOM_BAR:
            default:
                assert false : "Method not implemented.";
        }
        return null;
    }
}