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

import androidx.annotation.VisibleForTesting;

import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;

import java.util.concurrent.TimeUnit;

/**
 * A utility class providing the default browser promo impression counts, including total promo
 * count, session count, and intervals.
 */
public class DefaultBrowserPromoImpressionCounter {
    private static final int MAX_PROMO_COUNT = 1;
    private static final int MIN_TRIGGER_SESSION_COUNT = 3;
    private static final int MIN_PROMO_INTERVAL = 0;

    @VisibleForTesting static final String MAX_PROMO_COUNT_PARAM = "max_promo_count";

    @VisibleForTesting
    static final String PROMO_TIME_INTERVAL_DAYS_PARAM = "promo_time_interval_days";

    @VisibleForTesting static final String PROMO_SESSION_INTERVAL_PARAM = "promo_session_interval";

    static final String CHROME_STABLE_PACKAGE_NAME = "com.android.chrome";

    DefaultBrowserPromoImpressionCounter() {}

    int getPromoCount() {
        return ChromeSharedPreferences.getInstance()
                .readInt(ChromePreferenceKeys.DEFAULT_BROWSER_PROMO_PROMOED_COUNT, 0);
    }

    void onPromoShown() {
      incrementPromoCount();
      recordPromoTime();
      recordLastPromoSessionCount();
    }

    /**
     * This decides whether the promo should be promoted base on the impression count.
     * Return false if any of following criteria is met:
     * 1. A promo dialog has been displayed before, unless {@code ignoreMaxCount} is true.
     * 2. Not enough sessions have been started before.
     * 3. Less than the promo interval if re-promoting.
     *
     * @param ignoreMaxCount
     * @return boolean if promo dialog can be displayed.
     */
    boolean shouldShowPromo(boolean ignoreMaxCount) {
      return ignoreMaxCount ||
       (getPromoCount() < getMaxPromoCount()
              && getSessionCount() >= getMinSessionCount()
              && getLastPromoInterval() >= getMinPromoInterval());
    }

    private void incrementPromoCount() {
        ChromeSharedPreferences.getInstance()
                .incrementInt(ChromePreferenceKeys.DEFAULT_BROWSER_PROMO_PROMOED_COUNT);
    }

    int getMaxPromoCount() {
        if (ChromeFeatureList.isEnabled(ChromeFeatureList.DEFAULT_BROWSER_PROMO_ANDROID)) {
            return ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
                    ChromeFeatureList.DEFAULT_BROWSER_PROMO_ANDROID, MAX_PROMO_COUNT_PARAM, 3);
        }
        return MAX_PROMO_COUNT;
    }

    int getSessionCount() {
        return ChromeSharedPreferences.getInstance()
                .readInt(ChromePreferenceKeys.DEFAULT_BROWSER_PROMO_SESSION_COUNT, 0);
    }

    int getLastPromoSessionCount() {
        return ChromeSharedPreferences.getInstance()
                .readInt(ChromePreferenceKeys.DEFAULT_BROWSER_PROMO_LAST_SESSION_COUNT, 0);
    }

    private void recordLastPromoSessionCount() {
        ChromeSharedPreferences.getInstance()
                .writeInt(
                        ChromePreferenceKeys.DEFAULT_BROWSER_PROMO_LAST_SESSION_COUNT,
                        getSessionCount());
    }

    private void recordPromoTime() {
        ChromeSharedPreferences.getInstance()
                .writeInt(
                        ChromePreferenceKeys.DEFAULT_BROWSER_PROMO_LAST_PROMO_TIME,
                        (int) TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis()));
    }

    int getMinSessionCount() {
        if (ChromeFeatureList.isEnabled(ChromeFeatureList.DEFAULT_BROWSER_PROMO_ANDROID)) {
            if (getPromoCount() == 0) {
                return MIN_TRIGGER_SESSION_COUNT;
            } else {
                int sessionCountAtLastPromo = getLastPromoSessionCount();
                // If we've shown a promo before and the newer last promo session count hasn't
                // been set, assume the session count at the last time of promo was the minimum
                // required to show the promo.
                if (sessionCountAtLastPromo == 0) {
                    sessionCountAtLastPromo = MIN_TRIGGER_SESSION_COUNT;
                }
                int promoSessionInterval =
                        ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
                                ChromeFeatureList.DEFAULT_BROWSER_PROMO_ANDROID,
                                PROMO_SESSION_INTERVAL_PARAM,
                                2);

                return promoSessionInterval + sessionCountAtLastPromo;
            }
        }
        return MIN_TRIGGER_SESSION_COUNT;
    }

    int getLastPromoInterval() {
        int lastPromoTime =
                ChromeSharedPreferences.getInstance()
                        .readInt(ChromePreferenceKeys.DEFAULT_BROWSER_PROMO_LAST_PROMO_TIME, -1);
        if (lastPromoTime != -1) {
            return (int)
                    (TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis()) - lastPromoTime);
        }
        return Integer.MAX_VALUE;
    }

    int getMinPromoInterval() {
        if (ChromeFeatureList.isEnabled(ChromeFeatureList.DEFAULT_BROWSER_PROMO_ANDROID)) {
            int timeIntervalDays =
                    ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
                            ChromeFeatureList.DEFAULT_BROWSER_PROMO_ANDROID,
                            PROMO_TIME_INTERVAL_DAYS_PARAM,
                            3);
            // Scale the time delay by the number of times the promo has shown.
            // By default, expect 0 time delay for first promo show, 3 days for second promo
            // show and 6 days for third promo show.
            int timeIntervalMinutes =
                    (int) (TimeUnit.DAYS.toMinutes(timeIntervalDays) * getPromoCount());
            return timeIntervalMinutes;
        }
        return MIN_PROMO_INTERVAL;
    }
}