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

import androidx.annotation.IntDef;

import org.chromium.base.TraceEvent;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.browser.toolbar.ToolbarFeatures;

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

/**
 * Contains whether the toolbar is ready for a capture, and a reason for that boolean choice. Often
 * implementors may find they have multiple reasons for the same boolean result, but they should
 * arbitrarily and consistently pick one reason. The reason is used to report metrics and should
 * still be a useful tool for understanding captures.
 */
public class CaptureReadinessResult {
    /**
     * Reasons to allow toolbar captures. Treat this list as append only and keep it in sync with
     * TopToolbarAllowCaptureReason in enums.xml, as well as the proto in chrome_track_event.proto.
     **/
    @IntDef({
        TopToolbarAllowCaptureReason.UNKNOWN,
        TopToolbarAllowCaptureReason.FORCE_CAPTURE,
        TopToolbarAllowCaptureReason.SNAPSHOT_DIFFERENCE,
        TopToolbarAllowCaptureReason.NUM_ENTRIES
    })
    @Retention(RetentionPolicy.SOURCE)
    @interface TopToolbarAllowCaptureReason {
        int UNKNOWN = 0;
        int FORCE_CAPTURE = 1;
        int SNAPSHOT_DIFFERENCE = 2;
        int NUM_ENTRIES = 3;
    }

