// Copyright 2015 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.firstrun;
import android.accounts.Account;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.CommandLine;
import org.chromium.base.FieldTrialList;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
import org.chromium.chrome.browser.preferences.PrefServiceBridge;
import org.chromium.chrome.browser.preferences.privacy.PrivacyPreferencesManager;
import org.chromium.chrome.browser.services.AndroidEduAndChildAccountHelper;
import org.chromium.chrome.browser.signin.SigninManager;
import org.chromium.chrome.browser.util.FeatureUtilities;
import org.chromium.components.signin.AccountManagerHelper;
import org.chromium.components.signin.ChromeSigninController;
/**
* A helper to determine what should be the sequence of First Run Experience screens.
* Usage:
* new FirstRunFlowSequencer(activity, launcherProvidedProperties) {
* override onFlowIsKnown
* }.start();
*/
public abstract class FirstRunFlowSequencer {
private static final int FIRST_RUN_EXPERIENCE_REQUEST_CODE = 101;
private final Activity mActivity;
private final Bundle mLaunchProperties;
/**
* Callback that is called once the flow is determined.
* If the properties is null, the First Run experience needs to finish and
* restart the original intent if necessary.
* @param freProperties Properties to be used in the First Run activity, or null.
*/
public abstract void onFlowIsKnown(Bundle freProperties);
public FirstRunFlowSequencer(Activity activity, Bundle launcherProvidedProperties) {
mActivity = activity;
mLaunchProperties = launcherProvidedProperties;
}
/**
* Starts determining parameters for the First Run.
* Once finished, calls onFlowIsKnown().
*/
public void start() {
if (CommandLine.getInstance().hasSwitch(ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE)
|| ApiCompatibilityUtils.isDemoUser(mActivity)) {
onFlowIsKnown(null);
return;
}
if (!mLaunchProperties.getBoolean(FirstRunActivity.EXTRA_USE_FRE_FLOW_SEQUENCER)) {
onFlowIsKnown(mLaunchProperties);
return;
}
new AndroidEduAndChildAccountHelper() {
@Override
public void onParametersReady() {
processFreEnvironment(isAndroidEduDevice(), hasChildAccount());
}
}.start(mActivity.getApplicationContext());
}
@VisibleForTesting
protected boolean isFirstRunFlowComplete() {
return FirstRunStatus.getFirstRunFlowComplete(mActivity);
}
@VisibleForTesting
protected boolean isSignedIn() {
return ChromeSigninController.get(mActivity).isSignedIn();
}
@VisibleForTesting
protected boolean isSyncAllowed() {
SigninManager signinManager = SigninManager.get(mActivity.getApplicationContext());
return FeatureUtilities.canAllowSync(mActivity) && !signinManager.isSigninDisabledByPolicy()
&& signinManager.isSigninSupported();
}
@VisibleForTesting
protected Account[] getGoogleAccounts() {
return AccountManagerHelper.get(mActivity).getGoogleAccounts();
}
@VisibleForTesting
protected boolean hasAnyUserSeenToS() {
return ToSAckedReceiver.checkAnyUserHasSeenToS(mActivity);
}
@VisibleForTesting
protected boolean shouldSkipFirstUseHints() {
return ApiCompatibilityUtils.shouldSkipFirstUseHints(mActivity.getContentResolver());
}
@VisibleForTesting
protected boolean isFirstRunEulaAccepted() {
return PrefServiceBridge.getInstance().isFirstRunEulaAccepted();
}
protected boolean shouldShowDataReductionPage() {
return !DataReductionProxySettings.getInstance().isDataReductionProxyManaged()
&& FieldTrialList.findFullName("DataReductionProxyFREPromo").startsWith("Enabled");
}
@VisibleForTesting
protected void setDefaultMetricsAndCrashReporting() {
PrivacyPreferencesManager.getInstance().setUsageAndCrashReporting(
FirstRunActivity.DEFAULT_METRICS_AND_CRASH_REPORTING);
}
@VisibleForTesting
protected void setFirstRunFlowSignInComplete() {
FirstRunSignInProcessor.setFirstRunFlowSignInComplete(
mActivity.getApplicationContext(), true);
}
void processFreEnvironment(boolean androidEduDevice, boolean hasChildAccount) {
if (isFirstRunFlowComplete()) {
assert isFirstRunEulaAccepted();
// We do not need any interactive FRE.
onFlowIsKnown(null);
return;
}
Bundle freProperties = new Bundle();
freProperties.putAll(mLaunchProperties);
freProperties.remove(FirstRunActivity.EXTRA_USE_FRE_FLOW_SEQUENCER);
Account[] googleAccounts = getGoogleAccounts();
boolean onlyOneAccount = googleAccounts.length == 1;
// EDU devices should always have exactly 1 google account, which will be automatically
// signed-in. All FRE screens are skipped in this case.
boolean forceEduSignIn = androidEduDevice && onlyOneAccount && !isSignedIn();
// In the full FRE we always show the Welcome page, except on EDU devices.
boolean showWelcomePage = !forceEduSignIn;
freProperties.putBoolean(FirstRunActivity.SHOW_WELCOME_PAGE, showWelcomePage);
// Initialize usage and crash reporting according to the default value.
// The user can explicitly enable or disable the reporting on the Welcome page.
// This is controlled by the administrator via a policy on EDU devices.
setDefaultMetricsAndCrashReporting();
// We show the sign-in page if sync is allowed, and not signed in, and this is not an EDU
// device, and
// - no "skip the first use hints" is set, or
// - "skip the first use hints" is set, but there is at least one account.
final boolean offerSignInOk = isSyncAllowed()
&& !isSignedIn()
&& !forceEduSignIn
&& (!shouldSkipFirstUseHints() || googleAccounts.length > 0);
freProperties.putBoolean(FirstRunActivity.SHOW_SIGNIN_PAGE, offerSignInOk);
if (offerSignInOk || forceEduSignIn) {
// If the user has accepted the ToS in the Setup Wizard and there is exactly
// one account, or if the device has a child account, or if the device is an
// Android EDU device and there is exactly one account, preselect the sign-in
// account and force the selection if necessary.
if ((hasAnyUserSeenToS() && onlyOneAccount) || hasChildAccount || forceEduSignIn) {
freProperties.putString(AccountFirstRunFragment.FORCE_SIGNIN_ACCOUNT_TO,
googleAccounts[0].name);
freProperties.putBoolean(AccountFirstRunFragment.PRESELECT_BUT_ALLOW_TO_CHANGE,
!forceEduSignIn && !hasChildAccount);
}
}
freProperties.putBoolean(AccountFirstRunFragment.IS_CHILD_ACCOUNT, hasChildAccount);
freProperties.putBoolean(FirstRunActivity.SHOW_DATA_REDUCTION_PAGE,
shouldShowDataReductionPage());
onFlowIsKnown(freProperties);
if (hasChildAccount || forceEduSignIn) {
// Child and Edu forced signins are processed independently.
setFirstRunFlowSignInComplete();
}
}
/**
* Marks a given flow as completed.
* @param activity An activity.
* @param data Resulting FRE properties bundle.
*/
public static void markFlowAsCompleted(Activity activity, Bundle data) {
// When the user accepts ToS in the Setup Wizard (see ToSAckedReceiver), we do not
// show the ToS page to the user because the user has already accepted one outside FRE.
if (!PrefServiceBridge.getInstance().isFirstRunEulaAccepted()) {
PrefServiceBridge.getInstance().setEulaAccepted();
}
// Mark the FRE flow as complete and set the sign-in flow preferences if necessary.
FirstRunSignInProcessor.finalizeFirstRunFlowState(activity, data);
}
/**
* Checks if the First Run needs to be launched.
* @return The intent to launch the First Run Experience if necessary, or null.
* @param context The context.
* @param fromIntent The intent that was used to launch Chrome.
* @param forLightweightFre Whether this is a check for the Lightweight First Run Experience.
*/
public static Intent checkIfFirstRunIsNecessary(
Context context, Intent fromIntent, boolean forLightweightFre) {
// If FRE is disabled (e.g. in tests), proceed directly to the intent handling.
if (CommandLine.getInstance().hasSwitch(ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE)
|| ApiCompatibilityUtils.isDemoUser(context)) {
return null;
}
// If Chrome isn't opened via the Chrome icon, and the user accepted the ToS
// in the Setup Wizard, skip any First Run Experience screens and proceed directly
// to the intent handling.
final boolean fromChromeIcon =
fromIntent != null && TextUtils.equals(fromIntent.getAction(), Intent.ACTION_MAIN);
if (!fromChromeIcon && ToSAckedReceiver.checkAnyUserHasSeenToS(context)) return null;
final boolean baseFreComplete = FirstRunStatus.getFirstRunFlowComplete(context);
if (!baseFreComplete) {
if (forLightweightFre
&& CommandLine.getInstance().hasSwitch(
ChromeSwitches.ENABLE_LIGHTWEIGHT_FIRST_RUN_EXPERIENCE)) {
if (!FirstRunStatus.shouldSkipWelcomePage(context)
&& !FirstRunStatus.getLightweightFirstRunFlowComplete(context)) {
return createLightweightFirstRunIntent(context, fromChromeIcon);
}
} else {
return createGenericFirstRunIntent(context, fromChromeIcon);
}
}
// Promo pages are removed, so there is nothing else to show in FRE.
return null;
}
private static Intent createLightweightFirstRunIntent(Context context, boolean fromChromeIcon) {
Intent intent = new Intent();
intent.setClassName(context, LightweightFirstRunActivity.class.getName());
intent.putExtra(FirstRunActivity.EXTRA_COMING_FROM_CHROME_ICON, fromChromeIcon);
intent.putExtra(FirstRunActivity.EXTRA_START_LIGHTWEIGHT_FRE, true);
return intent;
}
/**
* @return A generic intent to show the First Run Activity.
* @param context The context.
* @param fromChromeIcon Whether Chrome is opened via the Chrome icon.
*/
public static Intent createGenericFirstRunIntent(Context context, boolean fromChromeIcon) {
Intent intent = new Intent();
intent.setClassName(context, FirstRunActivity.class.getName());
intent.putExtra(FirstRunActivity.EXTRA_COMING_FROM_CHROME_ICON, fromChromeIcon);
intent.putExtra(FirstRunActivity.EXTRA_USE_FRE_FLOW_SEQUENCER, true);
return intent;
}
/**
* Adds fromIntent as a PendingIntent to the firstRunIntent. This should be used to add a
* PendingIntent that will be sent when first run is either completed or canceled.
*
* @param context The context.
* @param firstRunIntent The intent that will be used to start first run.
* @param fromIntent The intent that was used to launch Chrome.
*/
public static void addPendingIntent(Context context, Intent firstRunIntent, Intent fromIntent) {
PendingIntent pendingIntent = PendingIntent.getActivity(context,
FIRST_RUN_EXPERIENCE_REQUEST_CODE,
fromIntent,
fromIntent.getFlags());
firstRunIntent.putExtra(FirstRunActivity.EXTRA_CHROME_LAUNCH_INTENT, pendingIntent);
}
}