Commit bde7a092 authored by Lijin Shen's avatar Lijin Shen Committed by Commit Bot

Refactor DefaultBrowserPromoUtils for easy testing

Introduce a Dep as a manager of external states for easy testing.

Bug: 1109255
Change-Id: I74cf6cac126a6f8ed4c6f9144131443e1ba57722
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2385982Reviewed-by: default avatarPavel Yatsuk <pavely@chromium.org>
Commit-Queue: Lijin Shen <lazzzis@google.com>
Cr-Commit-Position: refs/heads/master@{#804424}
parent ad4d07fb
......@@ -6,6 +6,7 @@ import("//build/config/android/rules.gni")
android_library("java") {
sources = [
"java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoDeps.java",
"java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoDialog.java",
"java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoManager.java",
"java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoMetrics.java",
......
// 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 static org.chromium.chrome.browser.ui.default_browser_promo.DefaultBrowserPromoManager.P_NO_DEFAULT_PROMO_STRATEGY;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.role.RoleManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.os.Build;
import android.provider.Settings;
import android.text.TextUtils;
import androidx.annotation.NonNull;
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.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
import org.chromium.chrome.browser.ui.default_browser_promo.DefaultBrowserPromoUtils.DefaultBrowserPromoAction;
import org.chromium.chrome.browser.ui.default_browser_promo.DefaultBrowserPromoUtils.DefaultBrowserState;
import java.util.concurrent.TimeUnit;
/**
* A utility class providing information regarding external states of the system to facilitate
* testing and interacting with external states by {@link SharedPreferencesManager},
* {@link PackageManagerUtils} and {@link RoleManager}.
*/
public class DefaultBrowserPromoDeps {
private static final int MAX_PROMO_COUNT = 1;
private static final int MIN_TRIGGER_SESSION_COUNT = 3;
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 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"};
private static DefaultBrowserPromoDeps sInstance;
private DefaultBrowserPromoDeps() {}
public static DefaultBrowserPromoDeps getInstance() {
if (sInstance == null) sInstance = new DefaultBrowserPromoDeps();
return sInstance;
}
boolean isFeatureEnabled() {
return ChromeFeatureList.isEnabled(ChromeFeatureList.ANDROID_DEFAULT_BROWSER_PROMO)
&& !CommandLine.getInstance().hasSwitch(
ChromeSwitches.DISABLE_DEFAULT_BROWSER_PROMO);
}
int getPromoCount() {
return SharedPreferencesManager.getInstance().readInt(
ChromePreferenceKeys.DEFAULT_BROWSER_PROMO_PROMOED_COUNT, 0);
}
void incrementPromoCount() {
SharedPreferencesManager.getInstance().incrementInt(
ChromePreferenceKeys.DEFAULT_BROWSER_PROMO_PROMOED_COUNT);
}
int getMaxPromoCount() {
return ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
ChromeFeatureList.ANDROID_DEFAULT_BROWSER_PROMO, PROMO_COUNT_PARAM,
MAX_PROMO_COUNT);
}
int getSessionCount() {
return SharedPreferencesManager.getInstance().readInt(
ChromePreferenceKeys.DEFAULT_BROWSER_PROMO_SESSION_COUNT, 0);
}
void recordPromoTime() {
SharedPreferencesManager.getInstance().writeInt(
ChromePreferenceKeys.DEFAULT_BROWSER_PROMO_LAST_PROMO_TIME,
(int) TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis()));
}
int getMinSessionCount() {
return ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
ChromeFeatureList.ANDROID_DEFAULT_BROWSER_PROMO, SESSION_COUNT_PARAM,
MIN_TRIGGER_SESSION_COUNT);
}
int getLastPromoInterval() {
int lastPromoTime = SharedPreferencesManager.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() {
return ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
ChromeFeatureList.ANDROID_DEFAULT_BROWSER_PROMO, PROMO_INTERVAL_PARAM, 0);
}
boolean isCurrentDefaultBrowserChrome(ResolveInfo info) {
String packageName = info.activityInfo.packageName;
for (String name : CHROME_PACKAGE_NAMES) {
if (name.equals(packageName)) return true;
}
return false;
}
@DefaultBrowserState
public int getCurrentDefaultBrowserState() {
ResolveInfo info = PackageManagerUtils.resolveDefaultWebBrowserActivity();
return getCurrentDefaultBrowserState(info);
}
@DefaultBrowserState
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;
}
boolean doesManageDefaultAppsSettingsActivityExist() {
if (getSDKInt() < Build.VERSION_CODES.N) return false;
ResolveInfo info = PackageManagerUtils.resolveActivity(
new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS), 0);
return info != null && info.match != 0;
}
boolean isChromeStable() {
return ContextUtils.getApplicationContext().getPackageName().equals(
CHROME_STABLE_PACKAGE_NAME);
}
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;
}
int getSDKInt() {
return Build.VERSION.SDK_INT;
}
int promoActionOnP() {
String promoOnP = ChromeFeatureList.getFieldTrialParamByFeature(
ChromeFeatureList.ANDROID_DEFAULT_BROWSER_PROMO, P_NO_DEFAULT_PROMO_STRATEGY);
if (TextUtils.equals(promoOnP, "disabled")) {
return DefaultBrowserPromoAction.NO_ACTION;
} else if (TextUtils.equals(promoOnP, "system_settings")) {
return DefaultBrowserPromoAction.SYSTEM_SETTINGS;
} else {
return DefaultBrowserPromoAction.DISAMBIGUATION_SHEET;
}
}
@SuppressLint("NewApi")
boolean isRoleAvailable(Activity activity) {
if (getSDKInt() < Build.VERSION_CODES.Q) {
return false;
}
RoleManager roleManager = (RoleManager) activity.getSystemService(Context.ROLE_SERVICE);
boolean isRoleAvailable = roleManager.isRoleAvailable(RoleManager.ROLE_BROWSER);
boolean isRoleHeld = roleManager.isRoleHeld(RoleManager.ROLE_BROWSER);
return isRoleAvailable && !isRoleHeld;
}
}
......@@ -10,9 +10,7 @@ import android.app.role.RoleManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.text.TextUtils;
import androidx.annotation.VisibleForTesting;
......@@ -47,62 +45,24 @@ public class DefaultBrowserPromoManager implements PauseResumeWithNativeObserver
* @param activity Activity to show promo dialogs.
* @param dispatcher The {@link ActivityLifecycleDispatcher} of the current activity.
* @param windowAndroid The {@link WindowAndroid} for sending an intent.
* @return A {@link DefaultBrowserPromoManager} displaying dialogs based on android version and
* current default browser state in system.
* @param currentState The current {@link DefaultBrowserState} in the system.
*/
public static DefaultBrowserPromoManager create(Activity activity,
ActivityLifecycleDispatcher dispatcher, WindowAndroid windowAndroid) {
return new DefaultBrowserPromoManager(activity, dispatcher, windowAndroid);
}
private DefaultBrowserPromoManager(Activity activity, ActivityLifecycleDispatcher dispatcher,
WindowAndroid windowAndroid) {
public DefaultBrowserPromoManager(Activity activity, ActivityLifecycleDispatcher dispatcher,
WindowAndroid windowAndroid, @DefaultBrowserState int currentState) {
mActivity = activity;
mDispatcher = dispatcher;
mWindowAndroid = windowAndroid;
}
/**
* @param state The current {@link DefaultBrowserPromoUtils.DefaultBrowserState} in system.
*/
public void promo(@DefaultBrowserPromoUtils.DefaultBrowserState int state) {
promoInternal(state, Build.VERSION.SDK_INT);
}
private void promoInternal(
@DefaultBrowserPromoUtils.DefaultBrowserState int state, int sdkInt) {
mCurrentState = state;
if (sdkInt >= Build.VERSION_CODES.Q) {
promoByRoleManager();
} else if (state == DefaultBrowserPromoUtils.DefaultBrowserState.NO_DEFAULT) {
String promoOnP = ChromeFeatureList.getFieldTrialParamByFeature(
ChromeFeatureList.ANDROID_DEFAULT_BROWSER_PROMO, P_NO_DEFAULT_PROMO_STRATEGY);
if (TextUtils.equals(promoOnP, "system_settings")) {
promoBySystemSettings();
} else {
promoByDisambiguationSheet();
}
} else if (sdkInt >= Build.VERSION_CODES.N) {
promoBySystemSettings();
} else {
destroy();
}
mCurrentState = currentState;
}
@SuppressLint({"WrongConstant", "NewApi"})
private void promoByRoleManager() {
void promoByRoleManager() {
mPromoStyle = DialogStyle.ROLE_MANAGER;
boolean shouldSkipPrimer = ChromeFeatureList.getFieldTrialParamByFeatureAsBoolean(
ChromeFeatureList.ANDROID_DEFAULT_BROWSER_PROMO, SKIP_PRIMER_PARAM, false);
Runnable onOK = () -> {
RoleManager roleManager =
(RoleManager) mActivity.getSystemService(Context.ROLE_SERVICE);
boolean isRoleAvailable = roleManager.isRoleAvailable(RoleManager.ROLE_BROWSER);
boolean isRoleHeld = roleManager.isRoleHeld(RoleManager.ROLE_BROWSER);
// TODO(crbug.com/1090103): check the condition before deciding
// to show promo and remove the assertion.
assert isRoleAvailable && !isRoleHeld;
DefaultBrowserPromoMetrics.recordRoleManagerShow(mCurrentState);
if (!shouldSkipPrimer) {
......@@ -112,8 +72,8 @@ public class DefaultBrowserPromoManager implements PauseResumeWithNativeObserver
Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_BROWSER);
mWindowAndroid.showCancelableIntent(intent, (window, resultCode, data) -> {
DefaultBrowserPromoMetrics.recordOutcome(
mCurrentState, DefaultBrowserPromoUtils.getCurrentDefaultBrowserState());
DefaultBrowserPromoMetrics.recordOutcome(mCurrentState,
DefaultBrowserPromoDeps.getInstance().getCurrentDefaultBrowserState());
}, null);
destroy();
};
......@@ -124,7 +84,7 @@ public class DefaultBrowserPromoManager implements PauseResumeWithNativeObserver
}
}
private void promoBySystemSettings() {
void promoBySystemSettings() {
mPromoStyle = DialogStyle.SYSTEM_SETTINGS;
showDialog(DefaultBrowserPromoDialog.DialogStyle.SYSTEM_SETTINGS, () -> {
// Users are leaving Chrome, so the app may be shut down or killed in the background.
......@@ -141,7 +101,7 @@ public class DefaultBrowserPromoManager implements PauseResumeWithNativeObserver
});
}
private void promoByDisambiguationSheet() {
void promoByDisambiguationSheet() {
mPromoStyle = DialogStyle.DISAMBIGUATION_SHEET;
showDialog(DialogStyle.DISAMBIGUATION_SHEET, () -> {
DefaultBrowserPromoMetrics.recordUiDismissalReason(
......@@ -181,8 +141,8 @@ public class DefaultBrowserPromoManager implements PauseResumeWithNativeObserver
// or role manager dialog is shown, leading to no metrics recording.
if (mPromoStyle == null) return;
if (mPromoStyle == DialogStyle.DISAMBIGUATION_SHEET) {
DefaultBrowserPromoMetrics.recordOutcome(
mCurrentState, DefaultBrowserPromoUtils.getCurrentDefaultBrowserState());
DefaultBrowserPromoMetrics.recordOutcome(mCurrentState,
DefaultBrowserPromoDeps.getInstance().getCurrentDefaultBrowserState());
destroy();
} else if (mPromoStyle == DialogStyle.SYSTEM_SETTINGS) {
// Result may also be confirmed on start up of chrome tabbed activity.
......@@ -204,9 +164,4 @@ public class DefaultBrowserPromoManager implements PauseResumeWithNativeObserver
DefaultBrowserPromoDialog getDialogForTesting() {
return mDialog;
}
@VisibleForTesting
void promoForTesting(@DefaultBrowserPromoUtils.DefaultBrowserState int state, int sdkInt) {
promoInternal(state, sdkInt);
}
}
......@@ -11,7 +11,6 @@ import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import android.app.Activity;
import android.os.Build;
import androidx.test.filters.MediumTest;
......@@ -26,6 +25,7 @@ import org.chromium.chrome.R;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.LifecycleObserver;
import org.chromium.chrome.browser.ui.default_browser_promo.DefaultBrowserPromoUtils.DefaultBrowserState;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.components.browser_ui.widget.PromoDialog;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
......@@ -50,7 +50,7 @@ public class DefaultBrowserPromoManagerTest extends DummyUiActivityTestCase {
super.setUpTest();
mActivity = getActivity();
mWindowAndroid = TestThreadUtils.runOnUiThreadBlocking(() -> new WindowAndroid(mActivity));
mManager = DefaultBrowserPromoManager.create(mActivity, new ActivityLifecycleDispatcher() {
mManager = new DefaultBrowserPromoManager(mActivity, new ActivityLifecycleDispatcher() {
@Override
public void register(LifecycleObserver observer) {}
......@@ -66,7 +66,7 @@ public class DefaultBrowserPromoManagerTest extends DummyUiActivityTestCase {
public boolean isNativeInitializationFinished() {
return false;
}
}, mWindowAndroid);
}, mWindowAndroid, DefaultBrowserState.NO_DEFAULT);
mAppName = BuildInfo.getInstance().hostPackageLabel;
// Enabling feature can assign a default value to the fieldtrial param.
FeatureList.setTestFeatures(Collections.EMPTY_MAP);
......@@ -81,10 +81,7 @@ public class DefaultBrowserPromoManagerTest extends DummyUiActivityTestCase {
@Test
@MediumTest
public void testPromoByRoleManager() {
TestThreadUtils.runOnUiThreadBlocking(() -> {
mManager.promoForTesting(
DefaultBrowserPromoUtils.DefaultBrowserState.NO_DEFAULT, Build.VERSION_CODES.Q);
});
TestThreadUtils.runOnUiThreadBlocking(() -> { mManager.promoByRoleManager(); });
DefaultBrowserPromoDialog dialog = mManager.getDialogForTesting();
Assert.assertEquals("Dialog should be of role manager style on Q+",
dialog.getDialogStyleForTesting(),
......@@ -110,25 +107,10 @@ public class DefaultBrowserPromoManagerTest extends DummyUiActivityTestCase {
checkDialogVisibility();
}
@Test
@MediumTest
public void testPromoBySystemSettingsOnL() {
TestThreadUtils.runOnUiThreadBlocking(() -> {
mManager.promoForTesting(DefaultBrowserPromoUtils.DefaultBrowserState.OTHER_DEFAULT,
Build.VERSION_CODES.LOLLIPOP);
});
DefaultBrowserPromoDialog dialog = mManager.getDialogForTesting();
Assert.assertNull("Dialog of system settings style should not be displayed on L", dialog);
}
@Test
@MediumTest
public void testPromoBySystemSettingsOnP() {
TestThreadUtils.runOnUiThreadBlocking(() -> {
mManager.promoForTesting(DefaultBrowserPromoUtils.DefaultBrowserState.OTHER_DEFAULT,
Build.VERSION_CODES.P);
});
TestThreadUtils.runOnUiThreadBlocking(() -> { mManager.promoBySystemSettings(); });
DefaultBrowserPromoDialog dialog = mManager.getDialogForTesting();
Assert.assertEquals(
"Dialog should be of system settings style on P-, when there is another default in system",
......@@ -158,10 +140,7 @@ public class DefaultBrowserPromoManagerTest extends DummyUiActivityTestCase {
@Test
@MediumTest
public void testPromoByDisambiguationSheet() {
TestThreadUtils.runOnUiThreadBlocking(() -> {
mManager.promoForTesting(
DefaultBrowserPromoUtils.DefaultBrowserState.NO_DEFAULT, Build.VERSION_CODES.P);
});
TestThreadUtils.runOnUiThreadBlocking(() -> { mManager.promoByDisambiguationSheet(); });
DefaultBrowserPromoDialog dialog = mManager.getDialogForTesting();
Assert.assertEquals(
"Dialog should be of disambiguation sheet style on P-, when there is no default in system",
......
......@@ -40,7 +40,7 @@ public class DefaultBrowserPromoUtilsTest {
public void testGetCurrentDefaultStateForNoDefault() {
Assert.assertEquals("Should be no default when resolve info matches no browser.",
DefaultBrowserState.NO_DEFAULT,
DefaultBrowserPromoUtils.getCurrentDefaultBrowserState(
DefaultBrowserPromoDeps.getInstance().getCurrentDefaultBrowserState(
createResolveInfo("android", 0)));
}
......@@ -48,7 +48,7 @@ public class DefaultBrowserPromoUtilsTest {
public void testGetCurrentDefaultStateForOtherDefault() {
Assert.assertEquals("Should be other default when resolve info matches another browser.",
DefaultBrowserPromoUtils.DefaultBrowserState.OTHER_DEFAULT,
DefaultBrowserPromoUtils.getCurrentDefaultBrowserState(
DefaultBrowserPromoDeps.getInstance().getCurrentDefaultBrowserState(
createResolveInfo("android", 1)));
}
......@@ -57,8 +57,9 @@ public class DefaultBrowserPromoUtilsTest {
Assert.assertEquals(
"Should be chrome default when resolve info matches current package name.",
DefaultBrowserPromoUtils.DefaultBrowserState.CHROME_DEFAULT,
DefaultBrowserPromoUtils.getCurrentDefaultBrowserState(createResolveInfo(
ContextUtils.getApplicationContext().getPackageName(), 1)));
DefaultBrowserPromoDeps.getInstance().getCurrentDefaultBrowserState(
createResolveInfo(
ContextUtils.getApplicationContext().getPackageName(), 1)));
}
@Test
......@@ -67,17 +68,18 @@ public class DefaultBrowserPromoUtilsTest {
ShadowPackageManager packageManager =
Shadows.shadowOf(RuntimeEnvironment.application.getPackageManager());
DefaultBrowserPromoDeps deps = DefaultBrowserPromoDeps.getInstance();
infoList.add(createResolveInfo(DefaultBrowserPromoUtils.CHROME_STABLE_PACKAGE_NAME, 1));
packageManager.addResolveInfoForIntent(
PackageManagerUtils.getQueryInstalledBrowsersIntent(), infoList);
Assert.assertFalse("Chrome stable should not be counted as a pre-stable channel",
DefaultBrowserPromoUtils.isChromePreStableInstalled());
deps.isChromePreStableInstalled());
infoList.add(createResolveInfo("com.android.chrome.123", 1));
packageManager.addResolveInfoForIntent(
PackageManagerUtils.getQueryInstalledBrowsersIntent(), infoList);
Assert.assertFalse("A random package should not be counted as a pre-stable channel",
DefaultBrowserPromoUtils.isChromePreStableInstalled());
deps.isChromePreStableInstalled());
for (String name : DefaultBrowserPromoUtils.CHROME_PACKAGE_NAMES) {
if (name.equals(DefaultBrowserPromoUtils.CHROME_STABLE_PACKAGE_NAME)) continue;
......@@ -86,20 +88,20 @@ public class DefaultBrowserPromoUtilsTest {
packageManager.addResolveInfoForIntent(
PackageManagerUtils.getQueryInstalledBrowsersIntent(), list);
Assert.assertTrue(name + " should be considered as a pre-stable channel",
DefaultBrowserPromoUtils.isChromePreStableInstalled());
deps.isChromePreStableInstalled());
}
}
@Test
public void testIsCurrentDefaultBrowserChrome() {
DefaultBrowserPromoDeps deps = DefaultBrowserPromoDeps.getInstance();
for (String name : DefaultBrowserPromoUtils.CHROME_PACKAGE_NAMES) {
Assert.assertTrue(name + " should be considered as a chrome channel",
DefaultBrowserPromoUtils.isCurrentDefaultBrowserChrome(
createResolveInfo(name, 1)));
deps.isCurrentDefaultBrowserChrome(createResolveInfo(name, 1)));
}
Assert.assertFalse("A random string should not be considered as a chrome channel",
DefaultBrowserPromoUtils.isCurrentDefaultBrowserChrome(
deps.isCurrentDefaultBrowserChrome(
createResolveInfo("com.android.chrome.random.string", 1)));
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment