1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.chrome.features.start_surface;
6 
7 import androidx.annotation.VisibleForTesting;
8 
9 import org.chromium.base.Log;
10 import org.chromium.base.SysUtils;
11 import org.chromium.base.metrics.RecordHistogram;
12 import org.chromium.chrome.browser.flags.BooleanCachedFieldTrialParameter;
13 import org.chromium.chrome.browser.flags.CachedFeatureFlags;
14 import org.chromium.chrome.browser.flags.ChromeFeatureList;
15 import org.chromium.chrome.browser.flags.IntCachedFieldTrialParameter;
16 import org.chromium.chrome.browser.flags.StringCachedFieldTrialParameter;
17 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
18 import org.chromium.chrome.browser.preferences.Pref;
19 import org.chromium.chrome.browser.preferences.PrefChangeRegistrar;
20 import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
21 import org.chromium.chrome.browser.profiles.Profile;
22 import org.chromium.components.user_prefs.UserPrefs;
23 
24 /**
25  * Flag configuration for Start Surface. Source of truth for whether it should be enabled and
26  * which variation should be used.
27  */
28 public class StartSurfaceConfiguration {
29     private static final String TAG = "StartSurfaceConfig";
30     public static final StringCachedFieldTrialParameter START_SURFACE_VARIATION =
31             new StringCachedFieldTrialParameter(
32                     ChromeFeatureList.START_SURFACE_ANDROID, "start_surface_variation", "");
33     public static final BooleanCachedFieldTrialParameter START_SURFACE_EXCLUDE_MV_TILES =
34             new BooleanCachedFieldTrialParameter(
35                     ChromeFeatureList.START_SURFACE_ANDROID, "exclude_mv_tiles", false);
36     public static final BooleanCachedFieldTrialParameter
37             START_SURFACE_HIDE_INCOGNITO_SWITCH_NO_TAB =
38                     new BooleanCachedFieldTrialParameter(ChromeFeatureList.START_SURFACE_ANDROID,
39                             "hide_switch_when_no_incognito_tabs", false);
40 
41     // This parameter hides the incognito switch on non-incognito Start Surface homepage but still
42     // shows the switch in Grid tab switcher. It is different from
43     // {@link START_SURFACE_HIDE_INCOGNITO_SWITCH_NO_TAB} which hides the incognito switch in both
44     // Start Surface homepage and Grid tab switcher whenever there is no incognito tab.
45     public static final BooleanCachedFieldTrialParameter START_SURFACE_HIDE_INCOGNITO_SWITCH =
46             new BooleanCachedFieldTrialParameter(
47                     ChromeFeatureList.START_SURFACE_ANDROID, "hide_incognito_switch", false);
48 
49     public static final BooleanCachedFieldTrialParameter START_SURFACE_LAST_ACTIVE_TAB_ONLY =
50             new BooleanCachedFieldTrialParameter(
51                     ChromeFeatureList.START_SURFACE_ANDROID, "show_last_active_tab_only", false);
52     public static final BooleanCachedFieldTrialParameter START_SURFACE_SHOW_STACK_TAB_SWITCHER =
53             new BooleanCachedFieldTrialParameter(
54                     ChromeFeatureList.START_SURFACE_ANDROID, "show_stack_tab_switcher", false);
55     public static final BooleanCachedFieldTrialParameter START_SURFACE_OPEN_NTP_INSTEAD_OF_START =
56             new BooleanCachedFieldTrialParameter(
57                     ChromeFeatureList.START_SURFACE_ANDROID, "open_ntp_instead_of_start", false);
58     public static final StringCachedFieldTrialParameter START_SURFACE_OMNIBOX_SCROLL_MODE =
59             new StringCachedFieldTrialParameter(
60                     ChromeFeatureList.START_SURFACE_ANDROID, "omnibox_scroll_mode", "");
61 
62     private static final String TRENDY_ENABLED_PARAM = "trendy_enabled";
63     public static final BooleanCachedFieldTrialParameter TRENDY_ENABLED =
64             new BooleanCachedFieldTrialParameter(
65                     ChromeFeatureList.START_SURFACE_ANDROID, TRENDY_ENABLED_PARAM, false);
66 
67     private static final String SUCCESS_MIN_PERIOD_MS_PARAM = "trendy_success_min_period_ms";
68     public static final IntCachedFieldTrialParameter TRENDY_SUCCESS_MIN_PERIOD_MS =
69             new IntCachedFieldTrialParameter(ChromeFeatureList.START_SURFACE_ANDROID,
70                     SUCCESS_MIN_PERIOD_MS_PARAM, 86400_000);
71 
72     private static final String FAILURE_MIN_PERIOD_MS_PARAM = "trendy_failure_min_period_ms";
73     public static final IntCachedFieldTrialParameter TRENDY_FAILURE_MIN_PERIOD_MS =
74             new IntCachedFieldTrialParameter(
75                     ChromeFeatureList.START_SURFACE_ANDROID, FAILURE_MIN_PERIOD_MS_PARAM, 7200_000);
76 
77     private static final String TRENDY_ENDPOINT_PARAM = "trendy_endpoint";
78     public static final StringCachedFieldTrialParameter TRENDY_ENDPOINT =
79             new StringCachedFieldTrialParameter(ChromeFeatureList.START_SURFACE_ANDROID,
80                     TRENDY_ENDPOINT_PARAM,
81                     "https://trends.google.com/trends/trendingsearches/daily/rss"
82                             + "?lite=true&safe=true&geo=");
83 
84     private static final String STARTUP_UMA_PREFIX = "Startup.Android.";
85     private static final String INSTANT_START_SUBFIX = ".Instant";
86     private static final String REGULAR_START_SUBFIX = ".NoInstant";
87 
88     /**
89      * @return Whether the Start Surface is enabled.
90      */
isStartSurfaceEnabled()91     public static boolean isStartSurfaceEnabled() {
92         return CachedFeatureFlags.isEnabled(ChromeFeatureList.START_SURFACE_ANDROID)
93                 && !SysUtils.isLowEndDevice();
94     }
95 
96     /**
97      * @return Whether the Start Surface SinglePane is enabled.
98      */
isStartSurfaceSinglePaneEnabled()99     public static boolean isStartSurfaceSinglePaneEnabled() {
100         return isStartSurfaceEnabled() && START_SURFACE_VARIATION.getValue().equals("single");
101     }
102 
103     /**
104      *@return Whether the Start Surface Stack Tab Switcher is enabled.
105      */
isStartSurfaceStackTabSwitcherEnabled()106     public static boolean isStartSurfaceStackTabSwitcherEnabled() {
107         return isStartSurfaceSinglePaneEnabled()
108                 && START_SURFACE_SHOW_STACK_TAB_SWITCHER.getValue();
109     }
110 
111     /**
112      * Add an observer to keep {@link ChromePreferenceKeys.FEED_ARTICLES_LIST_VISIBLE} consistent
113      * with {@link Pref.ARTICLES_LIST_VISIBLE}.
114      */
addFeedVisibilityObserver()115     public static void addFeedVisibilityObserver() {
116         updateFeedVisibility();
117         PrefChangeRegistrar prefChangeRegistrar = new PrefChangeRegistrar();
118         prefChangeRegistrar.addObserver(
119                 Pref.ARTICLES_LIST_VISIBLE, StartSurfaceConfiguration::updateFeedVisibility);
120     }
121 
updateFeedVisibility()122     private static void updateFeedVisibility() {
123         SharedPreferencesManager.getInstance().writeBoolean(
124                 ChromePreferenceKeys.FEED_ARTICLES_LIST_VISIBLE,
125                 UserPrefs.get(Profile.getLastUsedRegularProfile())
126                         .getBoolean(Pref.ARTICLES_LIST_VISIBLE));
127     }
128 
129     /**
130      * @return Whether the Feed articles are visible.
131      */
getFeedArticlesVisibility()132     public static boolean getFeedArticlesVisibility() {
133         return SharedPreferencesManager.getInstance().readBoolean(
134                 ChromePreferenceKeys.FEED_ARTICLES_LIST_VISIBLE, true);
135     }
136 
137     @VisibleForTesting
setFeedVisibilityForTesting(boolean isVisible)138     static void setFeedVisibilityForTesting(boolean isVisible) {
139         SharedPreferencesManager.getInstance().writeBoolean(
140                 ChromePreferenceKeys.FEED_ARTICLES_LIST_VISIBLE, isVisible);
141     }
142 
143     /**
144      * Records histograms of showing the StartSurface. Nothing will be recorded if timeDurationMs
145      * isn't valid.
146      */
recordHistogram(String name, long timeDurationMs, boolean isInstantStart)147     public static void recordHistogram(String name, long timeDurationMs, boolean isInstantStart) {
148         if (timeDurationMs < 0) return;
149         Log.i(TAG, "Recorded %s = %d ms", getHistogramName(name, isInstantStart), timeDurationMs);
150         RecordHistogram.recordTimesHistogram(
151                 getHistogramName(name, isInstantStart), timeDurationMs);
152     }
153 
154     @VisibleForTesting
getHistogramName(String name, boolean isInstantStart)155     public static String getHistogramName(String name, boolean isInstantStart) {
156         return STARTUP_UMA_PREFIX + name
157                 + (isInstantStart ? INSTANT_START_SUBFIX : REGULAR_START_SUBFIX);
158     }
159     /**
160      * @param isDense Whether the placeholder of Feed is dense. This depends on whether the first
161      *         article card of Feed is dense.
162      */
setFeedPlaceholderDense(boolean isDense)163     public static void setFeedPlaceholderDense(boolean isDense) {
164         SharedPreferencesManager.getInstance().writeBoolean(
165                 ChromePreferenceKeys.FEED_PLACEHOLDER_DENSE, isDense);
166     }
167 
168     /**
169      * @return Whether the placeholder of Feed is dense.
170      */
isFeedPlaceholderDense()171     public static boolean isFeedPlaceholderDense() {
172         return SharedPreferencesManager.getInstance().readBoolean(
173                 ChromePreferenceKeys.FEED_PLACEHOLDER_DENSE, false);
174     }
175 }
176