    /**
     * Reasons to block toolbar captures. Treat this list as append only and keep it in sync with
     * TopToolbarBlockCaptureReason in enums.xml, as well as the proto in chrome_track_event.proto.
     */
    @IntDef({
        TopToolbarBlockCaptureReason.UNKNOWN,
        TopToolbarBlockCaptureReason.TOOLBAR_OR_RESULT_NULL,
        TopToolbarBlockCaptureReason.VIEW_NOT_DIRTY,
        TopToolbarBlockCaptureReason.SNAPSHOT_SAME,
        TopToolbarBlockCaptureReason.URL_BAR_HAS_FOCUS,
        TopToolbarBlockCaptureReason.URL_BAR_FOCUS_IN_PROGRESS,
        TopToolbarBlockCaptureReason.OPTIONAL_BUTTON_ANIMATION_IN_PROGRESS,
        TopToolbarBlockCaptureReason.STATUS_ICON_ANIMATION_IN_PROGRESS,
        TopToolbarBlockCaptureReason.SCROLL_ABLATION,
        TopToolbarBlockCaptureReason.BROWSER_CONTROLS_LOCKED,
        TopToolbarBlockCaptureReason.TAB_SWITCHER_MODE,
        TopToolbarBlockCaptureReason.COMPOSITOR_IN_MOTION,
        TopToolbarBlockCaptureReason.NTP_Y_TRANSLATION,
        TopToolbarBlockCaptureReason.FULLSCREEN,
        TopToolbarBlockCaptureReason.TABLET_BUTTON_ANIMATION_IN_PROGRESS,
        TopToolbarBlockCaptureReason.NUM_ENTRIES
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface TopToolbarBlockCaptureReason {
        int UNKNOWN = 0;
        int TOOLBAR_OR_RESULT_NULL = 1;
        int VIEW_NOT_DIRTY = 2;
        int SNAPSHOT_SAME = 3;
        int URL_BAR_HAS_FOCUS = 4;
        int URL_BAR_FOCUS_IN_PROGRESS = 5;
        int OPTIONAL_BUTTON_ANIMATION_IN_PROGRESS = 6;
        int STATUS_ICON_ANIMATION_IN_PROGRESS = 7;
        int SCROLL_ABLATION = 8;
        int BROWSER_CONTROLS_LOCKED = 9;
        int TAB_SWITCHER_MODE = 10;
        int COMPOSITOR_IN_MOTION = 11;
        int NTP_Y_TRANSLATION = 12;
        int FULLSCREEN = 13;
        int TABLET_BUTTON_ANIMATION_IN_PROGRESS = 14;
        int NUM_ENTRIES = 15;
    }

    public static CaptureReadinessResult readyForced() {
        return new CaptureReadinessResult(
                true,
                TopToolbarAllowCaptureReason.FORCE_CAPTURE,
                TopToolbarBlockCaptureReason.UNKNOWN,
                ToolbarSnapshotDifference.NONE);
    }

    public static CaptureReadinessResult readyWithSnapshotDifference(
            @ToolbarSnapshotDifference int difference) {
        return new CaptureReadinessResult(
                true,
                TopToolbarAllowCaptureReason.SNAPSHOT_DIFFERENCE,
                TopToolbarBlockCaptureReason.UNKNOWN,
                difference);
    }

    public static CaptureReadinessResult notReady(@TopToolbarBlockCaptureReason int blockReason) {
        return new CaptureReadinessResult(
                false,
                TopToolbarAllowCaptureReason.UNKNOWN,
                blockReason,
                ToolbarSnapshotDifference.NONE);
    }

    /* Used for legacy call sites where metrics are not filled out. */
    public static CaptureReadinessResult unknown(boolean isReady) {
        return new CaptureReadinessResult(
                isReady,
                TopToolbarAllowCaptureReason.UNKNOWN,
                TopToolbarBlockCaptureReason.UNKNOWN,
                ToolbarSnapshotDifference.NONE);
    }

    private static void logAllowCaptureReason(@TopToolbarAllowCaptureReason int reason) {
        RecordHistogram.recordEnumeratedHistogram(
                "Android.TopToolbar.AllowCaptureReason",
                reason,
                TopToolbarAllowCaptureReason.NUM_ENTRIES);
    }

    private static void logBlockCaptureReason(@TopToolbarBlockCaptureReason int reason) {
        RecordHistogram.recordEnumeratedHistogram(
                "Android.TopToolbar.BlockCaptureReason",
                reason,
                TopToolbarBlockCaptureReason.NUM_ENTRIES);
    }

    public static void logCaptureReasonFromResult(CaptureReadinessResult result) {
        if (!ToolbarFeatures.shouldRecordSuppressionMetrics()) {
            return;
        }

        // The Java -> C++ layer makes passing enums tricky so we store the integer value and then
        // convert it to a proto enum on the C++ side. If we pass a -1 we will not set that
        // corresponding field.
        int blockReason = -1;
        int allowReason = -1;
        int snapshotDiff = -1;

        // Log the reason to UMA and update our trace event values.
        if (result == null) {
            blockReason = TopToolbarBlockCaptureReason.TOOLBAR_OR_RESULT_NULL;
            logBlockCaptureReason(blockReason);
        } else if (result.isReady) {
            allowReason = result.allowReason;
            logAllowCaptureReason(allowReason);
            if (result.allowReason == TopToolbarAllowCaptureReason.SNAPSHOT_DIFFERENCE) {
                snapshotDiff = result.snapshotDifference;
                RecordHistogram.recordEnumeratedHistogram(
                        "Android.TopToolbar.SnapshotDifference",
                        snapshotDiff,
                        ToolbarSnapshotDifference.NUM_ENTRIES);
            }
        } else {
            blockReason = result.blockReason;
            logBlockCaptureReason(blockReason);
        }
        // Emit our trace event that will tell use why this capture occurred.
        TraceEvent.instantAndroidToolbar(blockReason, allowReason, snapshotDiff);
    }

    public final boolean isReady;
    public final @TopToolbarBlockCaptureReason int blockReason;
    public final @TopToolbarAllowCaptureReason int allowReason;
    public final @ToolbarSnapshotDifference int snapshotDifference;

    private CaptureReadinessResult(
            boolean isReady,
            @TopToolbarAllowCaptureReason int allowReason,
            @TopToolbarBlockCaptureReason int blockReason,
            @ToolbarSnapshotDifference int snapshotDifference) {
        this.isReady = isReady;

        assert blockReason == TopToolbarBlockCaptureReason.UNKNOWN
                || allowReason == TopToolbarAllowCaptureReason.UNKNOWN;
        this.blockReason = blockReason;
        this.allowReason = allowReason;

        assert snapshotDifference == ToolbarSnapshotDifference.NONE
                || allowReason == TopToolbarAllowCaptureReason.SNAPSHOT_DIFFERENCE;
        this.snapshotDifference = snapshotDifference;
    }
}