From: csagan5 <32685696+csagan5@users.noreply.github.com> Date: Fri, 29 Apr 2022 00:31:49 +0200 Subject: Welcome screen Allow toggling automatic updates License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html --- chrome/android/chrome_java_resources.gni | 1 + chrome/android/chrome_java_sources.gni | 2 + .../android/java/res/layout/fre_tosanduma.xml | 160 +++++++++ chrome/android/java/res/values/dimens.xml | 10 + chrome/android/java/res/values/styles.xml | 24 ++ .../firstrun/ChildAccountStatusSupplier.java | 8 - .../browser/firstrun/FirstRunActivity.java | 21 +- .../firstrun/FirstRunActivityBase.java | 6 +- .../firstrun/FirstRunFlowSequencer.java | 52 +-- .../browser/firstrun/FirstRunUtils.java | 16 +- .../firstrun/ToSAndUMAFirstRunFragment.java | 336 ++++++++++++++++++ .../firstrun/TosAndUmaFragmentView.java | 336 ++++++++++++++++++ .../strings/android_chrome_strings.grd | 24 +- 13 files changed, 910 insertions(+), 86 deletions(-) create mode 100644 chrome/android/java/res/layout/fre_tosanduma.xml create mode 100644 chrome/android/java/src/org/chromium/chrome/browser/firstrun/ToSAndUMAFirstRunFragment.java create mode 100644 chrome/android/java/src/org/chromium/chrome/browser/firstrun/TosAndUmaFragmentView.java diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni --- a/chrome/android/chrome_java_resources.gni +++ b/chrome/android/chrome_java_resources.gni @@ -523,6 +523,7 @@ chrome_java_resources = [ "java/res/layout/find_in_page.xml", "java/res/layout/find_toolbar.xml", "java/res/layout/fre_tos_privacy_disclaimer.xml", + "java/res/layout/fre_tosanduma.xml", "java/res/layout/history_clear_browsing_data_header.xml", "java/res/layout/history_item_view.xml", "java/res/layout/history_main.xml", diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni --- a/chrome/android/chrome_java_sources.gni +++ b/chrome/android/chrome_java_sources.gni @@ -650,6 +650,8 @@ chrome_java_sources = [ "java/src/org/chromium/chrome/browser/firstrun/SyncConsentFirstRunFragment.java", "java/src/org/chromium/chrome/browser/firstrun/TabbedModeFirstRunActivity.java", "java/src/org/chromium/chrome/browser/firstrun/TosDialogBehaviorSharedPrefInvalidator.java", + "java/src/org/chromium/chrome/browser/firstrun/ToSAndUMAFirstRunFragment.java", + "java/src/org/chromium/chrome/browser/firstrun/TosAndUmaFragmentView.java", "java/src/org/chromium/chrome/browser/flags/BadFlagsSnackbarManager.java", "java/src/org/chromium/chrome/browser/fonts/FontPreloader.java", "java/src/org/chromium/chrome/browser/fullscreen/BrowserControlsManager.java", diff --git a/chrome/android/java/res/layout/fre_tosanduma.xml b/chrome/android/java/res/layout/fre_tosanduma.xml new file mode 100644 --- /dev/null +++ b/chrome/android/java/res/layout/fre_tosanduma.xml @@ -0,0 +1,160 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright 2015 The Chromium Authors +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +--> +<!-- Most of the placement in this layout is controlled by TosAndUmaFragmentView#onMeasure. When changing the layout in this file, be sure to also check on the view object to see what is changing to avoid unexpected behavior. --> +<org.chromium.chrome.browser.firstrun.TosAndUmaFragmentView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ScrollView + android:id="@+id/scroll_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_above="@id/fre_bottom_group" + android:fillViewport="true"> + + <!-- The orientation of this view is changed dynamically to give a nicer layout when in + landscape mode on devices with small screens. --> + <LinearLayout + android:id="@+id/fre_main_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:animateLayoutChanges="true" + android:gravity="center_horizontal"> + + <ImageView + android:id="@+id/image" + android:layout_width="wrap_content" + android:layout_height="@dimen/fre_tos_image_height" + android:layout_marginHorizontal="@dimen/fre_vertical_spacing" + android:layout_marginBottom="@dimen/fre_image_bottom_margin" + android:importantForAccessibility="no" + android:src="@drawable/fre_product_logo" /> + + <LinearLayout + android:id="@+id/fre_title_and_content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + android:id="@+id/title" + android:text="@string/fre_welcome" + style="@style/FreWelcomePageTitle" /> + + <!-- The FrameLayout here is to facilitate adding a proper content description for + the loading view. During development, it didn't seem possible to override the + LoadingView contentDescription in XML, but if there's support for this at some + point then we can remove the FrameLayout. --> + <FrameLayout + android:id="@+id/loading_view_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:visibility="gone" + android:contentDescription="@string/sync_loading"> + + <org.chromium.ui.widget.LoadingView + android:id="@+id/progress_spinner_large" + style="@style/Widget.AppCompat.ProgressBar" + android:layout_height="@dimen/fre_loading_spinner_size" + android:layout_width="@dimen/fre_loading_spinner_size" + android:visibility="gone"/> + + </FrameLayout> + + <LinearLayout + android:id="@+id/fre_content_wrapper" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:layout_marginEnd="@dimen/fre_content_margin" + android:gravity="center_vertical" + android:orientation="vertical" > + + <org.chromium.ui.widget.TextViewWithClickableSpans + android:id="@+id/tos_and_privacy" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/fre_vertical_spacing" + android:layout_marginBottom="@dimen/fre_tos_bottom_margin" + android:lineSpacingMultiplier="1.4" + android:textAppearance="@style/TextAppearance.TextMedium.Primary" /> + + <CheckBox + android:id="@+id/auto_updater_checkbox" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:lineSpacingMultiplier="1.4" + android:text="@string/auto_updater_check" + android:paddingStart="@dimen/fre_tos_checkbox_padding" + android:textAppearance="@style/TextAppearance.TextMedium.Primary" /> + </LinearLayout> + + <include + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_marginEnd="@dimen/fre_content_margin" + android:layout_marginTop="@dimen/fre_policy_privacy_disclaimer_top_margin" + android:layout_marginBottom="@dimen/fre_vertical_spacing" + android:visibility="gone" + layout="@layout/fre_tos_privacy_disclaimer" /> + </LinearLayout> + </LinearLayout> + </ScrollView> + + <FrameLayout + android:id="@+id/fre_bottom_group" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:layout_marginVertical="@dimen/fre_button_vertical_margin" + android:layout_marginHorizontal="@dimen/fre_content_margin"> + + <org.chromium.ui.widget.ButtonCompat + android:id="@+id/terms_accept" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:text="@string/fre_accept_continue" + android:animateLayoutChanges="true" + style="@style/FreAcceptTosButton" /> + + <!-- Same location as the button; marginBottom is adjusted for the different size. --> + <ProgressBar + android:id="@+id/progress_spinner" + style="@style/Widget.AppCompat.ProgressBar" + android:layout_gravity="center" + android:layout_width="@dimen/fre_bottom_loading_spinner_size" + android:layout_height="@dimen/fre_bottom_loading_spinner_size"/> + </FrameLayout> + + <ImageView + android:id="@+id/shadow" + android:layout_width="match_parent" + android:layout_height="@dimen/action_bar_shadow_height" + android:layout_gravity="bottom" + android:layout_above="@id/fre_bottom_group" + android:background="@drawable/modern_toolbar_shadow" + android:scaleY="-1" + android:visibility="gone" + android:importantForAccessibility="no" /> + + <!-- Empty TextView to preload fonts for following pages. See https://crbug.com/1119990#c20 --> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.Headline.Primary" + android:visibility="gone"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.Button.Text.Filled" + android:visibility="gone"/> + +</org.chromium.chrome.browser.firstrun.TosAndUmaFragmentView> diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml --- a/chrome/android/java/res/values/dimens.xml +++ b/chrome/android/java/res/values/dimens.xml @@ -111,6 +111,16 @@ found in the LICENSE file. <dimen name="fre_loading_spinner_size">48dp</dimen> <dimen name="fre_policy_privacy_disclaimer_icon_size">18dp</dimen> <dimen name="fre_policy_privacy_disclaimer_icon_padding">8dp</dimen> + <dimen name="fre_image_bottom_margin">36dp</dimen> + <dimen name="fre_vertical_spacing">32dp</dimen> + <dimen name="fre_content_margin">24dp</dimen> + <dimen name="fre_tos_bottom_margin">16dp</dimen> + <dimen name="fre_tos_checkbox_padding">12dp</dimen> + <dimen name="fre_bottom_loading_spinner_size">24dp</dimen> + <dimen name="fre_policy_privacy_disclaimer_top_margin">16dp</dimen> + <dimen name="fre_button_vertical_margin">24dp</dimen> + <dimen name="fre_landscape_top_padding">72dp</dimen> + <dimen name="fre_button_vertical_margin_small">16dp</dimen> <!-- Account Signin dimensions --> <!-- The Account Signin page appears in the First Run Experience (amongst other places), so uses diff --git a/chrome/android/java/res/values/styles.xml b/chrome/android/java/res/values/styles.xml --- a/chrome/android/java/res/values/styles.xml +++ b/chrome/android/java/res/values/styles.xml @@ -257,6 +257,30 @@ found in the LICENSE file. <item name="android:layout_weight">1</item> </style> + <!-- First Run Experience --> + <!-- Avoid using @font/accent_font, a downloaded font, on text that could appear in the first + couple of frames of app start. It may not be ready yet, see https://crbug.com/1119990 --> + <style name="TextAppearance.FreFirstFrameTitle" parent="TextAppearance.Headline.Primary" > + <item name="android:fontFamily">sans-serif</item> + </style> + <!-- Match the fontFamily in ui/android/java/res/values/styles.xml --> + <style name="TextAppearance.FreFirstFrameButton" parent="TextAppearance.Button.Text.Filled"> + <item name="android:fontFamily">sans-serif-medium</item> + </style> + <style name="FreTitle"> + <item name="android:layout_width">wrap_content</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:gravity">center</item> + <item name="android:lineSpacingMultiplier">1.4</item> + <item name="android:textAppearance">@style/TextAppearance.Headline.Primary</item> + </style> + <style name="FreWelcomePageTitle" parent="FreTitle"> + <item name="android:textAppearance">@style/TextAppearance.FreFirstFrameTitle</item> + </style> + <style name="FreAcceptTosButton" parent="FilledButton.Flat"> + <item name="android:textAppearance">@style/TextAppearance.FreFirstFrameButton</item> + </style> + <!-- Generic Overlay Panel styles --> <style name="OverlayPanelTextViewLayout"> <item name="android:layout_width">match_parent</item> diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/ChildAccountStatusSupplier.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/ChildAccountStatusSupplier.java --- a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/ChildAccountStatusSupplier.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/ChildAccountStatusSupplier.java @@ -13,7 +13,6 @@ import org.chromium.base.metrics.RecordHistogram; import org.chromium.base.supplier.OneshotSupplier; import org.chromium.base.supplier.OneshotSupplierImpl; import org.chromium.components.signin.AccountManagerFacade; -import org.chromium.components.signin.AccountUtils; /** * Fetches the child account status to be used by other FRE components. @@ -41,13 +40,6 @@ public class ChildAccountStatusSupplier implements OneshotSupplier<Boolean> { public ChildAccountStatusSupplier(AccountManagerFacade accountManagerFacade, FirstRunAppRestrictionInfo appRestrictionInfo) { mChildAccountStatusStartTime = SystemClock.elapsedRealtime(); - - appRestrictionInfo.getHasAppRestriction(this::onAppRestrictionDetected); - - accountManagerFacade.getAccounts().then(accounts -> { - AccountUtils.checkChildAccountStatusLegacy(accountManagerFacade, accounts, - (isChild, account) -> onChildAccountStatusReady(isChild)); - }); } @Override diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivity.java --- a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivity.java @@ -29,9 +29,7 @@ import org.chromium.chrome.browser.feature_engagement.TrackerFactory; import org.chromium.chrome.browser.fonts.FontPreloader; import org.chromium.chrome.browser.metrics.UmaUtils; import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory; -import org.chromium.chrome.browser.signin.SigninCheckerProvider; -import org.chromium.chrome.browser.signin.SigninFirstRunFragment; -import org.chromium.chrome.browser.signin.services.FREMobileIdentityConsistencyFieldTrial; +import org.chromium.chrome.browser.firstrun.ToSAndUMAFirstRunFragment; import org.chromium.components.browser_ui.modaldialog.AppModalPresenter; import org.chromium.components.feature_engagement.EventConstants; import org.chromium.components.metrics.LowEntropySource; @@ -121,7 +119,7 @@ public class FirstRunActivity extends FirstRunActivityBase implements FirstRunPa /** Creates first page and sets up adapter. Should result UI being shown on the screen. */ private void createFirstPage() { BooleanSupplier showWelcomePage = () -> !FirstRunStatus.shouldSkipWelcomePage(); - mPages.add(new FirstRunPage<>(SigninFirstRunFragment.class, showWelcomePage)); + mPages.add(new FirstRunPage<>(ToSAndUMAFirstRunFragment.class, showWelcomePage)); mFreProgressStates.add(MobileFreProgress.WELCOME_SHOWN); mPagerAdapter = new FirstRunPagerAdapter(FirstRunActivity.this, mPages); mPager.setAdapter(mPagerAdapter); @@ -143,13 +141,11 @@ public class FirstRunActivity extends FirstRunActivityBase implements FirstRunPa // // TODO(b/245912657): explicitly sign in supervised users in {@link // SigninFirstRunMediator#handleContinueWithNative} rather than relying on SigninChecker. - SigninCheckerProvider.get(); mFirstRunFlowSequencer.updateFirstRunProperties(mFreProperties); BooleanSupplier showSearchEnginePromo = () -> mFreProperties.getBoolean(SHOW_SEARCH_ENGINE_PAGE); - BooleanSupplier showSyncConsent = () -> mFreProperties.getBoolean(SHOW_SYNC_CONSENT_PAGE); // An optional page to select a default search engine. if (showSearchEnginePromo.getAsBoolean()) { @@ -158,11 +154,6 @@ public class FirstRunActivity extends FirstRunActivityBase implements FirstRunPa mFreProgressStates.add(MobileFreProgress.DEFAULT_SEARCH_ENGINE_SHOWN); } - // An optional sync consent page, the visibility of this page will be decided on the fly - // according to the situation. - mPages.add(new FirstRunPage<>(SyncConsentFirstRunFragment.class, showSyncConsent)); - mFreProgressStates.add(MobileFreProgress.SYNC_CONSENT_SHOWN); - if (mPagerAdapter != null) { mPagerAdapter.notifyDataSetChanged(); } @@ -207,10 +198,6 @@ public class FirstRunActivity extends FirstRunActivityBase implements FirstRunPa @Override public void triggerLayoutInflation() { - // Generate trial group as early as possible to guarantee it's available by the time native - // needs to register the synthetic trial group. See https://crbug.com/1295692 for details. - FREMobileIdentityConsistencyFieldTrial.createFirstRunVariationsTrial(); - super.triggerLayoutInflation(); initializeStateFromLaunchData(); @@ -500,10 +487,6 @@ public class FirstRunActivity extends FirstRunActivityBase implements FirstRunPa public void acceptTermsOfService(boolean allowMetricsAndCrashUploading) { assert mNativeInitializationPromise.isFulfilled(); - // If default is true then it corresponds to opt-out and false corresponds to opt-in. - UmaUtils.recordMetricsReportingDefaultOptIn(!DEFAULT_METRICS_AND_CRASH_REPORTING); - RecordHistogram.recordMediumTimesHistogram("MobileFre.FromLaunch.TosAccepted", - SystemClock.elapsedRealtime() - mIntentCreationElapsedRealtimeMs); FirstRunUtils.acceptTermsOfService(allowMetricsAndCrashUploading); FirstRunStatus.setSkipWelcomePage(true); flushPersistentData(); diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivityBase.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivityBase.java --- a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivityBase.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivityBase.java @@ -33,8 +33,6 @@ import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.profiles.ProfileManagerUtils; import org.chromium.components.browser_ui.widget.gesture.BackPressHandler; import org.chromium.components.policy.PolicyService; -import org.chromium.components.signin.AccountManagerFacade; -import org.chromium.components.signin.AccountManagerFacadeProvider; /** Base class for First Run Experience. */ public abstract class FirstRunActivityBase @@ -104,9 +102,7 @@ public abstract class FirstRunActivityBase @Override @CallSuper public void triggerLayoutInflation() { - AccountManagerFacade accountManagerFacade = AccountManagerFacadeProvider.getInstance(); - mChildAccountStatusSupplier = - new ChildAccountStatusSupplier(accountManagerFacade, mFirstRunAppRestrictionInfo); + mChildAccountStatusSupplier = new ChildAccountStatusSupplier(null, null); } @Override diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunFlowSequencer.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunFlowSequencer.java --- a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunFlowSequencer.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunFlowSequencer.java @@ -27,14 +27,9 @@ import org.chromium.chrome.browser.locale.LocaleManager; import org.chromium.chrome.browser.partnercustomizations.PartnerBrowserCustomizations; import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.search_engines.SearchEnginePromoType; -import org.chromium.chrome.browser.signin.services.IdentityServicesProvider; -import org.chromium.chrome.browser.signin.services.SigninManager; import org.chromium.components.crash.CrashKeyIndex; import org.chromium.components.crash.CrashKeys; import org.chromium.components.embedder_support.util.UrlConstants; -import org.chromium.components.signin.AccountManagerFacadeProvider; -import org.chromium.components.signin.identitymanager.ConsentLevel; -import org.chromium.components.signin.identitymanager.IdentityManager; import java.util.List; @@ -65,38 +60,19 @@ public abstract class FirstRunFlowSequencer { /** Returns true if the sync consent promo page should be shown. */ boolean shouldShowSyncConsentPage( Activity activity, List<Account> accounts, boolean isChild) { - if (isChild) { - // Always show the sync consent page for child account. - return true; - } - assert mProfileSupplier.get() != null; - final IdentityManager identityManager = - IdentityServicesProvider.get().getIdentityManager(mProfileSupplier.get()); - if (identityManager.hasPrimaryAccount(ConsentLevel.SYNC) || !isSyncAllowed()) { - // No need to show the sync consent page if users already consented to sync or - // if sync is not allowed. return false; - } - // Show the sync consent page only to the signed-in users. - return identityManager.hasPrimaryAccount(ConsentLevel.SIGNIN); } /** @return true if the Search Engine promo page should be shown. */ @VisibleForTesting public boolean shouldShowSearchEnginePage() { - @SearchEnginePromoType - int searchPromoType = LocaleManager.getInstance().getSearchEnginePromoShowType(); - return searchPromoType == SearchEnginePromoType.SHOW_NEW - || searchPromoType == SearchEnginePromoType.SHOW_EXISTING; + return false; } /** @return true if Sync is allowed for the current user. */ @VisibleForTesting protected boolean isSyncAllowed() { - SigninManager signinManager = - IdentityServicesProvider.get().getSigninManager(mProfileSupplier.get()); - return FirstRunUtils.canAllowSync() && !signinManager.isSigninDisabledByPolicy() - && signinManager.isSigninSupported(/*requireUpdatedPlayServices=*/false); + return false; } } @@ -147,12 +123,8 @@ public abstract class FirstRunFlowSequencer { * method. */ void start() { - AccountManagerFacadeProvider.getInstance().getAccounts().then(accounts -> { - RecordHistogram.recordCount1MHistogram( - "Signin.AndroidDeviceAccountsNumberWhenEnteringFRE", - Math.min(accounts.size(), 2)); - setAccountList(accounts); - }); + mIsChild = false; + maybeProcessFreEnvironmentPreNative(); } @VisibleForTesting @@ -177,14 +149,10 @@ public abstract class FirstRunFlowSequencer { } private void maybeProcessFreEnvironmentPreNative() { - // Wait till both child account status and the list of accounts are available. - if (mIsChild == null || mGoogleAccounts == null) return; - if (mIsFlowKnown) return; mIsFlowKnown = true; Bundle freProperties = new Bundle(); - freProperties.putBoolean(SyncConsentFirstRunFragment.IS_CHILD_ACCOUNT, mIsChild); onFlowIsKnown(freProperties); } @@ -195,8 +163,8 @@ public abstract class FirstRunFlowSequencer { * @param freProperties Resulting FRE properties bundle. */ public void updateFirstRunProperties(Bundle freProperties) { - freProperties.putBoolean( - FirstRunActivity.SHOW_SYNC_CONSENT_PAGE, shouldShowSyncConsentPage()); + if (freProperties == null) + throw new RuntimeException("attempting to update null FRE properties"); freProperties.putBoolean( FirstRunActivity.SHOW_SEARCH_ENGINE_PAGE, shouldShowSearchEnginePage()); } @@ -297,13 +265,17 @@ public abstract class FirstRunFlowSequencer { if (!(caller instanceof Activity)) { freIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } - IntentUtils.safeStartActivity(caller, freIntent); + if (!IntentUtils.safeStartActivity(caller, freIntent)) { + throw new RuntimeException("Cannot start FirstRunExperience activity"); + } } 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); + if (!IntentUtils.safeStartActivity(caller, newIntent)) { + throw new RuntimeException("Cannot start FirstRunExperience activity"); + } } return true; } diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunUtils.java --- a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunUtils.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunUtils.java @@ -18,9 +18,6 @@ import org.chromium.chrome.browser.metrics.ChangeMetricsReportingStateCalledFrom import org.chromium.chrome.browser.metrics.UmaSessionStats; import org.chromium.chrome.browser.preferences.ChromePreferenceKeys; import org.chromium.chrome.browser.preferences.SharedPreferencesManager; -import org.chromium.components.signin.AccountManagerFacade; -import org.chromium.components.signin.AccountManagerFacadeProvider; -import org.chromium.components.signin.AccountUtils; import org.chromium.ui.accessibility.AccessibilityState; /** Provides first run related utility functions. */ @@ -83,16 +80,12 @@ public class FirstRunUtils { * @return Whether or not sync is allowed on this device. */ static boolean canAllowSync() { - return (hasGoogleAccountAuthenticator() && hasSyncPermissions()) || hasGoogleAccounts(); + return false; } @VisibleForTesting static boolean hasGoogleAccountAuthenticator() { - if (sHasGoogleAccountAuthenticator == null) { - AccountManagerFacade accountHelper = AccountManagerFacadeProvider.getInstance(); - sHasGoogleAccountAuthenticator = accountHelper.hasGoogleAccountAuthenticator(); - } - return sHasGoogleAccountAuthenticator; + return false; } @VisibleForTesting @@ -102,10 +95,7 @@ public class FirstRunUtils { @VisibleForTesting static boolean hasGoogleAccounts() { - return !AccountUtils - .getAccountsIfFulfilledOrEmpty( - AccountManagerFacadeProvider.getInstance().getAccounts()) - .isEmpty(); + return false; } @SuppressLint("InlinedApi") diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/ToSAndUMAFirstRunFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/ToSAndUMAFirstRunFragment.java new file mode 100644 --- /dev/null +++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/ToSAndUMAFirstRunFragment.java @@ -0,0 +1,336 @@ +// Copyright 2015 The Chromium Authors +// 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.content.Context; +import android.os.Bundle; +import android.os.SystemClock; +import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.TextView; + +import android.content.SharedPreferences; +import org.chromium.chrome.browser.omaha.OmahaBase; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.fragment.app.Fragment; + +import org.chromium.base.Log; +import org.chromium.base.metrics.RecordHistogram; +import org.chromium.chrome.R; +import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManagerImpl; +import org.chromium.components.version_info.VersionInfo; +import org.chromium.ui.modaldialog.ModalDialogManagerHolder; +import org.chromium.ui.text.NoUnderlineClickableSpan; +import org.chromium.ui.text.SpanApplier; +import org.chromium.ui.text.SpanApplier.SpanInfo; + +import java.util.LinkedList; +import java.util.List; + +/** + * The First Run Experience fragment that allows the user to accept Terms of Service ("ToS") and + * Privacy Notice, and to opt-in to the usage statistics and crash reports collection ("UMA", + * User Metrics Analysis) as defined in the Chrome Privacy Notice. + */ +public class ToSAndUMAFirstRunFragment + extends Fragment implements FirstRunFragment { + /** Alerts about some methods once ToSAndUMAFirstRunFragment executes them. */ + public interface Observer { + /** See {@link #onNativeInitialized}. */ + public void onNativeInitialized(); + public void onPolicyServiceInitialized(); + public void onHideLoadingUIComplete(); + } + + private static boolean sShowUmaCheckBoxForTesting; + + @Nullable + private static ToSAndUMAFirstRunFragment.Observer sObserver; + + private boolean mNativeInitialized; + private boolean mPolicyServiceInitialized; + private boolean mTosButtonClicked; + private boolean mAllowMetricsAndCrashUploading; + private boolean mUserInteractedWithUmaCheckbox; + + private Button mAcceptButton; + private CheckBox mAutoUpdaterCheckBox; + private boolean mAutoUpdaterChecked; + private TextView mTosAndPrivacy; + private View mTitle; + private View mProgressSpinner; + + private long mTosAcceptedTime; + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.fre_tosanduma, container, false); + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + getPageDelegate().getPolicyLoadListener().onAvailable(this::onPolicyServiceInitialized); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + mTitle = view.findViewById(R.id.title); + mProgressSpinner = view.findViewById(R.id.progress_spinner); + mProgressSpinner.setVisibility(View.GONE); + mAcceptButton = (Button) view.findViewById(R.id.terms_accept); + mAutoUpdaterCheckBox = (CheckBox) view.findViewById(R.id.auto_updater_checkbox); + mTosAndPrivacy = (TextView) view.findViewById(R.id.tos_and_privacy); + + // Register event listeners. + mAcceptButton.setOnClickListener((v) -> onTosButtonClicked()); + mAutoUpdaterCheckBox.setOnCheckedChangeListener( + ((compoundButton, isChecked) -> { + mAutoUpdaterChecked = isChecked; + })); + + // Make TextView links clickable. + mTosAndPrivacy.setMovementMethod(LinkMovementMethod.getInstance()); + + updateView(); + + // If this page should be skipped, it can be one of the following cases: + // 1. Native hasn't been initialized yet and this page will be skipped once that happens. + // 2. The user has moved back to this page after advancing past it. In this case, this + // may not even be the same object as before, as the fragment may have been re-created. + // + // In case 1, hide all the elements except for Chrome logo and the spinner until native gets + // initialized at which point the activity will skip the page. + // We distinguish case 1 from case 2 by the value of |mNativeInitialized|, as that is set + // via onAttachFragment() from FirstRunActivity - which is before this onViewCreated(). + boolean isW = isWaitingForNativeAndPolicyInit(); + boolean ssw = FirstRunStatus.shouldSkipWelcomePage(); + if (isW && ssw) { + setSpinnerVisible(true); + } + } + + @Override + public void setInitialA11yFocus() { + // Ignore calls before view is created. + if (mTitle == null) return; + mTitle.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); + } + + @Override + public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + + // This may be called before onViewCreated(), in which case the below is not yet relevant. + if (mTitle == null) return; + + if (!isVisibleToUser) { + // Restore original enabled & visibility states, in case the user returns to the page. + setSpinnerVisible(false); + } else { + // On certain versions of Android, the checkbox will appear unchecked upon revisiting + // the page. Force it to the end state of the drawable animation as a work around. + // crbug.com/666258 + mAutoUpdaterCheckBox.jumpDrawablesToCurrentState(); + } + } + + @Override + public void onNativeInitialized() { + assert !mNativeInitialized; + + mNativeInitialized = true; + tryMarkTermsAccepted(false); + + if (mPolicyServiceInitialized) { + onNativeAndPolicyServiceInitialized(); + } + + if (sObserver != null) { + sObserver.onNativeInitialized(); + } + } + + @Override + public void reset() { + // We cannot pass the welcome page when native or policy is not initialized. When this page + // is revisited, this means this page is persist and we should re-show the ToS And UMA. + assert !isWaitingForNativeAndPolicyInit(); + + setSpinnerVisible(false); + mAutoUpdaterCheckBox.setChecked(true); + } + + private void updateView() { + // Avoid early calls. + if (getPageDelegate() == null) { + return; + } + + updateTosText(); + + updateReportCheckbox(); + } + + private SpanInfo buildPrivacyPolicyLink(String suffix, int url) { + NoUnderlineClickableSpan clickableSpan = + new NoUnderlineClickableSpan(getContext(), (view1) -> { + if (!isAdded()) return; + getPageDelegate().showInfoPage(url); + }); + + return new SpanInfo("<PRIVACY_LINK" + suffix + ">", "</PRIVACY_LINK" + suffix + ">", clickableSpan); + } + + private void updateTosText() { + List<SpanInfo> spans = new LinkedList<SpanInfo>(); + + spans.add(buildPrivacyPolicyLink("1", R.string.adblock_wiki_url)); + + spans.add(buildPrivacyPolicyLink("2", R.string.adblock_updater_privacy_policy_url)); + + spans.add(buildPrivacyPolicyLink("3", R.string.auto_updates_wiki_url)); + + spans.add(buildPrivacyPolicyLink("4", R.string.bromite_updater_privacy_policy_url)); + + String tosString = getString(R.string.bromite_fre_footer_privacy_policy); + + mTosAndPrivacy.setText(SpanApplier.applySpans(tosString, spans.toArray(new SpanInfo[0]))); + } + + private void updateReportCheckbox() { + mAutoUpdaterCheckBox.setChecked(true); + } + + private void onPolicyServiceInitialized(boolean onDevicePolicyFound) { + assert !mPolicyServiceInitialized; + + mPolicyServiceInitialized = true; + tryMarkTermsAccepted(false); + + if (mNativeInitialized) { + onNativeAndPolicyServiceInitialized(); + } + + if (sObserver != null) { + sObserver.onPolicyServiceInitialized(); + } + } + + private void onNativeAndPolicyServiceInitialized() { + // Once we have native & policies, Check whether metrics reporting are permitted by policy + // and update interface accordingly. + updateView(); + } + + private void onTosButtonClicked() { + mTosButtonClicked = true; + mTosAcceptedTime = SystemClock.elapsedRealtime(); + + // save updater configuration only on button click + SharedPreferences.Editor sharedPreferenceEditor = OmahaBase.getSharedPreferences().edit(); + sharedPreferenceEditor.putBoolean(OmahaBase.PREF_ALLOW_INLINE_UPDATE, mAutoUpdaterChecked); + sharedPreferenceEditor.apply(); + + tryMarkTermsAccepted(true); + } + + /** + * This should be called Tos button is clicked for a fresh new FRE, or when native and policies + * are initialized if Tos has ever been accepted. + * + * @param fromButtonClicked Whether called from {@link #onTosButtonClicked()}. + */ + private void tryMarkTermsAccepted(boolean fromButtonClicked) { + boolean isW = isWaitingForNativeAndPolicyInit(); + if (!mTosButtonClicked || isW) { + if (fromButtonClicked) setSpinnerVisible(true); + return; + } + + // In cases where the attempt is triggered other than button click, the ToS should have been + // accepted by the user already. + if (!fromButtonClicked) { + RecordHistogram.recordTimesHistogram("MobileFre.TosFragment.SpinnerVisibleDuration", + SystemClock.elapsedRealtime() - mTosAcceptedTime); + } + getPageDelegate().acceptTermsOfService(false); + getPageDelegate().advanceToNextPage(); + } + + private void setSpinnerVisible(boolean spinnerVisible) { + // When the progress spinner is visible, we hide the other UI elements so that + // the user can't interact with them. + boolean otherElementVisible = !spinnerVisible; + + setTosAndUmaVisible(otherElementVisible); + mTitle.setVisibility(otherElementVisible ? View.VISIBLE : View.INVISIBLE); + mProgressSpinner.setVisibility(spinnerVisible ? View.VISIBLE : View.GONE); + } + + private boolean isWaitingForNativeAndPolicyInit() { + return !mNativeInitialized || !mPolicyServiceInitialized; + } + + private boolean getUmaCheckBoxInitialState() { + // Metrics and crash reporting could not be permitted by policy. + if (!isWaitingForNativeAndPolicyInit() + && !PrivacyPreferencesManagerImpl.getInstance() + .isUsageAndCrashReportingPermittedByPolicy()) { + return false; + } + + // A user could start FRE and accept terms of service, then close the browser and start + // again. In this case we rely on whatever state the user has already set. + if (FirstRunUtils.didAcceptTermsOfService()) { + return PrivacyPreferencesManagerImpl.getInstance() + .isUsageAndCrashReportingPermittedByUser(); + } + + return FirstRunActivity.DEFAULT_METRICS_AND_CRASH_REPORTING; + } + + // Exposed methods for ToSAndUMACCTFirstRunFragment + + protected void setTosAndUmaVisible(boolean isVisible) { + int visibility = isVisible ? View.VISIBLE : View.GONE; + + mAcceptButton.setVisibility(visibility); + mTosAndPrivacy.setVisibility(visibility); + mAutoUpdaterCheckBox.setVisibility(visibility); + } + + protected View getToSAndPrivacyText() { + return mTosAndPrivacy; + } + + protected void onHideLoadingUIComplete() { + if (sObserver != null) { + sObserver.onHideLoadingUIComplete(); + } + } + + @VisibleForTesting + public static void setShowUmaCheckBoxForTesting(boolean showForTesting) { + sShowUmaCheckBoxForTesting = showForTesting; + } + + @VisibleForTesting + public static void setObserverForTesting(ToSAndUMAFirstRunFragment.Observer observer) { + assert observer == null || sObserver == null; + sObserver = observer; + } +} diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/TosAndUmaFragmentView.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/TosAndUmaFragmentView.java new file mode 100644 --- /dev/null +++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/TosAndUmaFragmentView.java @@ -0,0 +1,336 @@ +// Copyright 2020 The Chromium Authors +// 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.content.Context; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.ScrollView; + +import org.chromium.chrome.R; + +/** + * Base view for fre_tosanduma.xml. This view may change child view placement when changing screen + * dimensions (e.g. on rotation). + * + * See https://crbug.com/1151537 for illustration. + */ +public class TosAndUmaFragmentView extends RelativeLayout { + private ScrollView mScrollView; + + private LinearLayout mMainLayout; + + // The "title and content" contains the mTitle, mContentWrapper, and mLoadingSpinner that is + // visible when waiting for policy to be loaded. + private View mTitleAndContent; + + // The "content wrapper" contains the ToS text and the UMA check box. + private View mContentWrapper; + + // The "bottom group" contains the accept & continue button, and a small spinner that displays + // in its place when waiting for C++ to load before processing the FRE screen. + private View mBottomGroup; + + private View mTitle; + private View mLogo; + private View mLoadingSpinnerContainer; + private View mPrivacyDisclaimer; + private View mShadow; + + private int mLastHeight; + private int mLastWidth; + + // Spacing params + private int mImageBottomMargin; + private int mVerticalSpacing; + private int mImageSize; + private int mLoadingSpinnerSize; + private int mLandscapeTopPadding; + private int mHeadlineSize; + private int mContentMargin; + private int mAcceptButtonHeight; + private int mBottomGroupVerticalMarginRegular; + private int mBottomGroupVerticalMarginSmall; + + // Store the bottom margins for different screen orientations. We are using a smaller bottom + // margin when the content becomes scrollable. Storing margins per orientation because there are + // cases where content is scrollable in landscape mode while not in portrait mode. + private int mBottomMarginPortrait; + private int mBottomMarginLandscape; + + /** + * Constructor for inflating via XML. + */ + public TosAndUmaFragmentView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mScrollView = findViewById(R.id.scroll_view); + + mMainLayout = findViewById(R.id.fre_main_layout); + mTitleAndContent = findViewById(R.id.fre_title_and_content); + mContentWrapper = findViewById(R.id.fre_content_wrapper); + mBottomGroup = findViewById(R.id.fre_bottom_group); + + mTitle = findViewById(R.id.title); + mLogo = findViewById(R.id.image); + mLoadingSpinnerContainer = findViewById(R.id.loading_view_container); + mPrivacyDisclaimer = findViewById(R.id.privacy_disclaimer); + mShadow = findViewById(R.id.shadow); + + // Set up shadow. + // Needed when scrolling to/away from the bottom of the ScrollView. + mScrollView.getViewTreeObserver().addOnScrollChangedListener(this::updateShadowVisibility); + // Needed when other elements are added / removed from ScrollView. + mScrollView.getViewTreeObserver().addOnGlobalLayoutListener(this::updateShadowVisibility); + + // Cache resource dimensions that used in #onMeasure. + mImageBottomMargin = getResources().getDimensionPixelSize(R.dimen.fre_image_bottom_margin); + mVerticalSpacing = getResources().getDimensionPixelSize(R.dimen.fre_vertical_spacing); + mImageSize = getResources().getDimensionPixelSize(R.dimen.fre_tos_image_height); + mLoadingSpinnerSize = + getResources().getDimensionPixelSize(R.dimen.fre_loading_spinner_size); + mLandscapeTopPadding = + getResources().getDimensionPixelSize(R.dimen.fre_landscape_top_padding); + mHeadlineSize = getResources().getDimensionPixelSize(R.dimen.headline_size); + mContentMargin = getResources().getDimensionPixelSize(R.dimen.fre_content_margin); + mAcceptButtonHeight = getResources().getDimensionPixelSize(R.dimen.min_touch_target_size); + + mBottomGroupVerticalMarginRegular = + getResources().getDimensionPixelSize(R.dimen.fre_button_vertical_margin); + mBottomGroupVerticalMarginSmall = + getResources().getDimensionPixelSize(R.dimen.fre_button_vertical_margin_small); + + // Default bottom margin to "regular", consistent with what is defined in xml. + mBottomMarginPortrait = mBottomGroupVerticalMarginRegular; + mBottomMarginLandscape = mBottomGroupVerticalMarginRegular; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // For an alternate layout in horizontal mode for screens of a certain size. These are why + // the padding is set manually. + + // This assumes that view's layout_width is set to match_parent. + assert MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY; + + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + + // If the layout orientation does not change, there's no need to recalculate layout + // attributes. + if (width != mLastWidth || height != mLastHeight) { + mLastHeight = height; + mLastWidth = width; + + boolean useWideScreenLayout = shouldUseWideScreen(width, height); + + mMainLayout.setOrientation( + useWideScreenLayout ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL); + + mTitleAndContent.setPaddingRelative(mTitleAndContent.getPaddingStart(), + getTitleAndContentLayoutTopPadding(useWideScreenLayout), + mTitleAndContent.getPaddingEnd(), mTitleAndContent.getPaddingBottom()); + + setLogoLayoutParams(useWideScreenLayout, height); + setTitleLayoutParams(useWideScreenLayout); + setSpinnerLayoutParams(useWideScreenLayout, width, height); + + setContentLayoutParams(useWideScreenLayout); + setPrivacyDisclaimerLayoutParams(useWideScreenLayout); + + setBottomGroupLayoutParams(useWideScreenLayout); + } + + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + // Do another round of view adjustments that depends on sizes assigned to children views in + // super#onMeasure. If the state of any view is changed in this process, trigger another + // round of measure to make changes take effect. + boolean changed = doPostMeasureAdjustment(); + if (changed) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } + + private boolean shouldUseWideScreen(int width, int height) { + int maxButtonBarHeight = mAcceptButtonHeight + 2 * mBottomGroupVerticalMarginRegular; + return (height >= mImageSize + 2 * maxButtonBarHeight) && (width > 1.5 * height); + } + + /** + * Adjust views after measure, when every view components has an initial size assigned. + * @return Whether any change happened to children views. + */ + private boolean doPostMeasureAdjustment() { + boolean changed = updateShadowVisibility(); + changed |= assignSmallBottomMarginIfNecessary(); + return changed; + } + + private boolean updateShadowVisibility() { + int newVisibility = mScrollView.canScrollVertically(1) ? VISIBLE : GONE; + if (newVisibility == mShadow.getVisibility()) { + return false; + } + mShadow.setVisibility(newVisibility); + return true; + } + + /** + * When content is scrollable, use a smaller margin to present more content on screen. + * Note that once we change to using a smaller margin we currently will never switch back to the + * default margin size (e.g. enter then exit multi-window). + * + * TODO(https://crbug.com/1159198): Adjust the margin according to the size of + * TosAndUmaFragmentView. + */ + private boolean assignSmallBottomMarginIfNecessary() { + // Check the width and height of TosAndUmaFragmentView. This function may be executed + // between transitioning from landscape to portrait. If the current measure spec (mLastWidth + // and mLastHeight) is different than the size actually measured (getHeight() && + // getWidth()), the results from mScrollView#canScrollVertically could be stale. In such + // cases, it is safe to early return here, as current measure is in transition and a + // follow-up measure will be triggered when the measured spec and actual size matches. + if (getHeight() != mLastHeight || getWidth() != mLastWidth) { + return false; + } + + // Do not assign margins if the content is not scrollable. + if (!mScrollView.canScrollVertically(1) && !mScrollView.canScrollVertically(-1)) { + return false; + } + + MarginLayoutParams params = (MarginLayoutParams) mBottomGroup.getLayoutParams(); + if (params.bottomMargin == mBottomGroupVerticalMarginSmall) { + return false; + } + + if (shouldUseLandscapeBottomMargin()) { + mBottomMarginLandscape = mBottomGroupVerticalMarginSmall; + } else { + mBottomMarginPortrait = mBottomGroupVerticalMarginSmall; + } + params.setMargins(params.leftMargin, mBottomGroupVerticalMarginSmall, params.rightMargin, + mBottomGroupVerticalMarginSmall); + mBottomGroup.setLayoutParams(params); + return true; + } + + private void setSpinnerLayoutParams(boolean useWideScreen, int width, int height) { + LinearLayout.LayoutParams spinnerParams = + (LinearLayout.LayoutParams) mLoadingSpinnerContainer.getLayoutParams(); + + // Adjust the spinner placement. If in portrait mode, the spinner is placed in the region + // below the title; If in wide screen mode, the spinner is placed in the center of + // the entire screen. Because we cannot get the exact size for headline, + // the spinner placement is approximately centered in this case. + if (useWideScreen) { + int freImageWidth = mImageSize + mVerticalSpacing * 2; + int spinnerStartMargin = + Math.max(0, (width / 2) - freImageWidth - mLoadingSpinnerSize / 2); + + int topContentHeight = mHeadlineSize + mLandscapeTopPadding; + int spinnerTopMargin = + Math.max(0, height / 2 - topContentHeight - mLoadingSpinnerSize / 2); + + spinnerParams.gravity = Gravity.START; + spinnerParams.setMarginStart(spinnerStartMargin); + spinnerParams.topMargin = spinnerTopMargin; + } else { + // Use the same padding between title and logo for the spinner. + // TODO(crbug.com/1128123): Switch from top margin to an approach that will center the + // spinner in the bottom half of the screen. + int spinnerTopMargin = mImageBottomMargin; + + spinnerParams.gravity = Gravity.CENTER_HORIZONTAL; + spinnerParams.setMarginStart(0); + spinnerParams.topMargin = spinnerTopMargin; + } + + mLoadingSpinnerContainer.setLayoutParams(spinnerParams); + } + + private void setLogoLayoutParams(boolean useWideScreen, int height) { + LinearLayout.LayoutParams logoLayoutParams = + (LinearLayout.LayoutParams) mLogo.getLayoutParams(); + if (useWideScreen) { + // When using the wide screen layout, we want to vertically center the logo on the start + // side of the screen. While we have no padding on the main layout when using the wide + // screen, we'll calculate the space needed and set it as top margin above the logo to + // make it centered. + int topMargin = (height - mImageSize) / 2; + + // We only use the wide screen layout when the screen height is tall enough to + // accommodate the image and some padding. But just in case that calculation fails, + // ensure topMargin isn't negative. + assert topMargin > 0; + logoLayoutParams.topMargin = Math.max(0, topMargin); + logoLayoutParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP; + } else { + // Otherwise, in tall screen mode, we want the align the baseline of the title to the + // center of the screen. While calculation is done in a similar way, we are putting + // mVerticalSpacing for marginTop as minimum to avoid 0dp spacing between top and logo + // on small screen devices. + int freImageHeight = mImageSize + mImageBottomMargin; + logoLayoutParams.topMargin = + Math.max(mVerticalSpacing, (height / 2 - freImageHeight - mHeadlineSize)); + logoLayoutParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; + } + } + + private void setTitleLayoutParams(boolean useWideScreen) { + LinearLayout.LayoutParams titleParams = + (LinearLayout.LayoutParams) mTitle.getLayoutParams(); + titleParams.gravity = useWideScreen ? Gravity.START : Gravity.CENTER; + } + + private void setContentLayoutParams(boolean useWideScreen) { + MarginLayoutParams contentWrapperLayoutParams = + (MarginLayoutParams) mContentWrapper.getLayoutParams(); + contentWrapperLayoutParams.setMarginStart(useWideScreen ? 0 : mContentMargin); + mContentWrapper.setLayoutParams(contentWrapperLayoutParams); + } + + private void setPrivacyDisclaimerLayoutParams(boolean useWideScreen) { + LinearLayout.LayoutParams privacyDisclaimerParams = + (LinearLayout.LayoutParams) mPrivacyDisclaimer.getLayoutParams(); + privacyDisclaimerParams.gravity = useWideScreen ? Gravity.START : Gravity.CENTER; + privacyDisclaimerParams.setMarginStart(useWideScreen ? 0 : mContentMargin); + mPrivacyDisclaimer.setLayoutParams(privacyDisclaimerParams); + } + + private void setBottomGroupLayoutParams(boolean useWideScreen) { + RelativeLayout.LayoutParams bottomGroupParams = + (RelativeLayout.LayoutParams) mBottomGroup.getLayoutParams(); + int removedRule = + useWideScreen ? RelativeLayout.CENTER_HORIZONTAL : RelativeLayout.ALIGN_PARENT_END; + int addedRule = + useWideScreen ? RelativeLayout.ALIGN_PARENT_END : RelativeLayout.CENTER_HORIZONTAL; + bottomGroupParams.removeRule(removedRule); + bottomGroupParams.addRule(addedRule); + + int bottomMargin = + shouldUseLandscapeBottomMargin() ? mBottomMarginLandscape : mBottomMarginPortrait; + bottomGroupParams.setMargins(bottomGroupParams.leftMargin, bottomMargin, + bottomGroupParams.rightMargin, bottomMargin); + mBottomGroup.setLayoutParams(bottomGroupParams); + } + + private int getTitleAndContentLayoutTopPadding(boolean useWideScreen) { + return useWideScreen ? mLandscapeTopPadding : 0; + } + + private boolean shouldUseLandscapeBottomMargin() { + return mLastWidth > mLastHeight; + } +} diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd --- a/chrome/browser/ui/android/strings/android_chrome_strings.grd +++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd @@ -3301,7 +3301,29 @@ To change this setting, <ph name="BEGIN_LINK"><resetlink></ph>reset sync<p <ph name="APP_NAME">%1$s<ex>Google Maps</ex></ph> will open in Chrome. By continuing, you agree to the <ph name="BEGIN_LINK1"><LINK1></ph>Google Terms of Service<ph name="END_LINK1"></LINK1></ph>, and the <ph name="BEGIN_LINK2"><LINK2></ph>Google Chrome and ChromeOS Additional Terms of Service<ph name="END_LINK2"></LINK2></ph>. The <ph name="BEGIN_LINK3"><LINK3></ph>Privacy Policy<ph name="END_LINK3"></LINK3></ph> also applies. </message> <message name="IDS_FRE_ACCEPT_CONTINUE" desc="Text for first page accept and continue button [CHAR_LIMIT=20]"> - Accept & continue + Continue + </message> + <message name="IDS_ADBLOCK_UPDATER_PRIVACY_POLICY_URL" desc="URL for privacy policy for the ad block updater" translateable="false"> + https://docs.github.com/en/github/site-policy/github-privacy-statement#github-pages + </message> + <message name="IDS_BROMITE_UPDATER_PRIVACY_POLICY_URL" desc="URL for privacy policy for the Bromite auto updater" translateable="false"> + https://docs.github.com/en/github/site-policy/github-privacy-statement#github-pages + </message> + <message name="IDS_BROMITE_FRE_FOOTER_PRIVACY_POLICY" desc="Message explaining the privacy policy of the file hosting service provider for adblock updates and Bromite app automatic updates"> + <ph name="BEGIN_PRIVACY_LINK1"><PRIVACY_LINK1></ph>Automatic ad block filters updates<ph name="END_PRIVACY_LINK1"></PRIVACY_LINK1></ph> are subject to the <ph name="BEGIN_PRIVACY_LINK2"><PRIVACY_LINK2></ph>GitHub Privacy statement<ph name="END_PRIVACY_LINK2"></PRIVACY_LINK2></ph>; they cannot be disabled. + The following checkbox controls instead <ph name="BEGIN_PRIVACY_LINK3"><PRIVACY_LINK3></ph>automatic app updates<ph name="END_PRIVACY_LINK3"></PRIVACY_LINK3></ph> which are also subject to the <ph name="BEGIN_PRIVACY_LINK4"><PRIVACY_LINK4></ph>GitHub Privacy statement<ph name="END_PRIVACY_LINK4"></PRIVACY_LINK4></ph>. + </message> + <message name="IDS_AUTO_UPDATER_CHECK" desc="Message for the checkbox for automatic Bromite updates"> + Automatic checks for Bromite app updates + </message> + <message name="IDS_UPDATER_PRIVACY_POLICY_URL" desc="URL for GitHub privacy statement" translateable="false"> + https://docs.github.com/en/github/site-policy/github-privacy-statement#github-pages + </message> + <message name="IDS_ADBLOCK_WIKI_URL" desc="URL for Bromite wiki page about ad blocking" translateable="false"> + https://github.com/bromite/bromite/wiki/AdBlocking + </message> + <message name="IDS_AUTO_UPDATES_WIKI_URL" desc="URL for Bromite wiki page about automatic updates" translateable="false"> + https://github.com/bromite/bromite/wiki/AutomaticUpdates </message> <message name="IDS_FRE_WELCOME" desc="Text for greeting the user on Chrome First Run"> Welcome to Chrome -- 2.25.1