Home | History | Annotate | Download | only in login
      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/values.h"
     15 #include "chrome/browser/browser_process.h"
     16 #include "chrome/browser/chromeos/login/user_manager.h"
     17 #include "chrome/browser/prefs/pref_service_syncable.h"
     18 #include "chrome/browser/prefs/scoped_user_pref_update.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 
     26 namespace chromeos {
     27 namespace default_pinned_apps_field_trial {
     28 
     29 namespace {
     30 
     31 // Name of the default pinned app experiment.
     32 const char kExperimentName[] = "DefaultPinnedApps";
     33 
     34 // Name of a local state pref to store the list of users that participate
     35 // the experiment.
     36 const char kExperimentUsers[] = "DefaultPinnedAppsExperimentUsers";
     37 
     38 // Alternate default pinned apps.
     39 const 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.
     48 bool trial_configured = false;
     49 int alternate_group = base::FieldTrial::kNotFinalized;
     50 
     51 // Returns true if user participates the experiment.
     52 bool 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.
     59 void 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.
     65 void 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.
     72 bool 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.
    104 bool 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 
    122 void RegisterPrefs(PrefRegistrySimple* registry) {
    123   registry->RegisterListPref(kExperimentUsers);
    124 }
    125 
    126 void 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 
    147 void 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 
    163 base::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 
    181 void 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 
    195 void 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 
    201 void ResetStateForTest() {
    202   trial_configured = false;
    203   alternate_group = base::FieldTrial::kNotFinalized;
    204 }
    205 
    206 }  // namespace default_pinned_apps_field_trial
    207 }  // namespace chromeos
    208