Home | History | Annotate | Download | only in app_list
      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/ui/app_list/app_list_service_impl.h"
      6 
      7 #include <string>
      8 
      9 #include "base/bind.h"
     10 #include "base/command_line.h"
     11 #include "base/metrics/histogram.h"
     12 #include "base/prefs/pref_service.h"
     13 #include "base/strings/string16.h"
     14 #include "base/time/time.h"
     15 #include "chrome/browser/browser_process.h"
     16 #include "chrome/browser/browser_shutdown.h"
     17 #include "chrome/browser/profiles/profile_manager.h"
     18 #include "chrome/browser/ui/app_list/app_list_view_delegate.h"
     19 #include "chrome/browser/ui/app_list/profile_loader.h"
     20 #include "chrome/browser/ui/app_list/profile_store.h"
     21 #include "chrome/common/chrome_constants.h"
     22 #include "chrome/common/chrome_switches.h"
     23 #include "chrome/common/pref_names.h"
     24 
     25 namespace {
     26 
     27 const int kDiscoverabilityTimeoutMinutes = 60;
     28 
     29 void SendAppListAppLaunch(int count) {
     30   UMA_HISTOGRAM_CUSTOM_COUNTS(
     31       "Apps.AppListDailyAppLaunches", count, 1, 1000, 50);
     32   if (count > 0)
     33     UMA_HISTOGRAM_ENUMERATION("Apps.AppListHasLaunchedAppToday", 1, 2);
     34 }
     35 
     36 void SendAppListLaunch(int count) {
     37   UMA_HISTOGRAM_CUSTOM_COUNTS(
     38       "Apps.AppListDailyLaunches", count, 1, 1000, 50);
     39   if (count > 0)
     40     UMA_HISTOGRAM_ENUMERATION("Apps.AppListHasLaunchedAppListToday", 1, 2);
     41 }
     42 
     43 bool SendDailyEventFrequency(
     44     const char* last_ping_pref,
     45     const char* count_pref,
     46     void (*send_callback)(int count)) {
     47   PrefService* local_state = g_browser_process->local_state();
     48 
     49   base::Time now = base::Time::Now();
     50   base::Time last = base::Time::FromInternalValue(local_state->GetInt64(
     51       last_ping_pref));
     52   int days = (now - last).InDays();
     53   if (days > 0) {
     54     send_callback(local_state->GetInteger(count_pref));
     55     local_state->SetInt64(
     56         last_ping_pref,
     57         (last + base::TimeDelta::FromDays(days)).ToInternalValue());
     58     local_state->SetInteger(count_pref, 0);
     59     return true;
     60   }
     61   return false;
     62 }
     63 
     64 void RecordDailyEventFrequency(
     65     const char* last_ping_pref,
     66     const char* count_pref,
     67     void (*send_callback)(int count)) {
     68   if (!g_browser_process)
     69     return;  // In a unit test.
     70 
     71   PrefService* local_state = g_browser_process->local_state();
     72   if (!local_state)
     73     return;  // In a unit test.
     74 
     75   int count = local_state->GetInteger(count_pref);
     76   local_state->SetInteger(count_pref, count + 1);
     77   if (SendDailyEventFrequency(last_ping_pref, count_pref, send_callback)) {
     78     local_state->SetInteger(count_pref, 1);
     79   }
     80 }
     81 
     82 class ProfileStoreImpl : public ProfileStore {
     83  public:
     84   explicit ProfileStoreImpl(ProfileManager* profile_manager)
     85       : profile_manager_(profile_manager),
     86         weak_factory_(this) {
     87   }
     88 
     89   virtual void AddProfileObserver(ProfileInfoCacheObserver* observer) OVERRIDE {
     90     profile_manager_->GetProfileInfoCache().AddObserver(observer);
     91   }
     92 
     93   virtual void LoadProfileAsync(
     94       const base::FilePath& path,
     95       base::Callback<void(Profile*)> callback) OVERRIDE {
     96     profile_manager_->CreateProfileAsync(
     97         path,
     98         base::Bind(&ProfileStoreImpl::OnProfileCreated,
     99                    weak_factory_.GetWeakPtr(),
    100                    callback),
    101         base::string16(),
    102         base::string16(),
    103         std::string());
    104   }
    105 
    106   void OnProfileCreated(base::Callback<void(Profile*)> callback,
    107                         Profile* profile,
    108                         Profile::CreateStatus status) {
    109     switch (status) {
    110       case Profile::CREATE_STATUS_CREATED:
    111         break;
    112       case Profile::CREATE_STATUS_INITIALIZED:
    113         callback.Run(profile);
    114         break;
    115       case Profile::CREATE_STATUS_LOCAL_FAIL:
    116       case Profile::CREATE_STATUS_REMOTE_FAIL:
    117       case Profile::CREATE_STATUS_CANCELED:
    118         break;
    119       case Profile::MAX_CREATE_STATUS:
    120         NOTREACHED();
    121         break;
    122     }
    123   }
    124 
    125   virtual Profile* GetProfileByPath(const base::FilePath& path) OVERRIDE {
    126     return profile_manager_->GetProfileByPath(path);
    127   }
    128 
    129   virtual base::FilePath GetUserDataDir() OVERRIDE {
    130     return profile_manager_->user_data_dir();
    131   }
    132 
    133   virtual bool IsProfileSupervised(
    134       const base::FilePath& profile_path) OVERRIDE {
    135     ProfileInfoCache& profile_info =
    136         g_browser_process->profile_manager()->GetProfileInfoCache();
    137     size_t profile_index = profile_info.GetIndexOfProfileWithPath(profile_path);
    138     return profile_index != std::string::npos &&
    139         profile_info.ProfileIsSupervisedAtIndex(profile_index);
    140   }
    141 
    142  private:
    143   ProfileManager* profile_manager_;
    144   base::WeakPtrFactory<ProfileStoreImpl> weak_factory_;
    145 };
    146 
    147 void RecordAppListDiscoverability(PrefService* local_state,
    148                                   bool is_startup_check) {
    149   // Since this task may be delayed, ensure it does not interfere with shutdown
    150   // when they unluckily coincide.
    151   if (browser_shutdown::IsTryingToQuit())
    152     return;
    153 
    154   int64 enable_time_value = local_state->GetInt64(prefs::kAppListEnableTime);
    155   if (enable_time_value == 0)
    156     return;  // Already recorded or never enabled.
    157 
    158   base::Time app_list_enable_time =
    159       base::Time::FromInternalValue(enable_time_value);
    160   if (is_startup_check) {
    161     // When checking at startup, only clear and record the "timeout" case,
    162     // otherwise wait for a timeout.
    163     base::TimeDelta time_remaining =
    164         app_list_enable_time +
    165         base::TimeDelta::FromMinutes(kDiscoverabilityTimeoutMinutes) -
    166         base::Time::Now();
    167     if (time_remaining > base::TimeDelta()) {
    168       base::MessageLoop::current()->PostDelayedTask(
    169           FROM_HERE,
    170           base::Bind(&RecordAppListDiscoverability,
    171                      base::Unretained(local_state),
    172                      false),
    173           time_remaining);
    174       return;
    175     }
    176   }
    177 
    178   local_state->SetInt64(prefs::kAppListEnableTime, 0);
    179 
    180   AppListService::AppListEnableSource enable_source =
    181       static_cast<AppListService::AppListEnableSource>(
    182           local_state->GetInteger(prefs::kAppListEnableMethod));
    183   if (enable_source == AppListService::ENABLE_FOR_APP_INSTALL) {
    184     base::TimeDelta time_taken = base::Time::Now() - app_list_enable_time;
    185     // This means the user "discovered" the app launcher naturally, after it was
    186     // enabled on the first app install. Record how long it took to discover.
    187     // Note that the last bucket is essentially "not discovered": subtract 1
    188     // minute to account for clock inaccuracy.
    189     UMA_HISTOGRAM_CUSTOM_TIMES(
    190         "Apps.AppListTimeToDiscover",
    191         time_taken,
    192         base::TimeDelta::FromSeconds(1),
    193         base::TimeDelta::FromMinutes(kDiscoverabilityTimeoutMinutes - 1),
    194         10 /* bucket_count */);
    195   }
    196   UMA_HISTOGRAM_ENUMERATION("Apps.AppListHowEnabled",
    197                             enable_source,
    198                             AppListService::ENABLE_NUM_ENABLE_SOURCES);
    199 }
    200 
    201 }  // namespace
    202 
    203 void AppListServiceImpl::RecordAppListLaunch() {
    204   RecordDailyEventFrequency(prefs::kLastAppListLaunchPing,
    205                             prefs::kAppListLaunchCount,
    206                             &SendAppListLaunch);
    207   RecordAppListDiscoverability(local_state_, false);
    208 }
    209 
    210 // static
    211 void AppListServiceImpl::RecordAppListAppLaunch() {
    212   RecordDailyEventFrequency(prefs::kLastAppListAppLaunchPing,
    213                             prefs::kAppListAppLaunchCount,
    214                             &SendAppListAppLaunch);
    215 }
    216 
    217 // static
    218 void AppListServiceImpl::SendAppListStats() {
    219   if (!g_browser_process || g_browser_process->IsShuttingDown())
    220     return;
    221 
    222   SendDailyEventFrequency(prefs::kLastAppListLaunchPing,
    223                           prefs::kAppListLaunchCount,
    224                           &SendAppListLaunch);
    225   SendDailyEventFrequency(prefs::kLastAppListAppLaunchPing,
    226                           prefs::kAppListAppLaunchCount,
    227                           &SendAppListAppLaunch);
    228 }
    229 
    230 AppListServiceImpl::AppListServiceImpl()
    231     : profile_store_(
    232           new ProfileStoreImpl(g_browser_process->profile_manager())),
    233       command_line_(*CommandLine::ForCurrentProcess()),
    234       local_state_(g_browser_process->local_state()),
    235       profile_loader_(new ProfileLoader(profile_store_.get())),
    236       weak_factory_(this) {
    237   profile_store_->AddProfileObserver(this);
    238 }
    239 
    240 AppListServiceImpl::AppListServiceImpl(const CommandLine& command_line,
    241                                        PrefService* local_state,
    242                                        scoped_ptr<ProfileStore> profile_store)
    243     : profile_store_(profile_store.Pass()),
    244       command_line_(command_line),
    245       local_state_(local_state),
    246       profile_loader_(new ProfileLoader(profile_store_.get())),
    247       weak_factory_(this) {
    248   profile_store_->AddProfileObserver(this);
    249 }
    250 
    251 AppListServiceImpl::~AppListServiceImpl() {}
    252 
    253 AppListViewDelegate* AppListServiceImpl::GetViewDelegate(Profile* profile) {
    254   if (!view_delegate_)
    255     view_delegate_.reset(new AppListViewDelegate(GetControllerDelegate()));
    256   view_delegate_->SetProfile(profile);
    257   return view_delegate_.get();
    258 }
    259 
    260 void AppListServiceImpl::SetAppListNextPaintCallback(void (*callback)()) {}
    261 
    262 void AppListServiceImpl::HandleFirstRun() {}
    263 
    264 void AppListServiceImpl::Init(Profile* initial_profile) {}
    265 
    266 base::FilePath AppListServiceImpl::GetProfilePath(
    267     const base::FilePath& user_data_dir) {
    268   std::string app_list_profile;
    269   if (local_state_->HasPrefPath(prefs::kAppListProfile))
    270     app_list_profile = local_state_->GetString(prefs::kAppListProfile);
    271 
    272   // If the user has no profile preference for the app launcher, default to the
    273   // last browser profile used.
    274   if (app_list_profile.empty() &&
    275       local_state_->HasPrefPath(prefs::kProfileLastUsed)) {
    276     app_list_profile = local_state_->GetString(prefs::kProfileLastUsed);
    277   }
    278 
    279   // If there is no last used profile recorded, use the initial profile.
    280   if (app_list_profile.empty())
    281     app_list_profile = chrome::kInitialProfile;
    282 
    283   return user_data_dir.AppendASCII(app_list_profile);
    284 }
    285 
    286 void AppListServiceImpl::SetProfilePath(const base::FilePath& profile_path) {
    287   local_state_->SetString(
    288       prefs::kAppListProfile,
    289       profile_path.BaseName().MaybeAsASCII());
    290 }
    291 
    292 void AppListServiceImpl::CreateShortcut() {}
    293 
    294 void AppListServiceImpl::OnProfileWillBeRemoved(
    295     const base::FilePath& profile_path) {
    296   // We need to watch for profile removal to keep kAppListProfile updated, for
    297   // the case that the deleted profile is being used by the app list.
    298   std::string app_list_last_profile = local_state_->GetString(
    299       prefs::kAppListProfile);
    300   if (profile_path.BaseName().MaybeAsASCII() != app_list_last_profile)
    301     return;
    302 
    303   // Switch the app list over to a valid profile.
    304   // Before ProfileInfoCache::DeleteProfileFromCache() calls this function,
    305   // ProfileManager::ScheduleProfileForDeletion() will have checked to see if
    306   // the deleted profile was also "last used", and updated that setting with
    307   // something valid.
    308   local_state_->SetString(prefs::kAppListProfile,
    309                           local_state_->GetString(prefs::kProfileLastUsed));
    310 
    311   // If the app list was never shown, there won't be a |view_delegate_| yet.
    312   if (!view_delegate_)
    313     return;
    314 
    315   // The Chrome AppListViewDelegate now needs its profile cleared, because:
    316   //  1. it has many references to the profile and can't be profile-keyed, and
    317   //  2. the last used profile might not be loaded yet.
    318   //    - this loading is sometimes done by the ProfileManager asynchronously,
    319   //      so the app list can't just switch to that.
    320   // Only Mac supports showing the app list with a NULL profile, so tear down
    321   // the view.
    322   DestroyAppList();
    323   view_delegate_->SetProfile(NULL);
    324 }
    325 
    326 void AppListServiceImpl::Show() {
    327   profile_loader_->LoadProfileInvalidatingOtherLoads(
    328       GetProfilePath(profile_store_->GetUserDataDir()),
    329       base::Bind(&AppListServiceImpl::ShowForProfile,
    330                  weak_factory_.GetWeakPtr()));
    331 }
    332 
    333 void AppListServiceImpl::AutoShowForProfile(Profile* requested_profile) {
    334   if (local_state_->GetInt64(prefs::kAppListEnableTime) != 0) {
    335     // User has not yet discovered the app launcher. Update the enable method to
    336     // indicate this. It will then be recorded in UMA.
    337     local_state_->SetInteger(prefs::kAppListEnableMethod,
    338                              ENABLE_SHOWN_UNDISCOVERED);
    339   }
    340   ShowForProfile(requested_profile);
    341 }
    342 
    343 void AppListServiceImpl::EnableAppList(Profile* initial_profile,
    344                                        AppListEnableSource enable_source) {
    345   SetProfilePath(initial_profile->GetPath());
    346   // Always allow the webstore "enable" button to re-run the install flow.
    347   if (enable_source != AppListService::ENABLE_VIA_WEBSTORE_LINK &&
    348       local_state_->GetBoolean(prefs::kAppLauncherHasBeenEnabled)) {
    349     return;
    350   }
    351 
    352   local_state_->SetBoolean(prefs::kAppLauncherHasBeenEnabled, true);
    353   CreateShortcut();
    354 
    355   // UMA for launcher discoverability.
    356   local_state_->SetInt64(prefs::kAppListEnableTime,
    357                          base::Time::Now().ToInternalValue());
    358   local_state_->SetInteger(prefs::kAppListEnableMethod, enable_source);
    359   if (base::MessageLoop::current()) {
    360     // Ensure a value is recorded if the user "never" shows the app list. Note
    361     // there is no message loop in unit tests.
    362     base::MessageLoop::current()->PostDelayedTask(
    363         FROM_HERE,
    364         base::Bind(&RecordAppListDiscoverability,
    365                    base::Unretained(local_state_),
    366                    false),
    367         base::TimeDelta::FromMinutes(kDiscoverabilityTimeoutMinutes));
    368   }
    369 }
    370 
    371 void AppListServiceImpl::InvalidatePendingProfileLoads() {
    372   profile_loader_->InvalidatePendingProfileLoads();
    373 }
    374 
    375 void AppListServiceImpl::PerformStartupChecks(Profile* initial_profile) {
    376   // Except in rare, once-off cases, this just checks that a pref is "0" and
    377   // returns.
    378   RecordAppListDiscoverability(local_state_, true);
    379 
    380   if (command_line_.HasSwitch(switches::kResetAppListInstallState))
    381     local_state_->SetBoolean(prefs::kAppLauncherHasBeenEnabled, false);
    382 
    383   if (command_line_.HasSwitch(switches::kEnableAppList))
    384     EnableAppList(initial_profile, ENABLE_VIA_COMMAND_LINE);
    385 
    386   if (!base::MessageLoop::current())
    387     return;  // In a unit test.
    388 
    389   // Send app list usage stats after a delay.
    390   const int kSendUsageStatsDelay = 5;
    391   base::MessageLoop::current()->PostDelayedTask(
    392       FROM_HERE,
    393       base::Bind(&AppListServiceImpl::SendAppListStats),
    394       base::TimeDelta::FromSeconds(kSendUsageStatsDelay));
    395 }
    396