app_list_service_impl.cc revision 010d83a9304c5a91596085d917d248abff47903a
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/apps/shortcut_manager.h"
17#include "chrome/browser/apps/shortcut_manager_factory.h"
18#include "chrome/browser/browser_process.h"
19#include "chrome/browser/browser_shutdown.h"
20#include "chrome/browser/profiles/profile_manager.h"
21#include "chrome/browser/ui/app_list/profile_loader.h"
22#include "chrome/browser/ui/app_list/profile_store.h"
23#include "chrome/common/chrome_constants.h"
24#include "chrome/common/chrome_switches.h"
25#include "chrome/common/pref_names.h"
26
27namespace {
28
29const int kDiscoverabilityTimeoutMinutes = 60;
30
31void SendAppListAppLaunch(int count) {
32  UMA_HISTOGRAM_CUSTOM_COUNTS(
33      "Apps.AppListDailyAppLaunches", count, 1, 1000, 50);
34  if (count > 0)
35    UMA_HISTOGRAM_ENUMERATION("Apps.AppListHasLaunchedAppToday", 1, 2);
36}
37
38void SendAppListLaunch(int count) {
39  UMA_HISTOGRAM_CUSTOM_COUNTS(
40      "Apps.AppListDailyLaunches", count, 1, 1000, 50);
41  if (count > 0)
42    UMA_HISTOGRAM_ENUMERATION("Apps.AppListHasLaunchedAppListToday", 1, 2);
43}
44
45bool SendDailyEventFrequency(
46    const char* last_ping_pref,
47    const char* count_pref,
48    void (*send_callback)(int count)) {
49  PrefService* local_state = g_browser_process->local_state();
50
51  base::Time now = base::Time::Now();
52  base::Time last = base::Time::FromInternalValue(local_state->GetInt64(
53      last_ping_pref));
54  int days = (now - last).InDays();
55  if (days > 0) {
56    send_callback(local_state->GetInteger(count_pref));
57    local_state->SetInt64(
58        last_ping_pref,
59        (last + base::TimeDelta::FromDays(days)).ToInternalValue());
60    local_state->SetInteger(count_pref, 0);
61    return true;
62  }
63  return false;
64}
65
66void RecordDailyEventFrequency(
67    const char* last_ping_pref,
68    const char* count_pref,
69    void (*send_callback)(int count)) {
70  if (!g_browser_process)
71    return;  // In a unit test.
72
73  PrefService* local_state = g_browser_process->local_state();
74  if (!local_state)
75    return;  // In a unit test.
76
77  int count = local_state->GetInteger(count_pref);
78  local_state->SetInteger(count_pref, count + 1);
79  if (SendDailyEventFrequency(last_ping_pref, count_pref, send_callback)) {
80    local_state->SetInteger(count_pref, 1);
81  }
82}
83
84class ProfileStoreImpl : public ProfileStore {
85 public:
86  explicit ProfileStoreImpl(ProfileManager* profile_manager)
87      : profile_manager_(profile_manager),
88        weak_factory_(this) {
89  }
90
91  virtual void AddProfileObserver(ProfileInfoCacheObserver* observer) OVERRIDE {
92    profile_manager_->GetProfileInfoCache().AddObserver(observer);
93  }
94
95  virtual void LoadProfileAsync(
96      const base::FilePath& path,
97      base::Callback<void(Profile*)> callback) OVERRIDE {
98    profile_manager_->CreateProfileAsync(
99        path,
100        base::Bind(&ProfileStoreImpl::OnProfileCreated,
101                   weak_factory_.GetWeakPtr(),
102                   callback),
103        base::string16(),
104        base::string16(),
105        std::string());
106  }
107
108  void OnProfileCreated(base::Callback<void(Profile*)> callback,
109                        Profile* profile,
110                        Profile::CreateStatus status) {
111    switch (status) {
112      case Profile::CREATE_STATUS_CREATED:
113        break;
114      case Profile::CREATE_STATUS_INITIALIZED:
115        callback.Run(profile);
116        break;
117      case Profile::CREATE_STATUS_LOCAL_FAIL:
118      case Profile::CREATE_STATUS_REMOTE_FAIL:
119      case Profile::CREATE_STATUS_CANCELED:
120        break;
121      case Profile::MAX_CREATE_STATUS:
122        NOTREACHED();
123        break;
124    }
125  }
126
127  virtual Profile* GetProfileByPath(const base::FilePath& path) OVERRIDE {
128    return profile_manager_->GetProfileByPath(path);
129  }
130
131  virtual base::FilePath GetUserDataDir() OVERRIDE {
132    return profile_manager_->user_data_dir();
133  }
134
135  virtual bool IsProfileManaged(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_info.ProfileIsManagedAtIndex(profile_index);
140  }
141
142 private:
143  ProfileManager* profile_manager_;
144  base::WeakPtrFactory<ProfileStoreImpl> weak_factory_;
145};
146
147void 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
203void AppListServiceImpl::RecordAppListLaunch() {
204  RecordDailyEventFrequency(prefs::kLastAppListLaunchPing,
205                            prefs::kAppListLaunchCount,
206                            &SendAppListLaunch);
207  RecordAppListDiscoverability(local_state_, false);
208}
209
210// static
211void AppListServiceImpl::RecordAppListAppLaunch() {
212  RecordDailyEventFrequency(prefs::kLastAppListAppLaunchPing,
213                            prefs::kAppListAppLaunchCount,
214                            &SendAppListAppLaunch);
215}
216
217// static
218void 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
230AppListServiceImpl::AppListServiceImpl()
231    : profile_store_(
232          new ProfileStoreImpl(g_browser_process->profile_manager())),
233      weak_factory_(this),
234      command_line_(*CommandLine::ForCurrentProcess()),
235      local_state_(g_browser_process->local_state()),
236      profile_loader_(new ProfileLoader(profile_store_.get())) {
237  profile_store_->AddProfileObserver(this);
238}
239
240AppListServiceImpl::AppListServiceImpl(const CommandLine& command_line,
241                                       PrefService* local_state,
242                                       scoped_ptr<ProfileStore> profile_store)
243    : profile_store_(profile_store.Pass()),
244      weak_factory_(this),
245      command_line_(command_line),
246      local_state_(local_state),
247      profile_loader_(new ProfileLoader(profile_store_.get())) {
248  profile_store_->AddProfileObserver(this);
249}
250
251AppListServiceImpl::~AppListServiceImpl() {}
252
253void AppListServiceImpl::SetAppListNextPaintCallback(void (*callback)()) {}
254
255void AppListServiceImpl::HandleFirstRun() {}
256
257void AppListServiceImpl::Init(Profile* initial_profile) {}
258
259base::FilePath AppListServiceImpl::GetProfilePath(
260    const base::FilePath& user_data_dir) {
261  std::string app_list_profile;
262  if (local_state_->HasPrefPath(prefs::kAppListProfile))
263    app_list_profile = local_state_->GetString(prefs::kAppListProfile);
264
265  // If the user has no profile preference for the app launcher, default to the
266  // last browser profile used.
267  if (app_list_profile.empty() &&
268      local_state_->HasPrefPath(prefs::kProfileLastUsed)) {
269    app_list_profile = local_state_->GetString(prefs::kProfileLastUsed);
270  }
271
272  // If there is no last used profile recorded, use the initial profile.
273  if (app_list_profile.empty())
274    app_list_profile = chrome::kInitialProfile;
275
276  return user_data_dir.AppendASCII(app_list_profile);
277}
278
279void AppListServiceImpl::SetProfilePath(const base::FilePath& profile_path) {
280  // Ensure we don't set the pref to a managed user's profile path.
281  // TODO(calamity): Filter out managed profiles from the settings app so this
282  // can't get hit, so we can remove it.
283  if (profile_store_->IsProfileManaged(profile_path))
284    return;
285
286  local_state_->SetString(
287      prefs::kAppListProfile,
288      profile_path.BaseName().MaybeAsASCII());
289}
290
291void AppListServiceImpl::CreateShortcut() {}
292
293// We need to watch for profile removal to keep kAppListProfile updated.
294void AppListServiceImpl::OnProfileWillBeRemoved(
295    const base::FilePath& profile_path) {
296  // If the profile the app list uses just got deleted, reset it to the last
297  // used profile.
298  std::string app_list_last_profile = local_state_->GetString(
299      prefs::kAppListProfile);
300  if (profile_path.BaseName().MaybeAsASCII() == app_list_last_profile) {
301    local_state_->SetString(prefs::kAppListProfile,
302        local_state_->GetString(prefs::kProfileLastUsed));
303  }
304}
305
306void AppListServiceImpl::Show() {
307  profile_loader_->LoadProfileInvalidatingOtherLoads(
308      GetProfilePath(profile_store_->GetUserDataDir()),
309      base::Bind(&AppListServiceImpl::ShowForProfile,
310                 weak_factory_.GetWeakPtr()));
311}
312
313void AppListServiceImpl::AutoShowForProfile(Profile* requested_profile) {
314  if (local_state_->GetInt64(prefs::kAppListEnableTime) != 0) {
315    // User has not yet discovered the app launcher. Update the enable method to
316    // indicate this. It will then be recorded in UMA.
317    local_state_->SetInteger(prefs::kAppListEnableMethod,
318                             ENABLE_SHOWN_UNDISCOVERED);
319  }
320  ShowForProfile(requested_profile);
321}
322
323void AppListServiceImpl::EnableAppList(Profile* initial_profile,
324                                       AppListEnableSource enable_source) {
325  SetProfilePath(initial_profile->GetPath());
326  // Always allow the webstore "enable" button to re-run the install flow.
327  if (enable_source != AppListService::ENABLE_VIA_WEBSTORE_LINK &&
328      local_state_->GetBoolean(prefs::kAppLauncherHasBeenEnabled)) {
329    return;
330  }
331
332  local_state_->SetBoolean(prefs::kAppLauncherHasBeenEnabled, true);
333  CreateShortcut();
334
335  // UMA for launcher discoverability.
336  local_state_->SetInt64(prefs::kAppListEnableTime,
337                         base::Time::Now().ToInternalValue());
338  local_state_->SetInteger(prefs::kAppListEnableMethod, enable_source);
339  if (base::MessageLoop::current()) {
340    // Ensure a value is recorded if the user "never" shows the app list. Note
341    // there is no message loop in unit tests.
342    base::MessageLoop::current()->PostDelayedTask(
343        FROM_HERE,
344        base::Bind(&RecordAppListDiscoverability,
345                   base::Unretained(local_state_),
346                   false),
347        base::TimeDelta::FromMinutes(kDiscoverabilityTimeoutMinutes));
348  }
349
350  AppShortcutManager* shortcut_manager =
351      AppShortcutManagerFactory::GetForProfile(initial_profile);
352  if (shortcut_manager)
353    shortcut_manager->OnceOffCreateShortcuts();
354}
355
356void AppListServiceImpl::InvalidatePendingProfileLoads() {
357  profile_loader_->InvalidatePendingProfileLoads();
358}
359
360void AppListServiceImpl::PerformStartupChecks(Profile* initial_profile) {
361  // Except in rare, once-off cases, this just checks that a pref is "0" and
362  // returns.
363  RecordAppListDiscoverability(local_state_, true);
364
365  if (command_line_.HasSwitch(switches::kResetAppListInstallState))
366    local_state_->SetBoolean(prefs::kAppLauncherHasBeenEnabled, false);
367
368  if (command_line_.HasSwitch(switches::kEnableAppList))
369    EnableAppList(initial_profile, ENABLE_VIA_COMMAND_LINE);
370
371  if (!base::MessageLoop::current())
372    return;  // In a unit test.
373
374  // Send app list usage stats after a delay.
375  const int kSendUsageStatsDelay = 5;
376  base::MessageLoop::current()->PostDelayedTask(
377      FROM_HERE,
378      base::Bind(&AppListServiceImpl::SendAppListStats),
379      base::TimeDelta::FromSeconds(kSendUsageStatsDelay));
380}
381