1304 lines
62 KiB
Diff
1304 lines
62 KiB
Diff
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 | 11 +
|
|
chrome/android/java/res/values/styles.xml | 24 ++
|
|
.../DefaultSearchEngineFirstRunFragment.java | 3 -
|
|
.../browser/firstrun/FirstRunActivity.java | 47 +--
|
|
.../firstrun/FirstRunFlowSequencer.java | 55 +--
|
|
.../browser/firstrun/FirstRunFragment.java | 3 +
|
|
.../browser/firstrun/FirstRunUtils.java | 1 +
|
|
.../firstrun/ToSAndUMAFirstRunFragment.java | 321 +++++++++++++++++
|
|
.../firstrun/TosAndUmaFragmentView.java | 336 ++++++++++++++++++
|
|
.../signin/AppRestrictionSupplier.java | 4 +
|
|
.../signin/ChildAccountStatusSupplier.java | 4 +
|
|
...creenSigninAndHistorySyncActivityBase.java | 6 +-
|
|
.../browser/locale/LocaleManagerDelegate.java | 5 +-
|
|
.../Welcome-screen.grdp | 27 ++
|
|
17 files changed, 926 insertions(+), 84 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
|
|
create mode 100644 chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Welcome-screen.grdp
|
|
|
|
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
|
|
@@ -532,6 +532,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/gesture_user_education_iph_layout.xml",
|
|
"java/res/layout/google_wallet_widget.xml",
|
|
"java/res/layout/hats_survey_container.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
|
|
@@ -684,6 +684,8 @@ chrome_java_sources = [
|
|
"java/src/org/chromium/chrome/browser/firstrun/LightweightFirstRunActivity.java",
|
|
"java/src/org/chromium/chrome/browser/firstrun/SkipTosDialogPolicyListener.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_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
|
|
@@ -108,6 +108,17 @@ 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_tos_image_height">110dp</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
|
|
@@ -364,6 +364,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">
|
|
+ <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/DefaultSearchEngineFirstRunFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/DefaultSearchEngineFirstRunFragment.java
|
|
--- a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/DefaultSearchEngineFirstRunFragment.java
|
|
+++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/DefaultSearchEngineFirstRunFragment.java
|
|
@@ -59,15 +59,12 @@ public class DefaultSearchEngineFirstRunFragment extends Fragment implements Fir
|
|
assumeNonNull(delegate.getProfileProviderSupplier().get()).getOriginalProfile();
|
|
|
|
assert TemplateUrlServiceFactory.getForProfile(profile).isLoaded();
|
|
- mSearchEnginePromoDialogType = LocaleManager.getInstance().getSearchEnginePromoShowType();
|
|
- if (mSearchEnginePromoDialogType != SearchEnginePromoType.DONT_SHOW) {
|
|
new DefaultSearchEngineDialogHelper(
|
|
mSearchEnginePromoDialogType,
|
|
LocaleManager.getInstance(),
|
|
engineLayout,
|
|
button,
|
|
getPageDelegate()::advanceToNextPage);
|
|
- }
|
|
|
|
return rootView;
|
|
}
|
|
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
|
|
@@ -23,6 +23,7 @@ import androidx.annotation.CallSuper;
|
|
import androidx.annotation.ColorInt;
|
|
import androidx.annotation.StringRes;
|
|
import androidx.annotation.VisibleForTesting;
|
|
+import androidx.fragment.app.Fragment;
|
|
import androidx.viewpager2.widget.ViewPager2;
|
|
|
|
import org.chromium.base.ActivityState;
|
|
@@ -46,8 +47,7 @@ import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
|
|
import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
|
|
import org.chromium.chrome.browser.profiles.Profile;
|
|
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.firstrun.ToSAndUMAFirstRunFragment;
|
|
import org.chromium.chrome.browser.ui.default_browser_promo.DefaultBrowserPromoUtils;
|
|
import org.chromium.chrome.browser.ui.edge_to_edge.EdgeToEdgeUtils;
|
|
import org.chromium.chrome.browser.ui.signin.DialogWhenLargeContentLayout;
|
|
@@ -283,7 +283,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);
|
|
@@ -308,9 +308,6 @@ public class FirstRunActivity extends FirstRunActivityBase implements FirstRunPa
|
|
//
|
|
// TODO(b/245912657): explicitly sign in supervised users in {@link
|
|
// FullscreenSigninMediator#handleContinueWithNative} rather than relying on SigninChecker.
|
|
- Profile originalProfile =
|
|
- assumeNonNull(getProfileProviderSupplier().get()).getOriginalProfile();
|
|
- SigninCheckerProvider.get(originalProfile);
|
|
|
|
assumeNonNull(mFreProperties);
|
|
mFirstRunFlowSequencer.updateFirstRunProperties(mFreProperties);
|
|
@@ -326,24 +323,6 @@ public class FirstRunActivity extends FirstRunActivityBase implements FirstRunPa
|
|
mFreProgressStates.add(MobileFreProgress.DEFAULT_SEARCH_ENGINE_SHOWN);
|
|
}
|
|
|
|
- // An optional history sync opt-in page, the visibility of this page will be decided on the
|
|
- // fly according to the situation.
|
|
- BooleanSupplier showHistorySync =
|
|
- () -> {
|
|
- if (ChromeFeatureList.isEnabled(ChromeFeatureList.DEFAULT_BROWSER_PROMO_FRE)) {
|
|
- return mFreProperties.getBoolean(SHOW_HISTORY_SYNC_PAGE)
|
|
- && !mHistorySyncStepCompleted;
|
|
- } else {
|
|
- return mFreProperties.getBoolean(SHOW_HISTORY_SYNC_PAGE);
|
|
- }
|
|
- };
|
|
- if (!showHistorySync.getAsBoolean()) {
|
|
- HistorySyncHelper historySyncHelper = HistorySyncHelper.getForProfile(originalProfile);
|
|
- historySyncHelper.recordHistorySyncNotShown(SigninAccessPoint.START_PAGE);
|
|
- }
|
|
- mPages.add(new FirstRunPage<>(HistorySyncFirstRunFragment.class, showHistorySync));
|
|
- mFreProgressStates.add(MobileFreProgress.HISTORY_SYNC_OPT_IN_SHOWN);
|
|
-
|
|
if (ChromeFeatureList.isEnabled(ChromeFeatureList.DEFAULT_BROWSER_PROMO_FRE)) {
|
|
int promoIndex = mPages.size();
|
|
BooleanSupplier showDefaultBrowserPromo =
|
|
@@ -608,6 +587,21 @@ public class FirstRunActivity extends FirstRunActivityBase implements FirstRunPa
|
|
return true;
|
|
}
|
|
|
|
+ @Override
|
|
+ public void onAttachFragment(Fragment fragment) {
|
|
+ if (!(fragment instanceof FirstRunFragment)) return;
|
|
+
|
|
+ FirstRunFragment page = (FirstRunFragment) fragment;
|
|
+ if (mNativeInitializationPromise.isFulfilled()) {
|
|
+ page.onNativeInitialized();
|
|
+ } else {
|
|
+ mNativeInitializationPromise.then(
|
|
+ (ignored) -> {
|
|
+ page.onNativeInitialized();
|
|
+ });
|
|
+ }
|
|
+ }
|
|
+
|
|
// Activity:
|
|
@Override
|
|
public void onRestoreInstanceState(@Nullable Bundle state) {
|
|
@@ -755,11 +749,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.deprecatedRecordMediumTimesHistogram(
|
|
- "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/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
|
|
@@ -35,14 +35,9 @@ import org.chromium.chrome.browser.partnercustomizations.PartnerBrowserCustomiza
|
|
import org.chromium.chrome.browser.profiles.Profile;
|
|
import org.chromium.chrome.browser.profiles.ProfileProvider;
|
|
import org.chromium.chrome.browser.search_engines.SearchEnginePromoType;
|
|
-import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
|
|
-import org.chromium.chrome.browser.signin.services.SigninPreferencesManager;
|
|
-import org.chromium.chrome.browser.ui.signin.history_sync.HistorySyncHelper;
|
|
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;
|
|
|
|
/**
|
|
@@ -69,30 +64,13 @@ public abstract class FirstRunFlowSequencer {
|
|
}
|
|
|
|
boolean shouldShowHistorySyncOptIn(boolean isChild) {
|
|
- assert mProfileSupplier.get() != null;
|
|
- Profile profile = mProfileSupplier.get().getOriginalProfile();
|
|
- HistorySyncHelper historySyncHelper = HistorySyncHelper.getForProfile(profile);
|
|
- if (isChild) {
|
|
- return !historySyncHelper.isHistorySyncDisabledByCustodian();
|
|
- }
|
|
- if (historySyncHelper.isHistorySyncDisabledByPolicy()
|
|
- || historySyncHelper.didAlreadyOptIn()) {
|
|
- return false;
|
|
- }
|
|
- // Show the page only to signed-in users.
|
|
- IdentityManager identityManager =
|
|
- IdentityServicesProvider.get().getIdentityManager(profile);
|
|
- assumeNonNull(identityManager);
|
|
- return identityManager.hasPrimaryAccount(ConsentLevel.SIGNIN);
|
|
+ return false;
|
|
}
|
|
|
|
/** @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 true;
|
|
}
|
|
}
|
|
|
|
@@ -143,18 +121,6 @@ public abstract class FirstRunFlowSequencer {
|
|
* method.
|
|
*/
|
|
void start() {
|
|
- AccountManagerFacadeProvider.getInstance()
|
|
- .getAccounts()
|
|
- .then(
|
|
- accounts -> {
|
|
- RecordHistogram.recordCount1MHistogram(
|
|
- "Signin.AndroidDeviceAccountsNumberWhenEnteringFRE",
|
|
- Math.min(accounts.size(), 2));
|
|
-
|
|
- assert !mAccountsAvailable;
|
|
- mAccountsAvailable = true;
|
|
- maybeProcessFreEnvironmentPreNative();
|
|
- });
|
|
}
|
|
|
|
@VisibleForTesting
|
|
@@ -173,9 +139,6 @@ public abstract class FirstRunFlowSequencer {
|
|
}
|
|
|
|
private void maybeProcessFreEnvironmentPreNative() {
|
|
- // Wait till both child account status and the list of accounts are available.
|
|
- if (mIsChild == null || !mAccountsAvailable) return;
|
|
-
|
|
if (mIsFlowKnown) return;
|
|
mIsFlowKnown = true;
|
|
onFlowIsKnown(mIsChild);
|
|
@@ -187,8 +150,8 @@ public abstract class FirstRunFlowSequencer {
|
|
* @param freProperties Resulting FRE properties bundle.
|
|
*/
|
|
public void updateFirstRunProperties(Bundle freProperties) {
|
|
- // fix chromium vanilla startup
|
|
- if (freProperties == null) return;
|
|
+ if (freProperties == null)
|
|
+ throw new RuntimeException("attempting to update null FRE properties");
|
|
assert freProperties != null;
|
|
freProperties.putBoolean(
|
|
FirstRunActivity.SHOW_HISTORY_SYNC_PAGE, shouldShowHistorySyncOptIn());
|
|
@@ -207,8 +170,6 @@ public abstract class FirstRunFlowSequencer {
|
|
|
|
// Mark the FRE flow as complete.
|
|
FirstRunStatus.setFirstRunFlowComplete(true);
|
|
- SigninPreferencesManager.getInstance()
|
|
- .setCctMismatchNoticeSuppressionPeriodStart(TimeUtils.currentTimeMillis());
|
|
}
|
|
|
|
/**
|
|
@@ -332,13 +293,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/FirstRunFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunFragment.java
|
|
--- a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunFragment.java
|
|
+++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunFragment.java
|
|
@@ -12,6 +12,9 @@ import org.chromium.build.annotations.Nullable;
|
|
/** This interface is implemented by FRE fragments. */
|
|
@NullMarked
|
|
public interface FirstRunFragment {
|
|
+
|
|
+ default void onNativeInitialized() {}
|
|
+
|
|
/**
|
|
* @see androidx.fragment.app.Fragment#getActivity().
|
|
*/
|
|
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
|
|
@@ -43,6 +43,7 @@ public class FirstRunUtils {
|
|
* collect stats.
|
|
*/
|
|
static void acceptTermsOfService(boolean allowMetricsAndCrashUploading) {
|
|
+ allowMetricsAndCrashUploading = false;
|
|
UmaSessionStats.changeMetricsReportingConsent(
|
|
allowMetricsAndCrashUploading, ChangeMetricsReportingStateCalledFrom.UI_FIRST_RUN);
|
|
setEulaAccepted();
|
|
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,321 @@
|
|
+// 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.OmahaPrefUtils;
|
|
+
|
|
+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.ui.modaldialog.ModalDialogManagerHolder;
|
|
+import org.chromium.ui.text.ChromeClickableSpan;
|
|
+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(false);
|
|
+ }
|
|
+
|
|
+ private void updateView() {
|
|
+ // Avoid early calls.
|
|
+ if (getPageDelegate() == null) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ updateTosText();
|
|
+
|
|
+ updateReportCheckbox();
|
|
+ }
|
|
+
|
|
+ private SpanInfo buildPrivacyPolicyLink(String suffix, int url) {
|
|
+ ChromeClickableSpan clickableSpan =
|
|
+ new ChromeClickableSpan(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.privacy_link1));
|
|
+
|
|
+ spans.add(buildPrivacyPolicyLink("2", R.string.privacy_link2));
|
|
+
|
|
+ spans.add(buildPrivacyPolicyLink("3", R.string.privacy_link3));
|
|
+
|
|
+ spans.add(buildPrivacyPolicyLink("4", R.string.privacy_link4));
|
|
+
|
|
+ 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(false);
|
|
+ }
|
|
+
|
|
+ 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 = OmahaPrefUtils.getSharedPreferences().edit();
|
|
+ sharedPreferenceEditor.putBoolean(OmahaPrefUtils.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() {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ // 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/android/java/src/org/chromium/chrome/browser/signin/AppRestrictionSupplier.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/AppRestrictionSupplier.java
|
|
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/AppRestrictionSupplier.java
|
|
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/AppRestrictionSupplier.java
|
|
@@ -67,6 +67,10 @@ public class AppRestrictionSupplier implements OneshotSupplier<Boolean> {
|
|
private void initialize() {
|
|
ThreadUtils.assertOnUiThread();
|
|
long startTime = SystemClock.elapsedRealtime();
|
|
+ if ((true)) {
|
|
+ onRestrictionDetected(false, startTime);
|
|
+ return;
|
|
+ }
|
|
|
|
// This is an imperfect system, and can sometimes return true when there will not actually
|
|
// be any app restrictions. But we do not have parsing logic in Java to understand if the
|
|
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/ChildAccountStatusSupplier.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/ChildAccountStatusSupplier.java
|
|
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/ChildAccountStatusSupplier.java
|
|
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/ChildAccountStatusSupplier.java
|
|
@@ -42,7 +42,11 @@ public class ChildAccountStatusSupplier implements OneshotSupplier<Boolean> {
|
|
*/
|
|
public ChildAccountStatusSupplier(
|
|
AccountManagerFacade accountManagerFacade, AppRestrictionSupplier appRestrictionInfo) {
|
|
+ mHasRestriction = false;
|
|
+ mChildAccountStatusFromAccountManagerFacade = false;
|
|
mChildAccountStatusStartTime = SystemClock.elapsedRealtime();
|
|
+ mValue.set(false);
|
|
+ if ((true)) return;
|
|
|
|
appRestrictionInfo.onAvailable(this::onAppRestrictionDetected);
|
|
|
|
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/FullscreenSigninAndHistorySyncActivityBase.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/FullscreenSigninAndHistorySyncActivityBase.java
|
|
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/FullscreenSigninAndHistorySyncActivityBase.java
|
|
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/FullscreenSigninAndHistorySyncActivityBase.java
|
|
@@ -25,8 +25,6 @@ import org.chromium.chrome.browser.profiles.ProfileProvider;
|
|
import org.chromium.chrome.browser.ui.system.StatusBarColorController;
|
|
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 and fullscreen signin and history sync promos. */
|
|
@NullMarked
|
|
@@ -85,9 +83,7 @@ public abstract class FullscreenSigninAndHistorySyncActivityBase extends AsyncIn
|
|
@Override
|
|
@CallSuper
|
|
public void triggerLayoutInflation() {
|
|
- AccountManagerFacade accountManagerFacade = AccountManagerFacadeProvider.getInstance();
|
|
- mChildAccountStatusSupplier =
|
|
- new ChildAccountStatusSupplier(accountManagerFacade, mAppRestrictionSupplier);
|
|
+ mChildAccountStatusSupplier = new ChildAccountStatusSupplier(null, null);
|
|
|
|
// TODO(crbug.com/40939710): Find the underlying issue causing the status bar not to be set
|
|
// during re-FRE, this is just a temporary visual fix.
|
|
diff --git a/chrome/browser/locale/java/src/org/chromium/chrome/browser/locale/LocaleManagerDelegate.java b/chrome/browser/locale/java/src/org/chromium/chrome/browser/locale/LocaleManagerDelegate.java
|
|
--- a/chrome/browser/locale/java/src/org/chromium/chrome/browser/locale/LocaleManagerDelegate.java
|
|
+++ b/chrome/browser/locale/java/src/org/chromium/chrome/browser/locale/LocaleManagerDelegate.java
|
|
@@ -389,8 +389,9 @@ public class LocaleManagerDelegate {
|
|
|
|
/** @see LocaleManager#getSearchEnginesForPromoDialog */
|
|
public List<TemplateUrl> getSearchEnginesForPromoDialog(@SearchEnginePromoType int promoType) {
|
|
- throw new IllegalStateException(
|
|
- "Not applicable unless existing or new promos are required");
|
|
+ TemplateUrlService templateUrlService =
|
|
+ TemplateUrlServiceFactory.getForProfile(ProfileManager.getLastUsedRegularProfile());
|
|
+ return templateUrlService.getTemplateUrls();
|
|
}
|
|
|
|
/** @see LocaleManager#onUserSearchEngineChoice */
|
|
diff --git a/chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Welcome-screen.grdp b/chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Welcome-screen.grdp
|
|
new file mode 100644
|
|
--- /dev/null
|
|
+++ b/chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Welcome-screen.grdp
|
|
@@ -0,0 +1,27 @@
|
|
+<?xml version="1.0" encoding="utf-8"?>
|
|
+<grit-part>
|
|
+ <message name="IDS_FRE_CONTINUE" desc="Text for first page accept and continue button [CHAR_LIMIT=20]">
|
|
+ Continue
|
|
+ </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_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>Cromite Privacy statement<ph name="END_PRIVACY_LINK2"></PRIVACY_LINK2></ph>.
|
|
+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 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_PRIVACY_LINK1" desc="URL for Bromite wiki page about ad blocking" translateable="false">
|
|
+ https://github.com/uazo/cromite/blob/master/docs/ADBLOCKING.md
|
|
+ </message>
|
|
+ <message name="IDS_PRIVACY_LINK2" desc="URL for privacy policy for the ad block updater" translateable="false">
|
|
+ https://github.com/uazo/cromite/blob/master/docs/PRIVACY_POLICY.md
|
|
+ </message>
|
|
+ <message name="IDS_PRIVACY_LINK3" desc="URL for Bromite wiki page about automatic updates" translateable="false">
|
|
+ https://github.com/uazo/cromite/blob/master/docs/AUTOMATIC_UPDATES.md
|
|
+ </message>
|
|
+ <message name="IDS_PRIVACY_LINK4" 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>
|
|
+</grit-part>
|
|
--
|