app_list_service_impl.cc revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
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 "apps/pref_names.h"
10#include "base/bind.h"
11#include "base/command_line.h"
12#include "base/metrics/histogram.h"
13#include "base/prefs/pref_service.h"
14#include "base/strings/string16.h"
15#include "base/time/time.h"
16#include "chrome/browser/browser_process.h"
17#include "chrome/browser/browser_shutdown.h"
18#include "chrome/browser/profiles/profile_manager.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
25namespace {
26
27const int kDiscoverabilityTimeoutMinutes = 60;
28
29void 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
36void 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
43bool 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
64void 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
82class 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 IsProfileManaged(const base::FilePath& profile_path) OVERRIDE {
134    ProfileInfoCache& profile_info =
135        g_browser_process->profile_manager()->GetProfileInfoCache();
136    size_t profile_index = profile_info.GetIndexOfProfileWithPath(profile_path);
137    return profile_index != std::string::npos &&
138        profile_info.ProfileIsManagedAtIndex(profile_index);
139  }
140
141 private:
142  ProfileManager* profile_manager_;
143  base::WeakPtrFactory<ProfileStoreImpl> weak_factory_;
144};
145
146void RecordAppListDiscoverability(PrefService* local_state,
147                                  bool is_startup_check) {
148  // Since this task may be delayed, ensure it does not interfere with shutdown
149  // when they unluckily coincide.
150  if (browser_shutdown::IsTryingToQuit())
151    return;
152
153  int64 enable_time_value = local_state->GetInt64(prefs::kAppListEnableTime);
154  if (enable_time_value == 0)
155    return;  // Already recorded or never enabled.
156
157  base::Time app_list_enable_time =
158      base::Time::FromInternalValue(enable_time_value);
159  if (is_startup_check) {
160    // When checking at startup, only clear and record the "timeout" case,
161    // otherwise wait for a timeout.
162    base::TimeDelta time_remaining =
163        app_list_enable_time +
164        base::TimeDelta::FromMinutes(kDiscoverabilityTimeoutMinutes) -
165        base::Time::Now();
166    if (time_remaining > base::TimeDelta()) {
167      base::MessageLoop::current()->PostDelayedTask(
168          FROM_HERE,
169          base::Bind(&RecordAppListDiscoverability,
170                     base::Unretained(local_state),
171                     false),
172          time_remaining);
173      return;
174    }
175  }
176
177  local_state->SetInt64(prefs::kAppListEnableTime, 0);
178
179  AppListService::AppListEnableSource enable_source =
180      static_cast<AppListService::AppListEnableSource>(
181          local_state->GetInteger(prefs::kAppListEnableMethod));
182  if (enable_source == AppListService::ENABLE_FOR_APP_INSTALL) {
183    base::TimeDelta time_taken = base::Time::Now() - app_list_enable_time;
184    // This means the user "discovered" the app launcher naturally, after it was
185    // enabled on the first app install. Record how long it took to discover.
186    // Note that the last bucket is essentially "not discovered": subtract 1
187    // minute to account for clock inaccuracy.
188    UMA_HISTOGRAM_CUSTOM_TIMES(
189        "Apps.AppListTimeToDiscover",
190        time_taken,
191        base::TimeDelta::FromSeconds(1),
192        base::TimeDelta::FromMinutes(kDiscoverabilityTimeoutMinutes - 1),
193        10 /* bucket_count */);
194  }
195  UMA_HISTOGRAM_ENUMERATION("Apps.AppListHowEnabled",
196                            enable_source,
197                            AppListService::ENABLE_NUM_ENABLE_SOURCES);
198}
199
200}  // namespace
201
202void AppListServiceImpl::RecordAppListLaunch() {
203  RecordDailyEventFrequency(prefs::kLastAppListLaunchPing,
204                            prefs::kAppListLaunchCount,
205                            &SendAppListLaunch);
206  RecordAppListDiscoverability(local_state_, false);
207}
208
209// static
210void AppListServiceImpl::RecordAppListAppLaunch() {
211  RecordDailyEventFrequency(prefs::kLastAppListAppLaunchPing,
212                            prefs::kAppListAppLaunchCount,
213                            &SendAppListAppLaunch);
214}
215
216// static
217void AppListServiceImpl::SendAppListStats() {
218  if (!g_browser_process || g_browser_process->IsShuttingDown())
219    return;
220
221  SendDailyEventFrequency(prefs::kLastAppListLaunchPing,
222                          prefs::kAppListLaunchCount,
223                          &SendAppListLaunch);
224  SendDailyEventFrequency(prefs::kLastAppListAppLaunchPing,
225                          prefs::kAppListAppLaunchCount,
226                          &SendAppListAppLaunch);
227}
228
229AppListServiceImpl::AppListServiceImpl()
230    : profile_store_(
231          new ProfileStoreImpl(g_browser_process->profile_manager())),
232      weak_factory_(this),
233      command_line_(*CommandLine::ForCurrentProcess()),
234      local_state_(g_browser_process->local_state()),
235      profile_loader_(new ProfileLoader(profile_store_.get())) {
236  profile_store_->AddProfileObserver(this);
237}
238
239AppListServiceImpl::AppListServiceImpl(const CommandLine& command_line,
240                                       PrefService* local_state,
241                                       scoped_ptr<ProfileStore> profile_store)
242    : profile_store_(profile_store.Pass()),
243      weak_factory_(this),
244      command_line_(command_line),
245      local_state_(local_state),
246      profile_loader_(new ProfileLoader(profile_store_.get())) {
247  profile_store_->AddProfileObserver(this);
248}
249
250AppListServiceImpl::~AppListServiceImpl() {}
251
252void AppListServiceImpl::SetAppListNextPaintCallback(void (*callback)()) {}
253
254void AppListServiceImpl::HandleFirstRun() {}
255
256void AppListServiceImpl::Init(Profile* initial_profile) {}
257
258base::FilePath AppListServiceImpl::GetProfilePath(
259    const base::FilePath& user_data_dir) {
260  std::string app_list_profile;
261  if (local_state_->HasPrefPath(prefs::kAppListProfile))
262    app_list_profile = local_state_->GetString(prefs::kAppListProfile);
263
264  // If the user has no profile preference for the app launcher, default to the
265  // last browser profile used.
266  if (app_list_profile.empty() &&
267      local_state_->HasPrefPath(prefs::kProfileLastUsed)) {
268    app_list_profile = local_state_->GetString(prefs::kProfileLastUsed);
269  }
270
271  // If there is no last used profile recorded, use the initial profile.
272  if (app_list_profile.empty())
273    app_list_profile = chrome::kInitialProfile;
274
275  return user_data_dir.AppendASCII(app_list_profile);
276}
277
278void AppListServiceImpl::SetProfilePath(const base::FilePath& profile_path) {
279  // Ensure we don't set the pref to a managed user's profile path.
280  // TODO(calamity): Filter out managed profiles from the settings app so this
281  // can't get hit, so we can remove it.
282  if (profile_store_->IsProfileManaged(profile_path))
283    return;
284
285  local_state_->SetString(
286      prefs::kAppListProfile,
287      profile_path.BaseName().MaybeAsASCII());
288}
289
290void AppListServiceImpl::CreateShortcut() {}
291
292// We need to watch for profile removal to keep kAppListProfile updated.
293void AppListServiceImpl::OnProfileWillBeRemoved(
294    const base::FilePath& profile_path) {
295  // If the profile the app list uses just got deleted, reset it to the last
296  // used profile.
297  std::string app_list_last_profile = local_state_->GetString(
298      prefs::kAppListProfile);
299  if (profile_path.BaseName().MaybeAsASCII() == app_list_last_profile) {
300    local_state_->SetString(prefs::kAppListProfile,
301        local_state_->GetString(prefs::kProfileLastUsed));
302  }
303}
304
305void AppListServiceImpl::Show() {
306  profile_loader_->LoadProfileInvalidatingOtherLoads(
307      GetProfilePath(profile_store_->GetUserDataDir()),
308      base::Bind(&AppListServiceImpl::ShowForProfile,
309                 weak_factory_.GetWeakPtr()));
310}
311
312void AppListServiceImpl::AutoShowForProfile(Profile* requested_profile) {
313  if (local_state_->GetInt64(prefs::kAppListEnableTime) != 0) {
314    // User has not yet discovered the app launcher. Update the enable method to
315    // indicate this. It will then be recorded in UMA.
316    local_state_->SetInteger(prefs::kAppListEnableMethod,
317                             ENABLE_SHOWN_UNDISCOVERED);
318  }
319  ShowForProfile(requested_profile);
320}
321
322void AppListServiceImpl::EnableAppList(Profile* initial_profile,
323                                       AppListEnableSource enable_source) {
324  SetProfilePath(initial_profile->GetPath());
325  // Always allow the webstore "enable" button to re-run the install flow.
326  if (enable_source != AppListService::ENABLE_VIA_WEBSTORE_LINK &&
327      local_state_->GetBoolean(prefs::kAppLauncherHasBeenEnabled)) {
328    return;
329  }
330
331  local_state_->SetBoolean(prefs::kAppLauncherHasBeenEnabled, true);
332  CreateShortcut();
333
334  // UMA for launcher discoverability.
335  local_state_->SetInt64(prefs::kAppListEnableTime,
336                         base::Time::Now().ToInternalValue());
337  local_state_->SetInteger(prefs::kAppListEnableMethod, enable_source);
338  if (base::MessageLoop::current()) {
339    // Ensure a value is recorded if the user "never" shows the app list. Note
340    // there is no message loop in unit tests.
341    base::MessageLoop::current()->PostDelayedTask(
342        FROM_HERE,
343        base::Bind(&RecordAppListDiscoverability,
344                   base::Unretained(local_state_),
345                   false),
346        base::TimeDelta::FromMinutes(kDiscoverabilityTimeoutMinutes));
347  }
348}
349
350void AppListServiceImpl::InvalidatePendingProfileLoads() {
351  profile_loader_->InvalidatePendingProfileLoads();
352}
353
354void AppListServiceImpl::PerformStartupChecks(Profile* initial_profile) {
355  // Except in rare, once-off cases, this just checks that a pref is "0" and
356  // returns.
357  RecordAppListDiscoverability(local_state_, true);
358
359  if (command_line_.HasSwitch(switches::kResetAppListInstallState))
360    local_state_->SetBoolean(prefs::kAppLauncherHasBeenEnabled, false);
361
362  if (command_line_.HasSwitch(switches::kEnableAppList))
363    EnableAppList(initial_profile, ENABLE_VIA_COMMAND_LINE);
364
365  if (!base::MessageLoop::current())
366    return;  // In a unit test.
367
368  // Send app list usage stats after a delay.
369  const int kSendUsageStatsDelay = 5;
370  base::MessageLoop::current()->PostDelayedTask(
371      FROM_HERE,
372      base::Bind(&AppListServiceImpl::SendAppListStats),
373      base::TimeDelta::FromSeconds(kSendUsageStatsDelay));
374}
375