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