| // 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.content.Context; |
| import android.content.Intent; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.text.TextUtils; |
| |
| import androidx.annotation.VisibleForTesting; |
| |
| import org.chromium.base.ApiCompatibilityUtils; |
| import org.chromium.base.CommandLine; |
| import org.chromium.base.Log; |
| import org.chromium.chrome.browser.ChromeSwitches; |
| import org.chromium.chrome.browser.IntentHandler; |
| import org.chromium.chrome.browser.flags.FeatureUtilities; |
| import org.chromium.chrome.browser.locale.LocaleManager; |
| import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings; |
| import org.chromium.chrome.browser.services.AndroidEduAndChildAccountHelper; |
| import org.chromium.chrome.browser.settings.privacy.PrivacyPreferencesManager; |
| import org.chromium.chrome.browser.signin.IdentityServicesProvider; |
| import org.chromium.chrome.browser.signin.SigninManager; |
| import org.chromium.chrome.browser.util.IntentUtils; |
| import org.chromium.chrome.browser.util.UrlConstants; |
| import org.chromium.chrome.browser.vr.VrModuleProvider; |
| import org.chromium.components.signin.AccountManagerFacade; |
| import org.chromium.components.signin.ChildAccountStatus; |
| import org.chromium.components.signin.ChromeSigninController; |
| |
| import java.util.List; |
| |
| /** |
| * A helper to determine what should be the sequence of First Run Experience screens, and whether |
| * it should be run. |
| * |
| * Usage: |
| * new FirstRunFlowSequencer(activity, launcherProvidedProperties) { |
| * override onFlowIsKnown |
| * }.start(); |
| */ |
| public abstract class FirstRunFlowSequencer { |
| private static final String TAG = "firstrun"; |
| |
| private final Activity mActivity; |
| |
| // The following are initialized via initializeSharedState(). |
| private boolean mIsAndroidEduDevice; |
| private @ChildAccountStatus.Status int mChildAccountStatus; |
| private List<Account> mGoogleAccounts; |
| private boolean mForceEduSignIn; |
| |
| /** |
| * 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) { |
| mActivity = activity; |
| } |
| |
| /** |
| * 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()) { |
| onFlowIsKnown(null); |
| return; |
| } |
| |
| new AndroidEduAndChildAccountHelper() { |
| @Override |
| public void onParametersReady() { |
| initializeSharedState(isAndroidEduDevice(), getChildAccountStatus()); |
| processFreEnvironmentPreNative(); |
| } |
| }.start(); |
| } |
| |
| @VisibleForTesting |
| protected boolean isFirstRunFlowComplete() { |
| return FirstRunStatus.getFirstRunFlowComplete(); |
| } |
| |
| @VisibleForTesting |
| protected boolean isSignedIn() { |
| return ChromeSigninController.get().isSignedIn(); |
| } |
| |
| @VisibleForTesting |
| protected boolean isSyncAllowed() { |
| SigninManager signinManager = IdentityServicesProvider.getSigninManager(); |
| return FirstRunUtils.canAllowSync() && !signinManager.isSigninDisabledByPolicy() |
| && signinManager.isSigninSupported(); |
| } |
| |
| @VisibleForTesting |
| protected List<Account> getGoogleAccounts() { |
| return AccountManagerFacade.get().tryGetGoogleAccounts(); |
| } |
| |
| @VisibleForTesting |
| protected boolean hasAnyUserSeenToS() { |
| return ToSAckedReceiver.checkAnyUserHasSeenToS(); |
| } |
| |
| @VisibleForTesting |
| protected boolean shouldSkipFirstUseHints() { |
| return ApiCompatibilityUtils.shouldSkipFirstUseHints(mActivity.getContentResolver()); |
| } |
| |
| @VisibleForTesting |
| protected boolean isFirstRunEulaAccepted() { |
| return FirstRunUtils.isFirstRunEulaAccepted(); |
| } |
| |
| protected boolean shouldShowDataReductionPage() { |
| return !DataReductionProxySettings.getInstance().isDataReductionProxyManaged() |
| && DataReductionProxySettings.getInstance().isDataReductionProxyFREPromoAllowed(); |
| } |
| |
| @VisibleForTesting |
| protected boolean shouldShowSearchEnginePage() { |
| @LocaleManager.SearchEnginePromoType |
| int searchPromoType = LocaleManager.getInstance().getSearchEnginePromoShowType(); |
| return searchPromoType == LocaleManager.SearchEnginePromoType.SHOW_NEW |
| || searchPromoType == LocaleManager.SearchEnginePromoType.SHOW_EXISTING; |
| } |
| |
| @VisibleForTesting |
| protected void setDefaultMetricsAndCrashReporting() { |
| PrivacyPreferencesManager.getInstance().setUsageAndCrashReporting( |
| FirstRunActivity.DEFAULT_METRICS_AND_CRASH_REPORTING); |
| } |
| |
| @VisibleForTesting |
| protected void setFirstRunFlowSignInComplete() { |
| FirstRunSignInProcessor.setFirstRunFlowSignInComplete(true); |
| } |
| |
| void initializeSharedState( |
| boolean isAndroidEduDevice, @ChildAccountStatus.Status int childAccountStatus) { |
| mIsAndroidEduDevice = isAndroidEduDevice; |
| mChildAccountStatus = childAccountStatus; |
| mGoogleAccounts = getGoogleAccounts(); |
| // EDU devices should always have exactly 1 google account, which will be automatically |
| // signed-in. All FRE screens are skipped in this case. |
| mForceEduSignIn = mIsAndroidEduDevice && mGoogleAccounts.size() == 1 && !isSignedIn(); |
| } |
| |
| void processFreEnvironmentPreNative() { |
| if (isFirstRunFlowComplete()) { |
| assert isFirstRunEulaAccepted(); |
| // We do not need any interactive FRE. |
| onFlowIsKnown(null); |
| return; |
| } |
| |
| Bundle freProperties = new Bundle(); |
| |
| // In the full FRE we always show the Welcome page, except on EDU devices. |
| boolean showWelcomePage = !mForceEduSignIn; |
| freProperties.putBoolean(FirstRunActivity.SHOW_WELCOME_PAGE, showWelcomePage); |
| freProperties.putInt(SigninFirstRunFragment.CHILD_ACCOUNT_STATUS, mChildAccountStatus); |
| |
| // 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(); |
| |
| onFlowIsKnown(freProperties); |
| if (ChildAccountStatus.isChild(mChildAccountStatus) || mForceEduSignIn) { |
| // Child and Edu forced signins are processed independently. |
| setFirstRunFlowSignInComplete(); |
| } |
| } |
| |
| /** |
| * Called onNativeInitialized() a given flow as completed. |
| * @param freProperties Resulting FRE properties bundle. |
| */ |
| public void onNativeInitialized(Bundle freProperties) { |
| // 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. |
| boolean offerSignInOk = isSyncAllowed() && !isSignedIn() && !mForceEduSignIn |
| && (!shouldSkipFirstUseHints() || !mGoogleAccounts.isEmpty()); |
| freProperties.putBoolean(FirstRunActivity.SHOW_SIGNIN_PAGE, offerSignInOk); |
| if (mForceEduSignIn || ChildAccountStatus.isChild(mChildAccountStatus)) { |
| // If the device is an Android EDU device or has a child account, there should be |
| // exactly account on the device. Force sign-in in to that account. |
| freProperties.putString( |
| SigninFirstRunFragment.FORCE_SIGNIN_ACCOUNT_TO, mGoogleAccounts.get(0).name); |
| } |
| |
| freProperties.putBoolean( |
| FirstRunActivity.SHOW_DATA_REDUCTION_PAGE, shouldShowDataReductionPage()); |
| freProperties.putBoolean( |
| FirstRunActivity.SHOW_SEARCH_ENGINE_PAGE, shouldShowSearchEnginePage()); |
| |
| // Cache the flag for the bottom toolbar. If the flag is not cached here, Users, who are in |
| // bottom toolbar experiment group, will see toolbar on the top in first run, and then |
| // toolbar will appear to the bottom on the second run. |
| FeatureUtilities.cacheBottomToolbarEnabled(); |
| } |
| |
| /** |
| * Marks a given flow as completed. |
| * @param signInAccountName The account name for the pending sign-in request. (Or null) |
| * @param showSignInSettings Whether the user selected to see the settings once signed in. |
| */ |
| public static void markFlowAsCompleted(String signInAccountName, boolean showSignInSettings) { |
| // 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 (!FirstRunUtils.isFirstRunEulaAccepted()) { |
| FirstRunUtils.setEulaAccepted(); |
| } |
| |
| // Mark the FRE flow as complete and set the sign-in flow preferences if necessary. |
| FirstRunSignInProcessor.finalizeFirstRunFlowState(signInAccountName, showSignInSettings); |
| } |
| |
| /** |
| * Checks if the First Run needs to be launched. |
| * @param fromIntent The intent that was used to launch Chrome. |
| * @param preferLightweightFre Whether to prefer the Lightweight First Run Experience. |
| * @return Whether the First Run Experience needs to be launched. |
| */ |
| public static boolean checkIfFirstRunIsNecessary( |
| Intent fromIntent, boolean preferLightweightFre) { |
| // 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()) { |
| return false; |
| } |
| |
| // 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()) return false; |
| |
| if (FirstRunStatus.getFirstRunFlowComplete()) { |
| // Promo pages are removed, so there is nothing else to show in FRE. |
| return false; |
| } |
| return !preferLightweightFre |
| || (!FirstRunStatus.shouldSkipWelcomePage() |
| && !FirstRunStatus.getLightweightFirstRunFlowComplete()); |
| } |
| |
| /** |
| * Tries to launch the First Run Experience. If the Activity was launched with the wrong Intent |
| * flags, we first relaunch it to make sure it runs in its own task, then trigger First Run. |
| * |
| * @param caller Activity instance that is checking if first run is necessary. |
| * @param fromIntent Intent used to launch the caller. |
| * @param requiresBroadcast Whether or not the Intent triggers a BroadcastReceiver. |
| * @param preferLightweightFre Whether to prefer the Lightweight First Run Experience. |
| * @return Whether startup must be blocked (e.g. via Activity#finish or dropping the Intent). |
| */ |
| public static boolean launch(Context caller, Intent fromIntent, boolean requiresBroadcast, |
| boolean preferLightweightFre) { |
| // Check if the user needs to go through First Run at all. |
| if (!checkIfFirstRunIsNecessary(fromIntent, preferLightweightFre)) return false; |
| |
| String intentUrl = IntentHandler.getUrlFromIntent(fromIntent); |
| Uri uri = intentUrl != null ? Uri.parse(intentUrl) : null; |
| if (uri != null && UrlConstants.CONTENT_SCHEME.equals(uri.getScheme())) { |
| caller.grantUriPermission( |
| caller.getPackageName(), uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); |
| } |
| |
| Log.d(TAG, "Redirecting user through FRE."); |
| if ((fromIntent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { |
| FreIntentCreator intentCreator = new FreIntentCreator(); |
| Intent freIntent = intentCreator.create( |
| caller, fromIntent, requiresBroadcast, preferLightweightFre); |
| |
| if (!(caller instanceof Activity)) freIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| boolean isVrIntent = VrModuleProvider.getIntentDelegate().isVrIntent(fromIntent); |
| if (isVrIntent) { |
| freIntent = |
| VrModuleProvider.getIntentDelegate().setupVrFreIntent(caller, freIntent); |
| // We cannot access Chrome right now, e.g. because the VR module is not installed. |
| if (freIntent == null) return true; |
| } |
| IntentUtils.safeStartActivity(caller, freIntent); |
| } else { |
| // First Run requires that the Intent contains NEW_TASK so that it doesn't sit on top |
| // of something else. |
| Intent newIntent = new Intent(fromIntent); |
| newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| IntentUtils.safeStartActivity(caller, newIntent); |
| } |
| return true; |
| } |
| } |