blob: a16f26568bddfd2f65b81c0a870fe465e1df7532 [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.annotation.SuppressLint;
import android.app.Activity;
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 androidx.annotation.VisibleForTesting;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.Destroyable;
import org.chromium.chrome.browser.lifecycle.PauseResumeWithNativeObserver;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
import org.chromium.chrome.browser.ui.default_browser_promo.DefaultBrowserPromoDialog.DialogStyle;
import org.chromium.chrome.browser.ui.default_browser_promo.DefaultBrowserPromoMetrics.UIDismissalReason;
import org.chromium.chrome.browser.ui.default_browser_promo.DefaultBrowserPromoUtils.DefaultBrowserState;
import org.chromium.ui.base.WindowAndroid;
/**
* Manage all types of default browser promo dialogs and listen to the activity state change to
* trigger dialogs.
*/
public class DefaultBrowserPromoManager implements PauseResumeWithNativeObserver, Destroyable {
private static final String SKIP_PRIMER_PARAM = "skip_primer";
private static final String DISABLE_DISAMBIGUATION_SHEET = "disable_disambiguation_sheet";
private static final String DISAMBIGUATION_PROMO_URL = "disambiguation_promo_url";
private final Activity mActivity;
private DefaultBrowserPromoDialog mDialog;
private @DefaultBrowserState int mCurrentState;
private @DialogStyle Integer mPromoStyle;
private ActivityLifecycleDispatcher mDispatcher;
private WindowAndroid mWindowAndroid;
/**
* @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.
*/
public static DefaultBrowserPromoManager create(Activity activity,
ActivityLifecycleDispatcher dispatcher, WindowAndroid windowAndroid) {
return new DefaultBrowserPromoManager(activity, dispatcher, windowAndroid);
}
private DefaultBrowserPromoManager(Activity activity, ActivityLifecycleDispatcher dispatcher,
WindowAndroid windowAndroid) {
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) {
boolean disabled = ChromeFeatureList.getFieldTrialParamByFeatureAsBoolean(
ChromeFeatureList.ANDROID_DEFAULT_BROWSER_PROMO, DISABLE_DISAMBIGUATION_SHEET,
false);
if (disabled) {
destroy();
} else {
promoByDisambiguationSheet();
}
} else if (sdkInt >= Build.VERSION_CODES.M) {
promoBySystemSettings();
} else {
destroy();
}
}
@SuppressLint({"WrongConstant", "NewApi"})
private 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) {
DefaultBrowserPromoMetrics.recordUiDismissalReason(
mCurrentState, UIDismissalReason.CHANGE_DEFAULT);
}
Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_BROWSER);
mWindowAndroid.showCancelableIntent(intent, (window, resultCode, data) -> {
DefaultBrowserPromoMetrics.recordOutcome(
mCurrentState, DefaultBrowserPromoUtils.getCurrentDefaultBrowserState());
}, null);
destroy();
};
if (shouldSkipPrimer) {
onOK.run();
} else {
showDialog(DefaultBrowserPromoDialog.DialogStyle.ROLE_MANAGER, onOK);
}
}
private 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.
// Save state to pref for checking result on the next app start up.
SharedPreferencesManager.getInstance().writeBoolean(
ChromePreferenceKeys.DEFAULT_BROWSER_PROMO_PROMOED_BY_SYSTEM_SETTINGS, true);
SharedPreferencesManager.getInstance().writeInt(
ChromePreferenceKeys.DEFAULT_BROWSER_PROMO_LAST_DEFAULT_STATE, mCurrentState);
DefaultBrowserPromoMetrics.recordUiDismissalReason(
mCurrentState, UIDismissalReason.CHANGE_DEFAULT);
Intent intent = new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS);
mActivity.startActivity(intent);
});
}
private void promoByDisambiguationSheet() {
mPromoStyle = DialogStyle.DISAMBIGUATION_SHEET;
showDialog(DialogStyle.DISAMBIGUATION_SHEET, () -> {
DefaultBrowserPromoMetrics.recordUiDismissalReason(
mCurrentState, UIDismissalReason.CHANGE_DEFAULT);
Intent intent = new Intent();
String url = ChromeFeatureList.getFieldTrialParamByFeature(
ChromeFeatureList.ANDROID_DEFAULT_BROWSER_PROMO, DISAMBIGUATION_PROMO_URL);
if (url != null && !url.isEmpty()) {
intent.setAction(Intent.ACTION_VIEW);
intent.addCategory(Intent.CATEGORY_BROWSABLE);
intent.setData(Uri.parse(url));
} else {
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_APP_BROWSER);
}
intent.putExtra(DefaultBrowserPromoUtils.DISAMBIGUATION_SHEET_PROMOED_KEY, true);
mActivity.startActivity(intent);
});
}
private void showDialog(@DialogStyle int style, Runnable okCallback) {
mDialog = DefaultBrowserPromoDialog.createDialog(mActivity, style, okCallback, () -> {
DefaultBrowserPromoMetrics.recordUiDismissalReason(
mCurrentState, UIDismissalReason.NO_THANKS);
destroy();
});
DefaultBrowserPromoMetrics.recordDialogShow(mCurrentState);
mDispatcher.register(this);
mDialog.show();
}
@Override
public void onResumeWithNative() {
// TODO(crbug.com/1090103): Edge case: user might shut down chrome when disambiguation sheet
// 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());
destroy();
} else if (mPromoStyle == DialogStyle.SYSTEM_SETTINGS) {
// Result may also be confirmed on start up of chrome tabbed activity.
DefaultBrowserPromoUtils.maybeRecordOutcomeOnStart();
destroy();
}
}
@Override
public void onPauseWithNative() {}
@Override
public void destroy() {
mPromoStyle = null;
mDispatcher.unregister(this);
}
@VisibleForTesting
DefaultBrowserPromoDialog getDialogForTesting() {
return mDialog;
}
@VisibleForTesting
void promoForTesting(@DefaultBrowserPromoUtils.DefaultBrowserState int state, int sdkInt) {
promoInternal(state, sdkInt);
}
}