blob: 70f0f6492804d1f0ea4753f2f959a8aa47fe414b [file] [log] [blame]
// Copyright (c) 2012 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/extensions/apps_promo.h"
#include "base/base64.h"
#include "base/command_line.h"
#include "base/metrics/histogram.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/pref_names.h"
#include "content/public/browser/notification_service.h"
#include "content/public/common/url_constants.h"
#include "content/public/common/url_fetcher.h"
#include "net/base/load_flags.h"
#include "net/url_request/url_request_status.h"
const int AppsPromo::kDefaultAppsCounterMax = 10;
namespace {
// The default logo for the promo.
const char kDefaultLogo[] = "chrome://theme/IDR_WEBSTORE_ICON";
// The default promo data (this is only used for testing with
// --force-apps-promo-visible).
const char kDefaultHeader[] = "Browse thousands of apps and games for Chrome";
const char kDefaultButton[] = "Visit the Chrome Web Store";
const char kDefaultExpire[] = "No thanks";
const char kDefaultLink[] = "https://chrome.google.com/webstore";
// Http success status code.
const int kHttpSuccess = 200;
// The match pattern for valid logo URLs.
const char kValidLogoPattern[] = "https://*.google.com/*.png";
// The prefix for 'data' URL images.
const char kPNGDataURLPrefix[] = "data:image/png;base64,";
// Returns the string pref at |path|, using |fallback| as the default (if there
// is no pref value present). |fallback| is used for debugging in concert with
// --force-apps-promo-visible.
std::string GetStringPref(const char* path, const std::string& fallback) {
PrefService* local_state = g_browser_process->local_state();
std::string retval(local_state->GetString(path));
if (retval.empty() && CommandLine::ForCurrentProcess()->HasSwitch(
switches::kForceAppsPromoVisible)) {
retval = fallback;
}
return retval;
}
} // namespace
AppsPromo::PromoData::PromoData() : user_group(AppsPromo::USERS_NONE) {}
AppsPromo::PromoData::PromoData(const std::string& id,
const std::string& header,
const std::string& button,
const GURL& link,
const std::string& expire,
const GURL& logo,
const int user_group)
: id(id),
header(header),
button(button),
link(link),
expire(expire),
logo(logo),
user_group(user_group) {}
AppsPromo::PromoData::~PromoData() {}
// static
void AppsPromo::RegisterPrefs(PrefService* local_state) {
std::string empty;
local_state->RegisterBooleanPref(prefs::kNtpWebStoreEnabled, false);
local_state->RegisterStringPref(prefs::kNtpWebStorePromoId, empty);
local_state->RegisterStringPref(prefs::kNtpWebStorePromoHeader, empty);
local_state->RegisterStringPref(prefs::kNtpWebStorePromoButton, empty);
local_state->RegisterStringPref(prefs::kNtpWebStorePromoLink, empty);
local_state->RegisterStringPref(prefs::kNtpWebStorePromoLogo, empty);
local_state->RegisterStringPref(prefs::kNtpWebStorePromoLogoSource, empty);
local_state->RegisterStringPref(prefs::kNtpWebStorePromoExpire, empty);
local_state->RegisterIntegerPref(prefs::kNtpWebStorePromoUserGroup, 0);
}
// static
void AppsPromo::RegisterUserPrefs(PrefService* prefs) {
// Set the default value for the counter to max+1 since we don't install
// default apps for new users.
prefs->RegisterIntegerPref(prefs::kAppsPromoCounter,
kDefaultAppsCounterMax + 1,
PrefService::UNSYNCABLE_PREF);
prefs->RegisterBooleanPref(prefs::kDefaultAppsInstalled,
false,
PrefService::UNSYNCABLE_PREF);
prefs->RegisterStringPref(prefs::kNtpWebStorePromoLastId,
std::string(),
PrefService::UNSYNCABLE_PREF);
prefs->RegisterBooleanPref(prefs::kNtpHideWebStorePromo,
false,
PrefService::UNSYNCABLE_PREF);
}
// static
bool AppsPromo::IsPromoSupportedForLocale() {
PrefService* local_state = g_browser_process->local_state();
// PromoResourceService will clear the promo data if the current locale is
// not supported.
return local_state->HasPrefPath(prefs::kNtpWebStorePromoId) &&
local_state->HasPrefPath(prefs::kNtpWebStorePromoHeader) &&
local_state->HasPrefPath(prefs::kNtpWebStorePromoButton) &&
local_state->HasPrefPath(prefs::kNtpWebStorePromoLink) &&
local_state->HasPrefPath(prefs::kNtpWebStorePromoLogo) &&
local_state->HasPrefPath(prefs::kNtpWebStorePromoExpire) &&
local_state->HasPrefPath(prefs::kNtpWebStorePromoUserGroup);
}
// static
bool AppsPromo::IsWebStoreSupportedForLocale() {
PrefService* local_state = g_browser_process->local_state();
return local_state->GetBoolean(prefs::kNtpWebStoreEnabled);
}
// static
void AppsPromo::SetWebStoreSupportedForLocale(bool supported) {
PrefService* local_state = g_browser_process->local_state();
local_state->SetBoolean(prefs::kNtpWebStoreEnabled, supported);
}
// static
void AppsPromo::ClearPromo() {
PrefService* local_state = g_browser_process->local_state();
local_state->ClearPref(prefs::kNtpWebStoreEnabled);
local_state->ClearPref(prefs::kNtpWebStorePromoId);
local_state->ClearPref(prefs::kNtpWebStorePromoHeader);
local_state->ClearPref(prefs::kNtpWebStorePromoButton);
local_state->ClearPref(prefs::kNtpWebStorePromoLink);
local_state->ClearPref(prefs::kNtpWebStorePromoLogo);
local_state->ClearPref(prefs::kNtpWebStorePromoLogoSource);
local_state->ClearPref(prefs::kNtpWebStorePromoExpire);
local_state->ClearPref(prefs::kNtpWebStorePromoUserGroup);
}
// static
AppsPromo::PromoData AppsPromo::GetPromo() {
PromoData data;
PrefService* local_state = g_browser_process->local_state();
data.id = GetStringPref(prefs::kNtpWebStorePromoId, "");
data.link = GURL(GetStringPref(prefs::kNtpWebStorePromoLink, kDefaultLink));
data.user_group = local_state->GetInteger(prefs::kNtpWebStorePromoUserGroup);
data.header = GetStringPref(prefs::kNtpWebStorePromoHeader, kDefaultHeader);
data.button = GetStringPref(prefs::kNtpWebStorePromoButton, kDefaultButton);
data.expire = GetStringPref(prefs::kNtpWebStorePromoExpire, kDefaultExpire);
GURL logo_url(local_state->GetString(prefs::kNtpWebStorePromoLogo));
if (logo_url.is_valid() && logo_url.SchemeIs(chrome::kDataScheme))
data.logo = logo_url;
else
data.logo = GURL(kDefaultLogo);
return data;
}
// static
void AppsPromo::SetPromo(const AppsPromo::PromoData& data) {
PrefService* local_state = g_browser_process->local_state();
local_state->SetString(prefs::kNtpWebStorePromoId, data.id);
local_state->SetString(prefs::kNtpWebStorePromoButton, data.button);
local_state->SetString(prefs::kNtpWebStorePromoHeader, data.header);
local_state->SetString(prefs::kNtpWebStorePromoLink, data.link.spec());
local_state->SetString(prefs::kNtpWebStorePromoLogo, data.logo.spec());
local_state->SetString(prefs::kNtpWebStorePromoExpire, data.expire);
local_state->SetInteger(prefs::kNtpWebStorePromoUserGroup, data.user_group);
}
// static
GURL AppsPromo::GetSourcePromoLogoURL() {
return GURL(GetStringPref(prefs::kNtpWebStorePromoLogoSource, ""));
}
// static
void AppsPromo::SetSourcePromoLogoURL(const GURL& logo_source) {
PrefService* local_state = g_browser_process->local_state();
local_state->SetString(prefs::kNtpWebStorePromoLogoSource,
logo_source.spec());
}
AppsPromo::AppsPromo(PrefService* prefs)
: prefs_(prefs) {
// Poppit, Entanglement
old_default_app_ids_.insert("mcbkbpnkkkipelfledbfocopglifcfmi");
old_default_app_ids_.insert("aciahcmjmecflokailenpkdchphgkefd");
}
AppsPromo::~AppsPromo() {}
bool AppsPromo::ShouldShowPromo(const extensions::ExtensionIdSet& installed_ids,
bool* just_expired) {
*just_expired = false;
if (CommandLine::ForCurrentProcess()->HasSwitch(
switches::kForceAppsPromoVisible)) {
return true;
}
// Don't show the promo if the policy says not to.
if (prefs_->GetBoolean(prefs::kNtpHideWebStorePromo)) {
ExpireDefaultApps();
return false;
}
// Don't show the promo if one wasn't served to this locale.
if (!IsPromoSupportedForLocale())
return false;
int promo_counter = GetPromoCounter();
if (GetDefaultAppsInstalled() && promo_counter <= kDefaultAppsCounterMax) {
// If the default apps were installed from a previous Chrome version, we
// should still show the promo. If we don't have the exact set of default
// apps, this means that the user manually installed or uninstalled one.
// We no longer keep track of the default apps once others have been
// installed, so expire them immediately.
if (old_default_app_ids_ != installed_ids) {
ExpireDefaultApps();
return false;
}
if (promo_counter == kDefaultAppsCounterMax) {
*just_expired = true;
// The default apps have expired due to inaction, so ping PROMO_EXPIRE.
UMA_HISTOGRAM_ENUMERATION(extension_misc::kAppsPromoHistogram,
extension_misc::PROMO_EXPIRE,
extension_misc::PROMO_BUCKET_BOUNDARY);
ExpireDefaultApps();
} else {
SetPromoCounter(++promo_counter);
}
return true;
} else if (installed_ids.empty()) {
return true;
}
return false;
}
bool AppsPromo::ShouldShowAppLauncher(
const extensions::ExtensionIdSet& installed_ids) {
// On Chrome OS the default apps are installed via a separate mechanism that
// is always enabled. Therefore we always show the launcher.
#if defined(OS_CHROMEOS)
return true;
#else
// Always show the app launcher if an app is installed.
if (!installed_ids.empty())
return true;
// Otherwise, only show the app launcher if the web store is supported for the
// current locale.
return IsWebStoreSupportedForLocale();
#endif
}
void AppsPromo::ExpireDefaultApps() {
SetPromoCounter(kDefaultAppsCounterMax + 1);
}
void AppsPromo::HidePromo() {
UMA_HISTOGRAM_ENUMERATION(extension_misc::kAppsPromoHistogram,
extension_misc::PROMO_CLOSE,
extension_misc::PROMO_BUCKET_BOUNDARY);
ExpireDefaultApps();
}
std::string AppsPromo::GetLastPromoId() {
return prefs_->GetString(prefs::kNtpWebStorePromoLastId);
}
void AppsPromo::SetLastPromoId(const std::string& id) {
prefs_->SetString(prefs::kNtpWebStorePromoLastId, id);
}
int AppsPromo::GetPromoCounter() const {
return prefs_->GetInteger(prefs::kAppsPromoCounter);
}
void AppsPromo::SetPromoCounter(int val) {
prefs_->SetInteger(prefs::kAppsPromoCounter, val);
}
bool AppsPromo::GetDefaultAppsInstalled() const {
return prefs_->GetBoolean(prefs::kDefaultAppsInstalled);
}
AppsPromo::UserGroup AppsPromo::GetCurrentUserGroup() const {
const PrefService::Preference* last_promo_id
= prefs_->FindPreference(prefs::kNtpWebStorePromoLastId);
CHECK(last_promo_id);
return last_promo_id->IsDefaultValue() ? USERS_NEW : USERS_EXISTING;
}
AppsPromoLogoFetcher::AppsPromoLogoFetcher(
Profile* profile,
const AppsPromo::PromoData& promo_data)
: profile_(profile),
promo_data_(promo_data) {
if (SupportsLogoURL()) {
if (HaveCachedLogo()) {
promo_data_.logo = AppsPromo::GetPromo().logo;
SavePromo();
} else {
FetchLogo();
}
} else {
// We only care about the source URL when this fetches the logo.
AppsPromo::SetSourcePromoLogoURL(GURL());
SavePromo();
}
}
AppsPromoLogoFetcher::~AppsPromoLogoFetcher() {}
void AppsPromoLogoFetcher::OnURLFetchComplete(
const net::URLFetcher* source) {
std::string data;
std::string base64_data;
CHECK(source == url_fetcher_.get());
source->GetResponseAsString(&data);
if (source->GetStatus().is_success() &&
source->GetResponseCode() == kHttpSuccess &&
base::Base64Encode(data, &base64_data)) {
AppsPromo::SetSourcePromoLogoURL(promo_data_.logo);
promo_data_.logo = GURL(kPNGDataURLPrefix + base64_data);
} else {
// The logo wasn't downloaded correctly or we failed to encode it in
// base64. Reset the source URL so we fetch it again next time. AppsPromo
// will revert to the default logo.
AppsPromo::SetSourcePromoLogoURL(GURL());
}
SavePromo();
}
void AppsPromoLogoFetcher::FetchLogo() {
CHECK(promo_data_.logo.scheme() == chrome::kHttpsScheme);
url_fetcher_.reset(content::URLFetcher::Create(
0, promo_data_.logo, content::URLFetcher::GET, this));
url_fetcher_->SetRequestContext(
g_browser_process->system_request_context());
url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
net::LOAD_DO_NOT_SAVE_COOKIES);
url_fetcher_->Start();
}
bool AppsPromoLogoFetcher::HaveCachedLogo() {
return promo_data_.logo == AppsPromo::GetSourcePromoLogoURL();
}
void AppsPromoLogoFetcher::SavePromo() {
AppsPromo::SetPromo(promo_data_);
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_WEB_STORE_PROMO_LOADED,
content::Source<Profile>(profile_),
content::NotificationService::NoDetails());
}
bool AppsPromoLogoFetcher::SupportsLogoURL() {
URLPattern allowed_urls(URLPattern::SCHEME_HTTPS, kValidLogoPattern);
return allowed_urls.MatchesURL(promo_data_.logo);
}