1// Copyright 2013 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#include "chrome/browser/bookmarks/enhanced_bookmarks_features.h"
6
7#include "base/command_line.h"
8#include "base/metrics/histogram.h"
9#include "base/prefs/pref_service.h"
10#include "base/prefs/scoped_user_pref_update.h"
11#include "base/sha1.h"
12#include "base/strings/string_number_conversions.h"
13#include "chrome/browser/browser_process.h"
14#include "chrome/browser/flags_storage.h"
15#include "chrome/browser/profiles/profile.h"
16#include "chrome/browser/signin/signin_manager_factory.h"
17#include "chrome/common/chrome_switches.h"
18#include "chrome/common/pref_names.h"
19#include "components/signin/core/browser/signin_manager.h"
20#include "components/sync_driver/pref_names.h"
21#include "components/variations/variations_associated_data.h"
22#include "extensions/common/features/feature.h"
23#include "extensions/common/features/feature_provider.h"
24
25namespace {
26
27const char kFieldTrialName[] = "EnhancedBookmarks";
28
29// Get extension id from Finch EnhancedBookmarks group parameters.
30std::string GetEnhancedBookmarksExtensionIdFromFinch() {
31  return variations::GetVariationParamValue(kFieldTrialName, "id");
32}
33
34// Returns true if enhanced bookmarks experiment is enabled from Finch.
35bool IsEnhancedBookmarksExperimentEnabledFromFinch() {
36  const std::string ext_id = GetEnhancedBookmarksExtensionIdFromFinch();
37#if defined(OS_ANDROID)
38  return !ext_id.empty();
39#else
40  const extensions::FeatureProvider* feature_provider =
41      extensions::FeatureProvider::GetPermissionFeatures();
42  extensions::Feature* feature = feature_provider->GetFeature("metricsPrivate");
43  return feature && feature->IsIdInWhitelist(ext_id);
44#endif
45}
46
47};  // namespace
48
49bool GetBookmarksExperimentExtensionID(const PrefService* user_prefs,
50                                       std::string* extension_id) {
51  BookmarksExperimentState bookmarks_experiment_state =
52      static_cast<BookmarksExperimentState>(user_prefs->GetInteger(
53          sync_driver::prefs::kEnhancedBookmarksExperimentEnabled));
54  if (bookmarks_experiment_state == BOOKMARKS_EXPERIMENT_ENABLED_FROM_FINCH) {
55    *extension_id = GetEnhancedBookmarksExtensionIdFromFinch();
56    return !extension_id->empty();
57  }
58  if (bookmarks_experiment_state == BOOKMARKS_EXPERIMENT_ENABLED) {
59    *extension_id = user_prefs->GetString(
60        sync_driver::prefs::kEnhancedBookmarksExtensionId);
61    return !extension_id->empty();
62  }
63
64  return false;
65}
66
67void UpdateBookmarksExperimentState(
68    PrefService* user_prefs,
69    PrefService* local_state,
70    bool user_signed_in,
71    BookmarksExperimentState experiment_enabled_from_sync) {
72 PrefService* flags_storage = local_state;
73#if defined(OS_CHROMEOS)
74  // Chrome OS is using user prefs for flags storage.
75  flags_storage = user_prefs;
76#endif
77
78  BookmarksExperimentState bookmarks_experiment_state_before =
79      static_cast<BookmarksExperimentState>(user_prefs->GetInteger(
80          sync_driver::prefs::kEnhancedBookmarksExperimentEnabled));
81  // If user signed out, clear possible previous state.
82  if (!user_signed_in) {
83    bookmarks_experiment_state_before = BOOKMARKS_EXPERIMENT_NONE;
84    ForceFinchBookmarkExperimentIfNeeded(flags_storage,
85        BOOKMARKS_EXPERIMENT_NONE);
86  }
87
88  // kEnhancedBookmarksExperiment flag could have values "", "1" and "0".
89  // "0" - user opted out.
90  bool opt_out = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
91                     switches::kEnhancedBookmarksExperiment) == "0";
92
93  BookmarksExperimentState bookmarks_experiment_new_state =
94      BOOKMARKS_EXPERIMENT_NONE;
95
96  if (IsEnhancedBookmarksExperimentEnabledFromFinch() && !user_signed_in) {
97    if (opt_out) {
98      // Experiment enabled but user opted out.
99      bookmarks_experiment_new_state = BOOKMARKS_EXPERIMENT_OPT_OUT_FROM_FINCH;
100    } else {
101      // Experiment enabled.
102      bookmarks_experiment_new_state = BOOKMARKS_EXPERIMENT_ENABLED_FROM_FINCH;
103    }
104  } else if (experiment_enabled_from_sync == BOOKMARKS_EXPERIMENT_ENABLED) {
105    // Experiment enabled from Chrome sync.
106    if (opt_out) {
107      // Experiment enabled but user opted out.
108      bookmarks_experiment_new_state =
109          BOOKMARKS_EXPERIMENT_ENABLED_USER_OPT_OUT;
110    } else {
111      // Experiment enabled.
112      bookmarks_experiment_new_state = BOOKMARKS_EXPERIMENT_ENABLED;
113    }
114  } else if (experiment_enabled_from_sync == BOOKMARKS_EXPERIMENT_NONE) {
115    // Experiment is not enabled from Chrome sync.
116    bookmarks_experiment_new_state = BOOKMARKS_EXPERIMENT_NONE;
117  } else if (bookmarks_experiment_state_before ==
118             BOOKMARKS_EXPERIMENT_ENABLED) {
119    if (opt_out) {
120      // Experiment enabled but user opted out.
121      bookmarks_experiment_new_state =
122          BOOKMARKS_EXPERIMENT_ENABLED_USER_OPT_OUT;
123    } else {
124      bookmarks_experiment_new_state = BOOKMARKS_EXPERIMENT_ENABLED;
125    }
126  } else if (bookmarks_experiment_state_before ==
127             BOOKMARKS_EXPERIMENT_ENABLED_USER_OPT_OUT) {
128    if (opt_out) {
129      bookmarks_experiment_new_state =
130          BOOKMARKS_EXPERIMENT_ENABLED_USER_OPT_OUT;
131    } else {
132      // User opted in again.
133      bookmarks_experiment_new_state = BOOKMARKS_EXPERIMENT_ENABLED;
134    }
135  }
136
137#if defined(OS_ANDROID)
138  bool opt_in = !opt_out
139      && CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
140          switches::kEnhancedBookmarksExperiment) == "1";
141  if (opt_in && bookmarks_experiment_new_state == BOOKMARKS_EXPERIMENT_NONE)
142    bookmarks_experiment_new_state = BOOKMARKS_EXPERIMENT_ENABLED;
143#endif
144
145  UMA_HISTOGRAM_ENUMERATION("EnhancedBookmarks.SyncExperimentState",
146                            bookmarks_experiment_new_state,
147                            BOOKMARKS_EXPERIMENT_ENUM_SIZE);
148  user_prefs->SetInteger(
149      sync_driver::prefs::kEnhancedBookmarksExperimentEnabled,
150      bookmarks_experiment_new_state);
151  ForceFinchBookmarkExperimentIfNeeded(flags_storage,
152                                       bookmarks_experiment_new_state);
153}
154
155void InitBookmarksExperimentState(Profile* profile) {
156  SigninManagerBase* signin = SigninManagerFactory::GetForProfile(profile);
157  bool is_signed_in = signin && signin->IsAuthenticated();
158  UpdateBookmarksExperimentState(
159      profile->GetPrefs(),
160      g_browser_process->local_state(),
161      is_signed_in,
162      BOOKMARKS_EXPERIMENT_ENABLED_FROM_SYNC_UNKNOWN);
163}
164
165void ForceFinchBookmarkExperimentIfNeeded(
166    PrefService* flags_storage,
167    BookmarksExperimentState bookmarks_experiment_state) {
168  if (!flags_storage)
169    return;
170  ListPrefUpdate update(flags_storage, prefs::kEnabledLabsExperiments);
171  base::ListValue* experiments_list = update.Get();
172  if (!experiments_list)
173    return;
174  size_t index;
175  if (bookmarks_experiment_state == BOOKMARKS_EXPERIMENT_NONE) {
176    experiments_list->Remove(
177        base::StringValue(switches::kManualEnhancedBookmarks), &index);
178    experiments_list->Remove(
179        base::StringValue(switches::kManualEnhancedBookmarksOptout), &index);
180  } else if (bookmarks_experiment_state == BOOKMARKS_EXPERIMENT_ENABLED) {
181    experiments_list->Remove(
182        base::StringValue(switches::kManualEnhancedBookmarksOptout), &index);
183    experiments_list->AppendIfNotPresent(
184        new base::StringValue(switches::kManualEnhancedBookmarks));
185  } else if (bookmarks_experiment_state ==
186                 BOOKMARKS_EXPERIMENT_ENABLED_USER_OPT_OUT) {
187    experiments_list->Remove(
188        base::StringValue(switches::kManualEnhancedBookmarks), &index);
189    experiments_list->AppendIfNotPresent(
190        new base::StringValue(switches::kManualEnhancedBookmarksOptout));
191  }
192}
193
194bool IsEnhancedBookmarksExperimentEnabled(
195    about_flags::FlagsStorage* flags_storage) {
196#if defined(OS_CHROMEOS)
197  // We are not setting command line flags on Chrome OS to avoid browser restart
198  // but still have flags in flags_storage. So check flags_storage instead.
199  const std::set<std::string> flags = flags_storage->GetFlags();
200  if (flags.find(switches::kManualEnhancedBookmarks) != flags.end())
201    return true;
202  if (flags.find(switches::kManualEnhancedBookmarksOptout) != flags.end())
203    return true;
204#else
205  CommandLine* command_line = CommandLine::ForCurrentProcess();
206  if (command_line->HasSwitch(switches::kManualEnhancedBookmarks) ||
207      command_line->HasSwitch(switches::kManualEnhancedBookmarksOptout)) {
208    return true;
209  }
210#endif
211
212  return IsEnhancedBookmarksExperimentEnabledFromFinch();
213}
214
215#if defined(OS_ANDROID)
216bool IsEnhancedBookmarkImageFetchingEnabled(const PrefService* user_prefs) {
217  if (IsEnhancedBookmarksEnabled(user_prefs))
218    return true;
219
220  // Salient images are collected from visited bookmarked pages even if the
221  // enhanced bookmark feature is turned off. This is to have some images
222  // available so that in the future, when the feature is turned on, the user
223  // experience is not a big list of flat colors. However as a precautionary
224  // measure it is possible to disable this collection of images from finch.
225  std::string disable_fetching = variations::GetVariationParamValue(
226      kFieldTrialName, "DisableImagesFetching");
227  return disable_fetching.empty();
228}
229
230bool IsEnhancedBookmarksEnabled(const PrefService* user_prefs) {
231  BookmarksExperimentState bookmarks_experiment_state =
232      static_cast<BookmarksExperimentState>(user_prefs->GetInteger(
233          sync_driver::prefs::kEnhancedBookmarksExperimentEnabled));
234  return bookmarks_experiment_state == BOOKMARKS_EXPERIMENT_ENABLED ||
235      bookmarks_experiment_state == BOOKMARKS_EXPERIMENT_ENABLED_FROM_FINCH;
236}
237#endif
238
239bool IsEnableDomDistillerSet() {
240  if (CommandLine::ForCurrentProcess()->
241      HasSwitch(switches::kEnableDomDistiller)) {
242    return true;
243  }
244  if (variations::GetVariationParamValue(
245          kFieldTrialName, "enable-dom-distiller") == "1")
246    return true;
247
248  return false;
249}
250
251bool IsEnableSyncArticlesSet() {
252  if (CommandLine::ForCurrentProcess()->
253      HasSwitch(switches::kEnableSyncArticles)) {
254    return true;
255  }
256  if (variations::GetVariationParamValue(
257          kFieldTrialName, "enable-sync-articles") == "1")
258    return true;
259
260  return false;
261}
262