blob: 8031b69075cbc8e73aaab05278fc81496b65e19d [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/app_list/app_list_service_impl.h"
#include <string>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/metrics/histogram.h"
#include "base/prefs/pref_service.h"
#include "base/strings/string16.h"
#include "base/time/time.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_shutdown.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/app_list/app_list_view_delegate.h"
#include "chrome/browser/ui/app_list/profile_loader.h"
#include "chrome/browser/ui/app_list/profile_store.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "ui/app_list/app_list_model.h"
namespace {
const int kDiscoverabilityTimeoutMinutes = 60;
void SendAppListAppLaunch(int count) {
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Apps.AppListDailyAppLaunches", count, 1, 1000, 50);
if (count > 0)
UMA_HISTOGRAM_ENUMERATION("Apps.AppListHasLaunchedAppToday", 1, 2);
}
void SendAppListLaunch(int count) {
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Apps.AppListDailyLaunches", count, 1, 1000, 50);
if (count > 0)
UMA_HISTOGRAM_ENUMERATION("Apps.AppListHasLaunchedAppListToday", 1, 2);
}
bool SendDailyEventFrequency(
const char* last_ping_pref,
const char* count_pref,
void (*send_callback)(int count)) {
PrefService* local_state = g_browser_process->local_state();
base::Time now = base::Time::Now();
base::Time last = base::Time::FromInternalValue(local_state->GetInt64(
last_ping_pref));
int days = (now - last).InDays();
if (days > 0) {
send_callback(local_state->GetInteger(count_pref));
local_state->SetInt64(
last_ping_pref,
(last + base::TimeDelta::FromDays(days)).ToInternalValue());
local_state->SetInteger(count_pref, 0);
return true;
}
return false;
}
void RecordDailyEventFrequency(
const char* last_ping_pref,
const char* count_pref,
void (*send_callback)(int count)) {
if (!g_browser_process)
return; // In a unit test.
PrefService* local_state = g_browser_process->local_state();
if (!local_state)
return; // In a unit test.
int count = local_state->GetInteger(count_pref);
local_state->SetInteger(count_pref, count + 1);
if (SendDailyEventFrequency(last_ping_pref, count_pref, send_callback)) {
local_state->SetInteger(count_pref, 1);
}
}
class ProfileStoreImpl : public ProfileStore {
public:
explicit ProfileStoreImpl(ProfileManager* profile_manager)
: profile_manager_(profile_manager),
weak_factory_(this) {
}
void AddProfileObserver(ProfileInfoCacheObserver* observer) override {
profile_manager_->GetProfileInfoCache().AddObserver(observer);
}
void LoadProfileAsync(const base::FilePath& path,
base::Callback<void(Profile*)> callback) override {
profile_manager_->CreateProfileAsync(
path,
base::Bind(&ProfileStoreImpl::OnProfileCreated,
weak_factory_.GetWeakPtr(),
callback),
base::string16(),
base::string16(),
std::string());
}
void OnProfileCreated(base::Callback<void(Profile*)> callback,
Profile* profile,
Profile::CreateStatus status) {
switch (status) {
case Profile::CREATE_STATUS_CREATED:
break;
case Profile::CREATE_STATUS_INITIALIZED:
callback.Run(profile);
break;
case Profile::CREATE_STATUS_LOCAL_FAIL:
case Profile::CREATE_STATUS_REMOTE_FAIL:
case Profile::CREATE_STATUS_CANCELED:
break;
case Profile::MAX_CREATE_STATUS:
NOTREACHED();
break;
}
}
Profile* GetProfileByPath(const base::FilePath& path) override {
return profile_manager_->GetProfileByPath(path);
}
base::FilePath GetUserDataDir() override {
return profile_manager_->user_data_dir();
}
bool IsProfileSupervised(const base::FilePath& profile_path) override {
ProfileInfoCache& profile_info =
g_browser_process->profile_manager()->GetProfileInfoCache();
size_t profile_index = profile_info.GetIndexOfProfileWithPath(profile_path);
return profile_index != std::string::npos &&
profile_info.ProfileIsSupervisedAtIndex(profile_index);
}
private:
ProfileManager* profile_manager_;
base::WeakPtrFactory<ProfileStoreImpl> weak_factory_;
};
void RecordAppListDiscoverability(PrefService* local_state,
bool is_startup_check) {
// Since this task may be delayed, ensure it does not interfere with shutdown
// when they unluckily coincide.
if (browser_shutdown::IsTryingToQuit())
return;
int64 enable_time_value = local_state->GetInt64(prefs::kAppListEnableTime);
if (enable_time_value == 0)
return; // Already recorded or never enabled.
base::Time app_list_enable_time =
base::Time::FromInternalValue(enable_time_value);
if (is_startup_check) {
// When checking at startup, only clear and record the "timeout" case,
// otherwise wait for a timeout.
base::TimeDelta time_remaining =
app_list_enable_time +
base::TimeDelta::FromMinutes(kDiscoverabilityTimeoutMinutes) -
base::Time::Now();
if (time_remaining > base::TimeDelta()) {
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&RecordAppListDiscoverability,
base::Unretained(local_state),
false),
time_remaining);
return;
}
}
local_state->SetInt64(prefs::kAppListEnableTime, 0);
AppListService::AppListEnableSource enable_source =
static_cast<AppListService::AppListEnableSource>(
local_state->GetInteger(prefs::kAppListEnableMethod));
if (enable_source == AppListService::ENABLE_FOR_APP_INSTALL) {
base::TimeDelta time_taken = base::Time::Now() - app_list_enable_time;
// This means the user "discovered" the app launcher naturally, after it was
// enabled on the first app install. Record how long it took to discover.
// Note that the last bucket is essentially "not discovered": subtract 1
// minute to account for clock inaccuracy.
UMA_HISTOGRAM_CUSTOM_TIMES(
"Apps.AppListTimeToDiscover",
time_taken,
base::TimeDelta::FromSeconds(1),
base::TimeDelta::FromMinutes(kDiscoverabilityTimeoutMinutes - 1),
10 /* bucket_count */);
}
UMA_HISTOGRAM_ENUMERATION("Apps.AppListHowEnabled",
enable_source,
AppListService::ENABLE_NUM_ENABLE_SOURCES);
}
} // namespace
void AppListServiceImpl::RecordAppListLaunch() {
RecordDailyEventFrequency(prefs::kLastAppListLaunchPing,
prefs::kAppListLaunchCount,
&SendAppListLaunch);
RecordAppListDiscoverability(local_state_, false);
}
// static
void AppListServiceImpl::RecordAppListAppLaunch() {
RecordDailyEventFrequency(prefs::kLastAppListAppLaunchPing,
prefs::kAppListAppLaunchCount,
&SendAppListAppLaunch);
}
// static
void AppListServiceImpl::SendAppListStats() {
if (!g_browser_process || g_browser_process->IsShuttingDown())
return;
SendDailyEventFrequency(prefs::kLastAppListLaunchPing,
prefs::kAppListLaunchCount,
&SendAppListLaunch);
SendDailyEventFrequency(prefs::kLastAppListAppLaunchPing,
prefs::kAppListAppLaunchCount,
&SendAppListAppLaunch);
}
AppListServiceImpl::AppListServiceImpl()
: profile_store_(
new ProfileStoreImpl(g_browser_process->profile_manager())),
command_line_(*CommandLine::ForCurrentProcess()),
local_state_(g_browser_process->local_state()),
profile_loader_(new ProfileLoader(profile_store_.get())),
weak_factory_(this) {
profile_store_->AddProfileObserver(this);
}
AppListServiceImpl::AppListServiceImpl(const CommandLine& command_line,
PrefService* local_state,
scoped_ptr<ProfileStore> profile_store)
: profile_store_(profile_store.Pass()),
command_line_(command_line),
local_state_(local_state),
profile_loader_(new ProfileLoader(profile_store_.get())),
weak_factory_(this) {
profile_store_->AddProfileObserver(this);
}
AppListServiceImpl::~AppListServiceImpl() {}
AppListViewDelegate* AppListServiceImpl::GetViewDelegate(Profile* profile) {
if (!view_delegate_)
view_delegate_.reset(new AppListViewDelegate(GetControllerDelegate()));
view_delegate_->SetProfile(profile);
return view_delegate_.get();
}
void AppListServiceImpl::SetAppListNextPaintCallback(void (*callback)()) {}
void AppListServiceImpl::HandleFirstRun() {}
void AppListServiceImpl::Init(Profile* initial_profile) {}
base::FilePath AppListServiceImpl::GetProfilePath(
const base::FilePath& user_data_dir) {
std::string app_list_profile;
if (local_state_->HasPrefPath(prefs::kAppListProfile))
app_list_profile = local_state_->GetString(prefs::kAppListProfile);
// If the user has no profile preference for the app launcher, default to the
// last browser profile used.
if (app_list_profile.empty() &&
local_state_->HasPrefPath(prefs::kProfileLastUsed)) {
app_list_profile = local_state_->GetString(prefs::kProfileLastUsed);
}
// If there is no last used profile recorded, use the initial profile.
if (app_list_profile.empty())
app_list_profile = chrome::kInitialProfile;
return user_data_dir.AppendASCII(app_list_profile);
}
void AppListServiceImpl::SetProfilePath(const base::FilePath& profile_path) {
local_state_->SetString(
prefs::kAppListProfile,
profile_path.BaseName().MaybeAsASCII());
}
void AppListServiceImpl::CreateShortcut() {}
void AppListServiceImpl::OnProfileWillBeRemoved(
const base::FilePath& profile_path) {
// We need to watch for profile removal to keep kAppListProfile updated, for
// the case that the deleted profile is being used by the app list.
std::string app_list_last_profile = local_state_->GetString(
prefs::kAppListProfile);
if (profile_path.BaseName().MaybeAsASCII() != app_list_last_profile)
return;
// Switch the app list over to a valid profile.
// Before ProfileInfoCache::DeleteProfileFromCache() calls this function,
// ProfileManager::ScheduleProfileForDeletion() will have checked to see if
// the deleted profile was also "last used", and updated that setting with
// something valid.
local_state_->SetString(prefs::kAppListProfile,
local_state_->GetString(prefs::kProfileLastUsed));
// If the app list was never shown, there won't be a |view_delegate_| yet.
if (!view_delegate_)
return;
// The Chrome AppListViewDelegate now needs its profile cleared, because:
// 1. it has many references to the profile and can't be profile-keyed, and
// 2. the last used profile might not be loaded yet.
// - this loading is sometimes done by the ProfileManager asynchronously,
// so the app list can't just switch to that.
// Only Mac supports showing the app list with a NULL profile, so tear down
// the view.
DestroyAppList();
view_delegate_->SetProfile(NULL);
}
void AppListServiceImpl::Show() {
profile_loader_->LoadProfileInvalidatingOtherLoads(
GetProfilePath(profile_store_->GetUserDataDir()),
base::Bind(&AppListServiceImpl::ShowForProfile,
weak_factory_.GetWeakPtr()));
}
void AppListServiceImpl::ShowForVoiceSearch(Profile* profile) {
ShowForProfile(profile);
view_delegate_->ToggleSpeechRecognition();
}
void AppListServiceImpl::ShowForAppInstall(Profile* profile,
const std::string& extension_id,
bool start_discovery_tracking) {
if (start_discovery_tracking) {
CreateForProfile(profile);
} else {
// Check if the app launcher has not yet been shown ever. Since this will
// show it, if discoverability UMA hasn't yet been recorded, it needs to be
// counted as undiscovered.
if (local_state_->GetInt64(prefs::kAppListEnableTime) != 0) {
local_state_->SetInteger(prefs::kAppListEnableMethod,
ENABLE_SHOWN_UNDISCOVERED);
}
ShowForProfile(profile);
}
if (extension_id.empty())
return; // Nothing to highlight. Only used in tests.
// The only way an install can happen is with the profile already loaded. So,
// ShowForProfile() can never be asynchronous, and the model is guaranteed to
// exist after a show.
DCHECK(view_delegate_->GetModel());
view_delegate_->GetModel()
->top_level_item_list()
->HighlightItemInstalledFromUI(extension_id);
}
void AppListServiceImpl::EnableAppList(Profile* initial_profile,
AppListEnableSource enable_source) {
SetProfilePath(initial_profile->GetPath());
// Always allow the webstore "enable" button to re-run the install flow.
if (enable_source != AppListService::ENABLE_VIA_WEBSTORE_LINK &&
local_state_->GetBoolean(prefs::kAppLauncherHasBeenEnabled)) {
return;
}
local_state_->SetBoolean(prefs::kAppLauncherHasBeenEnabled, true);
CreateShortcut();
// UMA for launcher discoverability.
local_state_->SetInt64(prefs::kAppListEnableTime,
base::Time::Now().ToInternalValue());
local_state_->SetInteger(prefs::kAppListEnableMethod, enable_source);
if (base::MessageLoop::current()) {
// Ensure a value is recorded if the user "never" shows the app list. Note
// there is no message loop in unit tests.
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&RecordAppListDiscoverability,
base::Unretained(local_state_),
false),
base::TimeDelta::FromMinutes(kDiscoverabilityTimeoutMinutes));
}
}
void AppListServiceImpl::InvalidatePendingProfileLoads() {
profile_loader_->InvalidatePendingProfileLoads();
}
void AppListServiceImpl::PerformStartupChecks(Profile* initial_profile) {
// Except in rare, once-off cases, this just checks that a pref is "0" and
// returns.
RecordAppListDiscoverability(local_state_, true);
if (command_line_.HasSwitch(switches::kResetAppListInstallState))
local_state_->SetBoolean(prefs::kAppLauncherHasBeenEnabled, false);
if (command_line_.HasSwitch(switches::kEnableAppList))
EnableAppList(initial_profile, ENABLE_VIA_COMMAND_LINE);
if (!base::MessageLoop::current())
return; // In a unit test.
// Send app list usage stats after a delay.
const int kSendUsageStatsDelay = 5;
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&AppListServiceImpl::SendAppListStats),
base::TimeDelta::FromSeconds(kSendUsageStatsDelay));
}