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.
5 #include "chrome/browser/ui/app_list/app_list_service_impl.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 #include "ui/app_list/app_list_model.h"
28 const int kDiscoverabilityTimeoutMinutes
= 60;
30 void SendAppListAppLaunch(int count
) {
31 UMA_HISTOGRAM_CUSTOM_COUNTS(
32 "Apps.AppListDailyAppLaunches", count
, 1, 1000, 50);
34 UMA_HISTOGRAM_ENUMERATION("Apps.AppListHasLaunchedAppToday", 1, 2);
37 void SendAppListLaunch(int count
) {
38 UMA_HISTOGRAM_CUSTOM_COUNTS(
39 "Apps.AppListDailyLaunches", count
, 1, 1000, 50);
41 UMA_HISTOGRAM_ENUMERATION("Apps.AppListHasLaunchedAppListToday", 1, 2);
44 bool SendDailyEventFrequency(
45 const char* last_ping_pref
,
46 const char* count_pref
,
47 void (*send_callback
)(int count
)) {
48 PrefService
* local_state
= g_browser_process
->local_state();
50 base::Time now
= base::Time::Now();
51 base::Time last
= base::Time::FromInternalValue(local_state
->GetInt64(
53 int days
= (now
- last
).InDays();
55 send_callback(local_state
->GetInteger(count_pref
));
56 local_state
->SetInt64(
58 (last
+ base::TimeDelta::FromDays(days
)).ToInternalValue());
59 local_state
->SetInteger(count_pref
, 0);
65 void RecordDailyEventFrequency(
66 const char* last_ping_pref
,
67 const char* count_pref
,
68 void (*send_callback
)(int count
)) {
69 if (!g_browser_process
)
70 return; // In a unit test.
72 PrefService
* local_state
= g_browser_process
->local_state();
74 return; // In a unit test.
76 int count
= local_state
->GetInteger(count_pref
);
77 local_state
->SetInteger(count_pref
, count
+ 1);
78 if (SendDailyEventFrequency(last_ping_pref
, count_pref
, send_callback
)) {
79 local_state
->SetInteger(count_pref
, 1);
83 class ProfileStoreImpl
: public ProfileStore
{
85 explicit ProfileStoreImpl(ProfileManager
* profile_manager
)
86 : profile_manager_(profile_manager
),
90 virtual void AddProfileObserver(ProfileInfoCacheObserver
* observer
) override
{
91 profile_manager_
->GetProfileInfoCache().AddObserver(observer
);
94 virtual void LoadProfileAsync(
95 const base::FilePath
& path
,
96 base::Callback
<void(Profile
*)> callback
) override
{
97 profile_manager_
->CreateProfileAsync(
99 base::Bind(&ProfileStoreImpl::OnProfileCreated
,
100 weak_factory_
.GetWeakPtr(),
107 void OnProfileCreated(base::Callback
<void(Profile
*)> callback
,
109 Profile::CreateStatus status
) {
111 case Profile::CREATE_STATUS_CREATED
:
113 case Profile::CREATE_STATUS_INITIALIZED
:
114 callback
.Run(profile
);
116 case Profile::CREATE_STATUS_LOCAL_FAIL
:
117 case Profile::CREATE_STATUS_REMOTE_FAIL
:
118 case Profile::CREATE_STATUS_CANCELED
:
120 case Profile::MAX_CREATE_STATUS
:
126 virtual Profile
* GetProfileByPath(const base::FilePath
& path
) override
{
127 return profile_manager_
->GetProfileByPath(path
);
130 virtual base::FilePath
GetUserDataDir() override
{
131 return profile_manager_
->user_data_dir();
134 virtual bool IsProfileSupervised(
135 const base::FilePath
& profile_path
) override
{
136 ProfileInfoCache
& profile_info
=
137 g_browser_process
->profile_manager()->GetProfileInfoCache();
138 size_t profile_index
= profile_info
.GetIndexOfProfileWithPath(profile_path
);
139 return profile_index
!= std::string::npos
&&
140 profile_info
.ProfileIsSupervisedAtIndex(profile_index
);
144 ProfileManager
* profile_manager_
;
145 base::WeakPtrFactory
<ProfileStoreImpl
> weak_factory_
;
148 void RecordAppListDiscoverability(PrefService
* local_state
,
149 bool is_startup_check
) {
150 // Since this task may be delayed, ensure it does not interfere with shutdown
151 // when they unluckily coincide.
152 if (browser_shutdown::IsTryingToQuit())
155 int64 enable_time_value
= local_state
->GetInt64(prefs::kAppListEnableTime
);
156 if (enable_time_value
== 0)
157 return; // Already recorded or never enabled.
159 base::Time app_list_enable_time
=
160 base::Time::FromInternalValue(enable_time_value
);
161 if (is_startup_check
) {
162 // When checking at startup, only clear and record the "timeout" case,
163 // otherwise wait for a timeout.
164 base::TimeDelta time_remaining
=
165 app_list_enable_time
+
166 base::TimeDelta::FromMinutes(kDiscoverabilityTimeoutMinutes
) -
168 if (time_remaining
> base::TimeDelta()) {
169 base::MessageLoop::current()->PostDelayedTask(
171 base::Bind(&RecordAppListDiscoverability
,
172 base::Unretained(local_state
),
179 local_state
->SetInt64(prefs::kAppListEnableTime
, 0);
181 AppListService::AppListEnableSource enable_source
=
182 static_cast<AppListService::AppListEnableSource
>(
183 local_state
->GetInteger(prefs::kAppListEnableMethod
));
184 if (enable_source
== AppListService::ENABLE_FOR_APP_INSTALL
) {
185 base::TimeDelta time_taken
= base::Time::Now() - app_list_enable_time
;
186 // This means the user "discovered" the app launcher naturally, after it was
187 // enabled on the first app install. Record how long it took to discover.
188 // Note that the last bucket is essentially "not discovered": subtract 1
189 // minute to account for clock inaccuracy.
190 UMA_HISTOGRAM_CUSTOM_TIMES(
191 "Apps.AppListTimeToDiscover",
193 base::TimeDelta::FromSeconds(1),
194 base::TimeDelta::FromMinutes(kDiscoverabilityTimeoutMinutes
- 1),
195 10 /* bucket_count */);
197 UMA_HISTOGRAM_ENUMERATION("Apps.AppListHowEnabled",
199 AppListService::ENABLE_NUM_ENABLE_SOURCES
);
204 void AppListServiceImpl::RecordAppListLaunch() {
205 RecordDailyEventFrequency(prefs::kLastAppListLaunchPing
,
206 prefs::kAppListLaunchCount
,
208 RecordAppListDiscoverability(local_state_
, false);
212 void AppListServiceImpl::RecordAppListAppLaunch() {
213 RecordDailyEventFrequency(prefs::kLastAppListAppLaunchPing
,
214 prefs::kAppListAppLaunchCount
,
215 &SendAppListAppLaunch
);
219 void AppListServiceImpl::SendAppListStats() {
220 if (!g_browser_process
|| g_browser_process
->IsShuttingDown())
223 SendDailyEventFrequency(prefs::kLastAppListLaunchPing
,
224 prefs::kAppListLaunchCount
,
226 SendDailyEventFrequency(prefs::kLastAppListAppLaunchPing
,
227 prefs::kAppListAppLaunchCount
,
228 &SendAppListAppLaunch
);
231 AppListServiceImpl::AppListServiceImpl()
233 new ProfileStoreImpl(g_browser_process
->profile_manager())),
234 command_line_(*CommandLine::ForCurrentProcess()),
235 local_state_(g_browser_process
->local_state()),
236 profile_loader_(new ProfileLoader(profile_store_
.get())),
237 weak_factory_(this) {
238 profile_store_
->AddProfileObserver(this);
241 AppListServiceImpl::AppListServiceImpl(const CommandLine
& command_line
,
242 PrefService
* local_state
,
243 scoped_ptr
<ProfileStore
> profile_store
)
244 : profile_store_(profile_store
.Pass()),
245 command_line_(command_line
),
246 local_state_(local_state
),
247 profile_loader_(new ProfileLoader(profile_store_
.get())),
248 weak_factory_(this) {
249 profile_store_
->AddProfileObserver(this);
252 AppListServiceImpl::~AppListServiceImpl() {}
254 AppListViewDelegate
* AppListServiceImpl::GetViewDelegate(Profile
* profile
) {
256 view_delegate_
.reset(new AppListViewDelegate(GetControllerDelegate()));
257 view_delegate_
->SetProfile(profile
);
258 return view_delegate_
.get();
261 void AppListServiceImpl::SetAppListNextPaintCallback(void (*callback
)()) {}
263 void AppListServiceImpl::HandleFirstRun() {}
265 void AppListServiceImpl::Init(Profile
* initial_profile
) {}
267 base::FilePath
AppListServiceImpl::GetProfilePath(
268 const base::FilePath
& user_data_dir
) {
269 std::string app_list_profile
;
270 if (local_state_
->HasPrefPath(prefs::kAppListProfile
))
271 app_list_profile
= local_state_
->GetString(prefs::kAppListProfile
);
273 // If the user has no profile preference for the app launcher, default to the
274 // last browser profile used.
275 if (app_list_profile
.empty() &&
276 local_state_
->HasPrefPath(prefs::kProfileLastUsed
)) {
277 app_list_profile
= local_state_
->GetString(prefs::kProfileLastUsed
);
280 // If there is no last used profile recorded, use the initial profile.
281 if (app_list_profile
.empty())
282 app_list_profile
= chrome::kInitialProfile
;
284 return user_data_dir
.AppendASCII(app_list_profile
);
287 void AppListServiceImpl::SetProfilePath(const base::FilePath
& profile_path
) {
288 local_state_
->SetString(
289 prefs::kAppListProfile
,
290 profile_path
.BaseName().MaybeAsASCII());
293 void AppListServiceImpl::CreateShortcut() {}
295 void AppListServiceImpl::OnProfileWillBeRemoved(
296 const base::FilePath
& profile_path
) {
297 // We need to watch for profile removal to keep kAppListProfile updated, for
298 // the case that the deleted profile is being used by the app list.
299 std::string app_list_last_profile
= local_state_
->GetString(
300 prefs::kAppListProfile
);
301 if (profile_path
.BaseName().MaybeAsASCII() != app_list_last_profile
)
304 // Switch the app list over to a valid profile.
305 // Before ProfileInfoCache::DeleteProfileFromCache() calls this function,
306 // ProfileManager::ScheduleProfileForDeletion() will have checked to see if
307 // the deleted profile was also "last used", and updated that setting with
309 local_state_
->SetString(prefs::kAppListProfile
,
310 local_state_
->GetString(prefs::kProfileLastUsed
));
312 // If the app list was never shown, there won't be a |view_delegate_| yet.
316 // The Chrome AppListViewDelegate now needs its profile cleared, because:
317 // 1. it has many references to the profile and can't be profile-keyed, and
318 // 2. the last used profile might not be loaded yet.
319 // - this loading is sometimes done by the ProfileManager asynchronously,
320 // so the app list can't just switch to that.
321 // Only Mac supports showing the app list with a NULL profile, so tear down
324 view_delegate_
->SetProfile(NULL
);
327 void AppListServiceImpl::Show() {
328 profile_loader_
->LoadProfileInvalidatingOtherLoads(
329 GetProfilePath(profile_store_
->GetUserDataDir()),
330 base::Bind(&AppListServiceImpl::ShowForProfile
,
331 weak_factory_
.GetWeakPtr()));
334 void AppListServiceImpl::ShowForVoiceSearch(Profile
* profile
) {
335 ShowForProfile(profile
);
336 view_delegate_
->ToggleSpeechRecognition();
339 void AppListServiceImpl::ShowForAppInstall(Profile
* profile
,
340 const std::string
& extension_id
,
341 bool start_discovery_tracking
) {
342 if (start_discovery_tracking
) {
343 CreateForProfile(profile
);
345 // Check if the app launcher has not yet been shown ever. Since this will
346 // show it, if discoverability UMA hasn't yet been recorded, it needs to be
347 // counted as undiscovered.
348 if (local_state_
->GetInt64(prefs::kAppListEnableTime
) != 0) {
349 local_state_
->SetInteger(prefs::kAppListEnableMethod
,
350 ENABLE_SHOWN_UNDISCOVERED
);
352 ShowForProfile(profile
);
354 if (extension_id
.empty())
355 return; // Nothing to highlight. Only used in tests.
357 // The only way an install can happen is with the profile already loaded. So,
358 // ShowForProfile() can never be asynchronous, and the model is guaranteed to
359 // exist after a show.
360 DCHECK(view_delegate_
->GetModel());
361 view_delegate_
->GetModel()
362 ->top_level_item_list()
363 ->HighlightItemInstalledFromUI(extension_id
);
366 void AppListServiceImpl::EnableAppList(Profile
* initial_profile
,
367 AppListEnableSource enable_source
) {
368 SetProfilePath(initial_profile
->GetPath());
369 // Always allow the webstore "enable" button to re-run the install flow.
370 if (enable_source
!= AppListService::ENABLE_VIA_WEBSTORE_LINK
&&
371 local_state_
->GetBoolean(prefs::kAppLauncherHasBeenEnabled
)) {
375 local_state_
->SetBoolean(prefs::kAppLauncherHasBeenEnabled
, true);
378 // UMA for launcher discoverability.
379 local_state_
->SetInt64(prefs::kAppListEnableTime
,
380 base::Time::Now().ToInternalValue());
381 local_state_
->SetInteger(prefs::kAppListEnableMethod
, enable_source
);
382 if (base::MessageLoop::current()) {
383 // Ensure a value is recorded if the user "never" shows the app list. Note
384 // there is no message loop in unit tests.
385 base::MessageLoop::current()->PostDelayedTask(
387 base::Bind(&RecordAppListDiscoverability
,
388 base::Unretained(local_state_
),
390 base::TimeDelta::FromMinutes(kDiscoverabilityTimeoutMinutes
));
394 void AppListServiceImpl::InvalidatePendingProfileLoads() {
395 profile_loader_
->InvalidatePendingProfileLoads();
398 void AppListServiceImpl::PerformStartupChecks(Profile
* initial_profile
) {
399 // Except in rare, once-off cases, this just checks that a pref is "0" and
401 RecordAppListDiscoverability(local_state_
, true);
403 if (command_line_
.HasSwitch(switches::kResetAppListInstallState
))
404 local_state_
->SetBoolean(prefs::kAppLauncherHasBeenEnabled
, false);
406 if (command_line_
.HasSwitch(switches::kEnableAppList
))
407 EnableAppList(initial_profile
, ENABLE_VIA_COMMAND_LINE
);
409 if (!base::MessageLoop::current())
410 return; // In a unit test.
412 // Send app list usage stats after a delay.
413 const int kSendUsageStatsDelay
= 5;
414 base::MessageLoop::current()->PostDelayedTask(
416 base::Bind(&AppListServiceImpl::SendAppListStats
),
417 base::TimeDelta::FromSeconds(kSendUsageStatsDelay
));