blob: 4a14e5d93e1bf808fb8e5af972b3dc1553b09ff5 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// 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 android.app.Activity;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.text.TextUtils;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.CommandLine;
import org.chromium.base.ContextUtils;
import org.chromium.base.PackageManagerUtils;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
import org.chromium.ui.base.WindowAndroid;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.TimeUnit;
/**
* A utility class providing information regarding states of default browser.
*/
public class DefaultBrowserPromoUtils {
@IntDef({DefaultBrowserState.CHROME_DEFAULT, DefaultBrowserState.NO_DEFAULT,
DefaultBrowserState.OTHER_DEFAULT})
@Retention(RetentionPolicy.SOURCE)
public @interface DefaultBrowserState {
int NO_DEFAULT = 0;
int OTHER_DEFAULT = 1;
/**
* CHROME_DEFAULT means the currently running Chrome as opposed to
* #isCurrentDefaultBrowserChrome() which looks for any Chrome.
*/
int CHROME_DEFAULT = 2;
int NUM_ENTRIES = 3;
}
private static final int MIN_TRIGGER_SESSION_COUNT = 3;
private static final int MAX_PROMO_COUNT = 1;
private static final String SESSION_COUNT_PARAM = "min_trigger_session_count";
private static final String PROMO_COUNT_PARAM = "max_promo_count";
private static final String PROMO_INTERVAL_PARAM = "promo_interval";
static final String DISAMBIGUATION_SHEET_PROMOED_KEY = "disambiguation_sheet_promoed";
static final String CHROME_STABLE_PACKAGE_NAME = "com.android.chrome";
// TODO(crbug.com/1090103): move to some util class for reuse.
static final String[] CHROME_PACKAGE_NAMES = {CHROME_STABLE_PACKAGE_NAME, "org.chromium.chrome",
"com.chrome.canary", "com.chrome.beta", "com.chrome.dev"};
/**
* Determine whether a promo dialog should be displayed or not. And prepare related logic to
* launch promo if a promo dialog has been decided to display.
* Return false if any of following criteria is met:
* 1. A promo dialog has been displayed before.
* 2. Not enough sessions have been started before.
* 3. Any chrome, including pre-stable, has been set as default.
* 4. On Chrome stable while no default browser is set and multiple chrome channels
* are installed.
* 5. Less than the promo interval if re-promoing.
*
* @param activity The context.
* @param dispatcher The {@link ActivityLifecycleDispatcher} of the current activity.
* @param windowAndroid The {@link WindowAndroid} for sending an intent.
* @return True if promo dialog will be displayed.
*/
public static boolean prepareLaunchPromoIfNeeded(Activity activity,
ActivityLifecycleDispatcher dispatcher, WindowAndroid windowAndroid) {
if (!ChromeFeatureList.isEnabled(ChromeFeatureList.ANDROID_DEFAULT_BROWSER_PROMO)) {
return false;
}
if (CommandLine.getInstance().hasSwitch(ChromeSwitches.DISABLE_DEFAULT_BROWSER_PROMO)) {
return false;
}
// Criteria 1
int maxPromoCount = ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
ChromeFeatureList.ANDROID_DEFAULT_BROWSER_PROMO, PROMO_COUNT_PARAM,
MAX_PROMO_COUNT);
if (SharedPreferencesManager.getInstance().readInt(
ChromePreferenceKeys.DEFAULT_BROWSER_PROMO_PROMOED_COUNT, 0)
>= maxPromoCount) {
return false;
}
// Criteria 2
int minSessionCount = ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
ChromeFeatureList.ANDROID_DEFAULT_BROWSER_PROMO, SESSION_COUNT_PARAM,
MIN_TRIGGER_SESSION_COUNT);
if (SharedPreferencesManager.getInstance().readInt(
ChromePreferenceKeys.DEFAULT_BROWSER_PROMO_SESSION_COUNT, 0)
< minSessionCount) {
return false;
}
// Criteria 5
int lastPromoTime = SharedPreferencesManager.getInstance().readInt(
ChromePreferenceKeys.DEFAULT_BROWSER_PROMO_LAST_PROMO_TIME, -1);
if (lastPromoTime != -1) {
int promoInterval = ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
ChromeFeatureList.ANDROID_DEFAULT_BROWSER_PROMO, PROMO_INTERVAL_PARAM, 0);
if (TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis()) - lastPromoTime
< promoInterval) {
return false;
}
}
ResolveInfo info = PackageManagerUtils.resolveDefaultWebBrowserActivity();
int state = getCurrentDefaultBrowserState(info);
// Already default
if (state == DefaultBrowserState.CHROME_DEFAULT) return false;
// Criteria 3
if (state == DefaultBrowserState.OTHER_DEFAULT && isCurrentDefaultBrowserChrome(info)) {
return false;
}
// Criteria 4
if (ContextUtils.getApplicationContext().getPackageName().equals(CHROME_STABLE_PACKAGE_NAME)
&& isChromePreStableInstalled()
&& state == DefaultBrowserState.NO_DEFAULT) {
return false;
}
SharedPreferencesManager.getInstance().incrementInt(
ChromePreferenceKeys.DEFAULT_BROWSER_PROMO_PROMOED_COUNT);
SharedPreferencesManager.getInstance().writeInt(
ChromePreferenceKeys.DEFAULT_BROWSER_PROMO_LAST_PROMO_TIME,
(int) TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis()));
DefaultBrowserPromoManager.create(activity, dispatcher, windowAndroid).promo(state);
return true;
}
/**
* Increment session count for triggering feature in the future.
*/
public static void incrementSessionCount() {
if (!ChromeFeatureList.isEnabled(ChromeFeatureList.ANDROID_DEFAULT_BROWSER_PROMO)) return;
SharedPreferencesManager.getInstance().incrementInt(
ChromePreferenceKeys.DEFAULT_BROWSER_PROMO_SESSION_COUNT);
}
/**
* The current {@link DefaultBrowserState} in the system.
*/
@DefaultBrowserState
public static int getCurrentDefaultBrowserState() {
ResolveInfo info = PackageManagerUtils.resolveDefaultWebBrowserActivity();
return getCurrentDefaultBrowserState(info);
}
/**
* Check the result of default browser promo on start up if the default browser promo dialog is
* displayed in this session or last session and the result has not been recorded yet.
*/
public static void maybeRecordOutcomeOnStart() {
if (!ChromeFeatureList.isEnabled(ChromeFeatureList.ANDROID_DEFAULT_BROWSER_PROMO)) return;
if (!SharedPreferencesManager.getInstance().readBoolean(
ChromePreferenceKeys.DEFAULT_BROWSER_PROMO_PROMOED_BY_SYSTEM_SETTINGS, false)) {
return;
}
int previousState = SharedPreferencesManager.getInstance().readInt(
ChromePreferenceKeys.DEFAULT_BROWSER_PROMO_LAST_DEFAULT_STATE);
DefaultBrowserPromoMetrics.recordOutcome(previousState, getCurrentDefaultBrowserState());
// reset
SharedPreferencesManager.getInstance().writeBoolean(
ChromePreferenceKeys.DEFAULT_BROWSER_PROMO_PROMOED_BY_SYSTEM_SETTINGS, false);
}
/**
* Called on new intent is received on the activity so that we can record some metrics.
*/
public static void onNewIntentReceived(Intent intent) {
boolean promoed = intent.getBooleanExtra(DISAMBIGUATION_SHEET_PROMOED_KEY, false);
if (promoed) {
DefaultBrowserPromoMetrics.recordLaunchedByDisambiguationSheet(
getCurrentDefaultBrowserState());
}
}
/**
* Remove intent data if this intent is triggered by default browser promo; Otherwise,
* chrome will open a new tab.
*/
public static void maybeRemoveIntentData(Intent intent) {
if (intent.getBooleanExtra(DISAMBIGUATION_SHEET_PROMOED_KEY, false)) {
// Intent with Uri.EMPTY as data will be ignored by the IntentHandler.
intent.setData(Uri.EMPTY);
}
}
@VisibleForTesting
static boolean isChromePreStableInstalled() {
for (ResolveInfo info : PackageManagerUtils.queryAllWebBrowsersInfo()) {
for (String name : CHROME_PACKAGE_NAMES) {
if (name.equals(CHROME_STABLE_PACKAGE_NAME)) continue;
if (name.equals(info.activityInfo.packageName)) return true;
}
}
return false;
}
@VisibleForTesting
static boolean isCurrentDefaultBrowserChrome(ResolveInfo info) {
String packageName = info.activityInfo.packageName;
for (String name : CHROME_PACKAGE_NAMES) {
if (name.equals(packageName)) return true;
}
return false;
}
@VisibleForTesting
@DefaultBrowserState
static int getCurrentDefaultBrowserState(@NonNull ResolveInfo info) {
if (info.match == 0) return DefaultBrowserState.NO_DEFAULT; // no default
if (TextUtils.equals(ContextUtils.getApplicationContext().getPackageName(),
info.activityInfo.packageName)) {
return DefaultBrowserState.CHROME_DEFAULT; // Already default
}
return DefaultBrowserState.OTHER_DEFAULT;
}
}