default_pinned_apps_field_trial.cc revision 1e9bf3e0803691d0a228da41fc608347b6db4340
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/chromeos/login/default_pinned_apps_field_trial.h"
6
7#include "base/basictypes.h"
8#include "base/command_line.h"
9#include "base/memory/ref_counted.h"
10#include "base/metrics/field_trial.h"
11#include "base/metrics/histogram.h"
12#include "base/prefs/pref_registry_simple.h"
13#include "base/prefs/pref_service.h"
14#include "base/prefs/scoped_user_pref_update.h"
15#include "base/values.h"
16#include "chrome/browser/browser_process.h"
17#include "chrome/browser/chromeos/login/user_manager.h"
18#include "chrome/browser/prefs/pref_service_syncable.h"
19#include "chrome/browser/profiles/profile.h"
20#include "chrome/browser/profiles/profile_manager.h"
21#include "chrome/browser/ui/ash/chrome_launcher_prefs.h"
22#include "chrome/common/chrome_switches.h"
23#include "chrome/common/extensions/extension_constants.h"
24#include "chrome/common/pref_names.h"
25
26namespace chromeos {
27namespace default_pinned_apps_field_trial {
28
29namespace {
30
31// Name of the default pinned app experiment.
32const char kExperimentName[] = "DefaultPinnedApps";
33
34// Name of a local state pref to store the list of users that participate
35// the experiment.
36const char kExperimentUsers[] = "DefaultPinnedAppsExperimentUsers";
37
38// Alternate default pinned apps.
39const char* kAlternateDefaultPinnedApps[] = {
40  extension_misc::kGmailAppId,
41  extension_misc::kGoogleDocAppId,
42  extension_misc::kGoogleSheetsAppId,
43  extension_misc::kGoogleSlidesAppId,
44  extension_misc::kGooglePlayMusicAppId,
45};
46
47// Global state of trial setup.
48bool trial_configured = false;
49int alternate_group = base::FieldTrial::kNotFinalized;
50
51// Returns true if user participates the experiment.
52bool IsUserInExperiment(const std::string& username) {
53  const base::ListValue* users =
54      g_browser_process->local_state()->GetList(kExperimentUsers);
55  return users && users->Find(base::StringValue(username)) != users->end();
56}
57
58// Adds user to the experiment user list.
59void AddUserToExperiment(const std::string& username) {
60  ListPrefUpdate users(g_browser_process->local_state(), kExperimentUsers);
61  users->AppendString(username);
62}
63
64// Removes user from the experiment user list.
65void RemoveUserFromExperiment(const std::string& username) {
66  ListPrefUpdate users(g_browser_process->local_state(), kExperimentUsers);
67  users->Remove(base::StringValue(username), NULL);
68}
69
70// Gets click target type for given app id. Returns false if the app id is
71// not interesting.
72bool GetClickTargetForApp(const std::string& app_id,
73                          ClickTarget* click_target) {
74  static const struct AppIdToClickTarget {
75    const char* app_id;
76    ClickTarget click_target;
77  } kApps[] = {
78      { extension_misc::kChromeAppId, CHROME },
79      { extension_misc::kGmailAppId, GMAIL },
80      { extension_misc::kGoogleSearchAppId, SEARCH },
81      { extension_misc::kYoutubeAppId, YOUTUBE },
82      { extension_misc::kGoogleDocAppId, DOC },
83      { extension_misc::kGoogleSheetsAppId, SHEETS },
84      { extension_misc::kGoogleSlidesAppId, SLIDES },
85      { extension_misc::kGooglePlayMusicAppId, PLAY_MUSIC },
86  };
87
88  // kApps should define an app id for all click types except APP_LAUNCHER.
89  COMPILE_ASSERT(ARRAYSIZE_UNSAFE(kApps) == CLICK_TARGET_COUNT - 1,
90                 app_to_click_target_incorrect_size);
91
92  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kApps); ++i) {
93    if (app_id == kApps[i].app_id) {
94      *click_target = kApps[i].click_target;
95      return true;
96    }
97  }
98
99  return false;
100}
101
102// Check if the current user has default pinned apps pulled from sync. If that
103// is the case, kick the user out of the trial and returns true.
104bool ProcessIfUserHasSyncedAppPins() {
105  static bool has_synced_pref = false;
106  if (has_synced_pref)
107    return true;
108
109  Profile* user_profile = ProfileManager::GetDefaultProfile();
110  has_synced_pref = PrefServiceSyncable::FromProfile(user_profile)
111      ->IsPrefSynced(prefs::kPinnedLauncherApps);
112  if (has_synced_pref) {
113    const std::string username = UserManager::Get()->GetActiveUser()->email();
114    RemoveUserFromExperiment(username);
115  }
116
117  return has_synced_pref;
118}
119
120}  // namespace
121
122void RegisterPrefs(PrefRegistrySimple* registry) {
123  registry->RegisterListPref(kExperimentUsers);
124}
125
126void SetupTrial() {
127  // No trial if multiple profiles are enabled.
128  if (CommandLine::ForCurrentProcess()->HasSwitch(::switches::kMultiProfiles))
129    return;
130
131  // SetupForUser should only be called once for single profile mode.
132  DCHECK(!trial_configured);
133
134  const base::FieldTrial::Probability kDivisor = 100;
135  scoped_refptr<base::FieldTrial> trial =
136      base::FieldTrialList::FactoryGetFieldTrial(
137          kExperimentName, kDivisor, "Existing", 2013, 12, 31,
138          base::FieldTrial::ONE_TIME_RANDOMIZED, NULL);
139
140  // Split 50/50 between "Control" and "Alternamte" group for new user.
141  // Existing users already have their default pinned apps and have the trial
142  // disabled to go to "Existing" group.
143  trial->AppendGroup("Control", 50);
144  alternate_group = trial->AppendGroup("Alternate", 50);
145}
146
147void SetupForUser(const std::string& username, bool is_new_user) {
148  base::FieldTrial* trial = base::FieldTrialList::Find(kExperimentName);
149  if (!trial)
150    return;
151
152  trial_configured = true;
153
154  if (is_new_user) {
155    AddUserToExperiment(username);
156    return;
157  }
158
159  if (!IsUserInExperiment(username))
160    trial->Disable();
161}
162
163base::ListValue* GetAlternateDefaultPinnedApps() {
164  // This function is called for login manager profile, which happens
165  // before any user signs in. Returns NULL in this case. The case is covered
166  // by NULL trial check below but checking a boolean flag is faster.
167  if (!trial_configured)
168    return NULL;
169
170  base::FieldTrial* trial = base::FieldTrialList::Find(kExperimentName);
171  if (!trial || trial->group() != alternate_group)
172    return NULL;
173
174  scoped_ptr<base::ListValue> apps(new base::ListValue);
175  for (size_t i = 0; i < arraysize(kAlternateDefaultPinnedApps); ++i)
176    apps->Append(ash::CreateAppDict(kAlternateDefaultPinnedApps[i]));
177
178  return apps.release();
179}
180
181void RecordShelfClick(ClickTarget click_target) {
182  // The experiment does not include certain user types, such as public account,
183  // retail mode user, local user and ephemeral user.
184  if (!trial_configured)
185    return;
186
187  if (ProcessIfUserHasSyncedAppPins())
188    return;
189
190  UMA_HISTOGRAM_ENUMERATION("Cros.ClickOnShelf",
191                            click_target,
192                            CLICK_TARGET_COUNT);
193}
194
195void RecordShelfAppClick(const std::string& app_id) {
196  ClickTarget click_target = CLICK_TARGET_COUNT;
197  if (GetClickTargetForApp(app_id, &click_target))
198    RecordShelfClick(click_target);
199}
200
201void ResetStateForTest() {
202  trial_configured = false;
203  alternate_group = base::FieldTrial::kNotFinalized;
204}
205
206}  // namespace default_pinned_apps_field_trial
207}  // namespace chromeos
208