| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 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/extensions/apps_promo.h" | |
| 6 | |
| 7 #include "base/base64.h" | |
| 8 #include "base/command_line.h" | |
| 9 #include "base/metrics/histogram.h" | |
| 10 #include "chrome/browser/browser_process.h" | |
| 11 #include "chrome/browser/prefs/pref_service.h" | |
| 12 #include "chrome/browser/profiles/profile.h" | |
| 13 #include "chrome/common/chrome_notification_types.h" | |
| 14 #include "chrome/common/chrome_switches.h" | |
| 15 #include "chrome/common/extensions/extension.h" | |
| 16 #include "chrome/common/pref_names.h" | |
| 17 #include "content/public/browser/notification_service.h" | |
| 18 #include "content/public/common/url_constants.h" | |
| 19 #include "net/base/load_flags.h" | |
| 20 #include "net/url_request/url_fetcher.h" | |
| 21 #include "net/url_request/url_request_status.h" | |
| 22 | |
| 23 const int AppsPromo::kDefaultAppsCounterMax = 10; | |
| 24 | |
| 25 namespace { | |
| 26 | |
| 27 // The default logo for the promo. | |
| 28 const char kDefaultLogo[] = "chrome://theme/IDR_WEBSTORE_ICON"; | |
| 29 | |
| 30 // The default promo data (this is only used for testing with | |
| 31 // --force-apps-promo-visible). | |
| 32 const char kDefaultHeader[] = "Browse thousands of apps and games for Chrome"; | |
| 33 const char kDefaultButton[] = "Visit the Chrome Web Store"; | |
| 34 const char kDefaultExpire[] = "No thanks"; | |
| 35 const char kDefaultLink[] = "https://chrome.google.com/webstore"; | |
| 36 | |
| 37 // Http success status code. | |
| 38 const int kHttpSuccess = 200; | |
| 39 | |
| 40 // The match pattern for valid logo URLs. | |
| 41 const char kValidLogoPattern[] = "https://*.google.com/*.png"; | |
| 42 | |
| 43 // The prefix for 'data' URL images. | |
| 44 const char kPNGDataURLPrefix[] = "data:image/png;base64,"; | |
| 45 | |
| 46 // Returns the string pref at |path|, using |fallback| as the default (if there | |
| 47 // is no pref value present). |fallback| is used for debugging in concert with | |
| 48 // --force-apps-promo-visible. | |
| 49 std::string GetStringPref(const char* path, const std::string& fallback) { | |
| 50 PrefService* local_state = g_browser_process->local_state(); | |
| 51 std::string retval(local_state->GetString(path)); | |
| 52 if (retval.empty() && CommandLine::ForCurrentProcess()->HasSwitch( | |
| 53 switches::kForceAppsPromoVisible)) { | |
| 54 retval = fallback; | |
| 55 } | |
| 56 return retval; | |
| 57 } | |
| 58 | |
| 59 } // namespace | |
| 60 | |
| 61 AppsPromo::PromoData::PromoData() : user_group(AppsPromo::USERS_NONE) {} | |
| 62 AppsPromo::PromoData::PromoData(const std::string& id, | |
| 63 const std::string& header, | |
| 64 const std::string& button, | |
| 65 const GURL& link, | |
| 66 const std::string& expire, | |
| 67 const GURL& logo, | |
| 68 const int user_group) | |
| 69 : id(id), | |
| 70 header(header), | |
| 71 button(button), | |
| 72 link(link), | |
| 73 expire(expire), | |
| 74 logo(logo), | |
| 75 user_group(user_group) {} | |
| 76 | |
| 77 AppsPromo::PromoData::~PromoData() {} | |
| 78 | |
| 79 // static | |
| 80 void AppsPromo::RegisterPrefs(PrefService* local_state) { | |
| 81 std::string empty; | |
| 82 local_state->RegisterBooleanPref(prefs::kNtpWebStoreEnabled, false); | |
| 83 local_state->RegisterStringPref(prefs::kNtpWebStorePromoId, empty); | |
| 84 local_state->RegisterStringPref(prefs::kNtpWebStorePromoHeader, empty); | |
| 85 local_state->RegisterStringPref(prefs::kNtpWebStorePromoButton, empty); | |
| 86 local_state->RegisterStringPref(prefs::kNtpWebStorePromoLink, empty); | |
| 87 local_state->RegisterStringPref(prefs::kNtpWebStorePromoLogo, empty); | |
| 88 local_state->RegisterStringPref(prefs::kNtpWebStorePromoLogoSource, empty); | |
| 89 local_state->RegisterStringPref(prefs::kNtpWebStorePromoExpire, empty); | |
| 90 local_state->RegisterIntegerPref(prefs::kNtpWebStorePromoUserGroup, 0); | |
| 91 } | |
| 92 | |
| 93 // static | |
| 94 void AppsPromo::RegisterUserPrefs(PrefService* prefs) { | |
| 95 // Set the default value for the counter to max+1 since we don't install | |
| 96 // default apps for new users. | |
| 97 prefs->RegisterIntegerPref(prefs::kAppsPromoCounter, | |
| 98 kDefaultAppsCounterMax + 1, | |
| 99 PrefService::UNSYNCABLE_PREF); | |
| 100 prefs->RegisterBooleanPref(prefs::kDefaultAppsInstalled, | |
| 101 false, | |
| 102 PrefService::UNSYNCABLE_PREF); | |
| 103 prefs->RegisterStringPref(prefs::kNtpWebStorePromoLastId, | |
| 104 std::string(), | |
| 105 PrefService::UNSYNCABLE_PREF); | |
| 106 prefs->RegisterBooleanPref(prefs::kNtpHideWebStorePromo, | |
| 107 false, | |
| 108 PrefService::UNSYNCABLE_PREF); | |
| 109 } | |
| 110 | |
| 111 // static | |
| 112 bool AppsPromo::IsPromoSupportedForLocale() { | |
| 113 PrefService* local_state = g_browser_process->local_state(); | |
| 114 // PromoResourceService will clear the promo data if the current locale is | |
| 115 // not supported. | |
| 116 return local_state->HasPrefPath(prefs::kNtpWebStorePromoId) && | |
| 117 local_state->HasPrefPath(prefs::kNtpWebStorePromoHeader) && | |
| 118 local_state->HasPrefPath(prefs::kNtpWebStorePromoButton) && | |
| 119 local_state->HasPrefPath(prefs::kNtpWebStorePromoLink) && | |
| 120 local_state->HasPrefPath(prefs::kNtpWebStorePromoLogo) && | |
| 121 local_state->HasPrefPath(prefs::kNtpWebStorePromoExpire) && | |
| 122 local_state->HasPrefPath(prefs::kNtpWebStorePromoUserGroup); | |
| 123 } | |
| 124 | |
| 125 // static | |
| 126 bool AppsPromo::IsWebStoreSupportedForLocale() { | |
| 127 PrefService* local_state = g_browser_process->local_state(); | |
| 128 return local_state->GetBoolean(prefs::kNtpWebStoreEnabled); | |
| 129 } | |
| 130 | |
| 131 // static | |
| 132 void AppsPromo::SetWebStoreSupportedForLocale(bool supported) { | |
| 133 PrefService* local_state = g_browser_process->local_state(); | |
| 134 local_state->SetBoolean(prefs::kNtpWebStoreEnabled, supported); | |
| 135 } | |
| 136 | |
| 137 // static | |
| 138 void AppsPromo::ClearPromo() { | |
| 139 PrefService* local_state = g_browser_process->local_state(); | |
| 140 local_state->ClearPref(prefs::kNtpWebStoreEnabled); | |
| 141 local_state->ClearPref(prefs::kNtpWebStorePromoId); | |
| 142 local_state->ClearPref(prefs::kNtpWebStorePromoHeader); | |
| 143 local_state->ClearPref(prefs::kNtpWebStorePromoButton); | |
| 144 local_state->ClearPref(prefs::kNtpWebStorePromoLink); | |
| 145 local_state->ClearPref(prefs::kNtpWebStorePromoLogo); | |
| 146 local_state->ClearPref(prefs::kNtpWebStorePromoLogoSource); | |
| 147 local_state->ClearPref(prefs::kNtpWebStorePromoExpire); | |
| 148 local_state->ClearPref(prefs::kNtpWebStorePromoUserGroup); | |
| 149 } | |
| 150 | |
| 151 // static | |
| 152 AppsPromo::PromoData AppsPromo::GetPromo() { | |
| 153 PromoData data; | |
| 154 PrefService* local_state = g_browser_process->local_state(); | |
| 155 | |
| 156 data.id = GetStringPref(prefs::kNtpWebStorePromoId, ""); | |
| 157 data.link = GURL(GetStringPref(prefs::kNtpWebStorePromoLink, kDefaultLink)); | |
| 158 data.user_group = local_state->GetInteger(prefs::kNtpWebStorePromoUserGroup); | |
| 159 data.header = GetStringPref(prefs::kNtpWebStorePromoHeader, kDefaultHeader); | |
| 160 data.button = GetStringPref(prefs::kNtpWebStorePromoButton, kDefaultButton); | |
| 161 data.expire = GetStringPref(prefs::kNtpWebStorePromoExpire, kDefaultExpire); | |
| 162 | |
| 163 GURL logo_url(local_state->GetString(prefs::kNtpWebStorePromoLogo)); | |
| 164 if (logo_url.is_valid() && logo_url.SchemeIs(chrome::kDataScheme)) | |
| 165 data.logo = logo_url; | |
| 166 else | |
| 167 data.logo = GURL(kDefaultLogo); | |
| 168 | |
| 169 return data; | |
| 170 } | |
| 171 | |
| 172 // static | |
| 173 void AppsPromo::SetPromo(const AppsPromo::PromoData& data) { | |
| 174 PrefService* local_state = g_browser_process->local_state(); | |
| 175 local_state->SetString(prefs::kNtpWebStorePromoId, data.id); | |
| 176 local_state->SetString(prefs::kNtpWebStorePromoButton, data.button); | |
| 177 local_state->SetString(prefs::kNtpWebStorePromoHeader, data.header); | |
| 178 local_state->SetString(prefs::kNtpWebStorePromoLink, data.link.spec()); | |
| 179 local_state->SetString(prefs::kNtpWebStorePromoLogo, data.logo.spec()); | |
| 180 local_state->SetString(prefs::kNtpWebStorePromoExpire, data.expire); | |
| 181 local_state->SetInteger(prefs::kNtpWebStorePromoUserGroup, data.user_group); | |
| 182 } | |
| 183 | |
| 184 // static | |
| 185 GURL AppsPromo::GetSourcePromoLogoURL() { | |
| 186 return GURL(GetStringPref(prefs::kNtpWebStorePromoLogoSource, "")); | |
| 187 } | |
| 188 | |
| 189 // static | |
| 190 void AppsPromo::SetSourcePromoLogoURL(const GURL& logo_source) { | |
| 191 PrefService* local_state = g_browser_process->local_state(); | |
| 192 local_state->SetString(prefs::kNtpWebStorePromoLogoSource, | |
| 193 logo_source.spec()); | |
| 194 } | |
| 195 | |
| 196 AppsPromo::AppsPromo(PrefService* prefs) | |
| 197 : prefs_(prefs) { | |
| 198 // Poppit, Entanglement | |
| 199 old_default_app_ids_.insert("mcbkbpnkkkipelfledbfocopglifcfmi"); | |
| 200 old_default_app_ids_.insert("aciahcmjmecflokailenpkdchphgkefd"); | |
| 201 } | |
| 202 | |
| 203 AppsPromo::~AppsPromo() {} | |
| 204 | |
| 205 bool AppsPromo::ShouldShowPromo(const extensions::ExtensionIdSet& installed_ids, | |
| 206 bool* just_expired) { | |
| 207 *just_expired = false; | |
| 208 | |
| 209 if (CommandLine::ForCurrentProcess()->HasSwitch( | |
| 210 switches::kForceAppsPromoVisible)) { | |
| 211 return true; | |
| 212 } | |
| 213 | |
| 214 // Don't show the promo if the policy says not to. | |
| 215 if (prefs_->GetBoolean(prefs::kNtpHideWebStorePromo)) { | |
| 216 ExpireDefaultApps(); | |
| 217 return false; | |
| 218 } | |
| 219 | |
| 220 // Don't show the promo if one wasn't served to this locale. | |
| 221 if (!IsPromoSupportedForLocale()) | |
| 222 return false; | |
| 223 | |
| 224 int promo_counter = GetPromoCounter(); | |
| 225 if (GetDefaultAppsInstalled() && promo_counter <= kDefaultAppsCounterMax) { | |
| 226 // If the default apps were installed from a previous Chrome version, we | |
| 227 // should still show the promo. If we don't have the exact set of default | |
| 228 // apps, this means that the user manually installed or uninstalled one. | |
| 229 // We no longer keep track of the default apps once others have been | |
| 230 // installed, so expire them immediately. | |
| 231 if (old_default_app_ids_ != installed_ids) { | |
| 232 ExpireDefaultApps(); | |
| 233 return false; | |
| 234 } | |
| 235 | |
| 236 if (promo_counter == kDefaultAppsCounterMax) { | |
| 237 *just_expired = true; | |
| 238 | |
| 239 // The default apps have expired due to inaction, so ping PROMO_EXPIRE. | |
| 240 UMA_HISTOGRAM_ENUMERATION(extension_misc::kAppsPromoHistogram, | |
| 241 extension_misc::PROMO_EXPIRE, | |
| 242 extension_misc::PROMO_BUCKET_BOUNDARY); | |
| 243 | |
| 244 ExpireDefaultApps(); | |
| 245 } else { | |
| 246 SetPromoCounter(++promo_counter); | |
| 247 } | |
| 248 return true; | |
| 249 } else if (installed_ids.empty()) { | |
| 250 return true; | |
| 251 } | |
| 252 | |
| 253 return false; | |
| 254 } | |
| 255 | |
| 256 bool AppsPromo::ShouldShowAppLauncher( | |
| 257 const extensions::ExtensionIdSet& installed_ids) { | |
| 258 // On Chrome OS the default apps are installed via a separate mechanism that | |
| 259 // is always enabled. Therefore we always show the launcher. | |
| 260 #if defined(OS_CHROMEOS) | |
| 261 return true; | |
| 262 #else | |
| 263 | |
| 264 // Always show the app launcher if an app is installed. | |
| 265 if (!installed_ids.empty()) | |
| 266 return true; | |
| 267 | |
| 268 // Otherwise, only show the app launcher if the web store is supported for the | |
| 269 // current locale. | |
| 270 return IsWebStoreSupportedForLocale(); | |
| 271 #endif | |
| 272 } | |
| 273 | |
| 274 void AppsPromo::ExpireDefaultApps() { | |
| 275 SetPromoCounter(kDefaultAppsCounterMax + 1); | |
| 276 } | |
| 277 | |
| 278 void AppsPromo::HidePromo() { | |
| 279 UMA_HISTOGRAM_ENUMERATION(extension_misc::kAppsPromoHistogram, | |
| 280 extension_misc::PROMO_CLOSE, | |
| 281 extension_misc::PROMO_BUCKET_BOUNDARY); | |
| 282 | |
| 283 ExpireDefaultApps(); | |
| 284 } | |
| 285 | |
| 286 std::string AppsPromo::GetLastPromoId() { | |
| 287 return prefs_->GetString(prefs::kNtpWebStorePromoLastId); | |
| 288 } | |
| 289 | |
| 290 void AppsPromo::SetLastPromoId(const std::string& id) { | |
| 291 prefs_->SetString(prefs::kNtpWebStorePromoLastId, id); | |
| 292 } | |
| 293 | |
| 294 int AppsPromo::GetPromoCounter() const { | |
| 295 return prefs_->GetInteger(prefs::kAppsPromoCounter); | |
| 296 } | |
| 297 | |
| 298 void AppsPromo::SetPromoCounter(int val) { | |
| 299 prefs_->SetInteger(prefs::kAppsPromoCounter, val); | |
| 300 } | |
| 301 | |
| 302 bool AppsPromo::GetDefaultAppsInstalled() const { | |
| 303 return prefs_->GetBoolean(prefs::kDefaultAppsInstalled); | |
| 304 } | |
| 305 | |
| 306 AppsPromo::UserGroup AppsPromo::GetCurrentUserGroup() const { | |
| 307 const PrefService::Preference* last_promo_id | |
| 308 = prefs_->FindPreference(prefs::kNtpWebStorePromoLastId); | |
| 309 CHECK(last_promo_id); | |
| 310 return last_promo_id->IsDefaultValue() ? USERS_NEW : USERS_EXISTING; | |
| 311 } | |
| 312 | |
| 313 AppsPromoLogoFetcher::AppsPromoLogoFetcher( | |
| 314 Profile* profile, | |
| 315 const AppsPromo::PromoData& promo_data) | |
| 316 : profile_(profile), | |
| 317 promo_data_(promo_data) { | |
| 318 if (SupportsLogoURL()) { | |
| 319 if (HaveCachedLogo()) { | |
| 320 promo_data_.logo = AppsPromo::GetPromo().logo; | |
| 321 SavePromo(); | |
| 322 } else { | |
| 323 FetchLogo(); | |
| 324 } | |
| 325 } else { | |
| 326 // We only care about the source URL when this fetches the logo. | |
| 327 AppsPromo::SetSourcePromoLogoURL(GURL()); | |
| 328 SavePromo(); | |
| 329 } | |
| 330 } | |
| 331 | |
| 332 AppsPromoLogoFetcher::~AppsPromoLogoFetcher() {} | |
| 333 | |
| 334 void AppsPromoLogoFetcher::OnURLFetchComplete( | |
| 335 const net::URLFetcher* source) { | |
| 336 std::string data; | |
| 337 std::string base64_data; | |
| 338 | |
| 339 CHECK(source == url_fetcher_.get()); | |
| 340 source->GetResponseAsString(&data); | |
| 341 | |
| 342 if (source->GetStatus().is_success() && | |
| 343 source->GetResponseCode() == kHttpSuccess && | |
| 344 base::Base64Encode(data, &base64_data)) { | |
| 345 AppsPromo::SetSourcePromoLogoURL(promo_data_.logo); | |
| 346 promo_data_.logo = GURL(kPNGDataURLPrefix + base64_data); | |
| 347 } else { | |
| 348 // The logo wasn't downloaded correctly or we failed to encode it in | |
| 349 // base64. Reset the source URL so we fetch it again next time. AppsPromo | |
| 350 // will revert to the default logo. | |
| 351 AppsPromo::SetSourcePromoLogoURL(GURL()); | |
| 352 } | |
| 353 | |
| 354 SavePromo(); | |
| 355 } | |
| 356 | |
| 357 void AppsPromoLogoFetcher::FetchLogo() { | |
| 358 CHECK(promo_data_.logo.scheme() == chrome::kHttpsScheme); | |
| 359 | |
| 360 url_fetcher_.reset(net::URLFetcher::Create( | |
| 361 0, promo_data_.logo, net::URLFetcher::GET, this)); | |
| 362 url_fetcher_->SetRequestContext( | |
| 363 g_browser_process->system_request_context()); | |
| 364 url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | | |
| 365 net::LOAD_DO_NOT_SAVE_COOKIES); | |
| 366 url_fetcher_->Start(); | |
| 367 } | |
| 368 | |
| 369 bool AppsPromoLogoFetcher::HaveCachedLogo() { | |
| 370 return promo_data_.logo == AppsPromo::GetSourcePromoLogoURL(); | |
| 371 } | |
| 372 | |
| 373 void AppsPromoLogoFetcher::SavePromo() { | |
| 374 AppsPromo::SetPromo(promo_data_); | |
| 375 | |
| 376 content::NotificationService::current()->Notify( | |
| 377 chrome::NOTIFICATION_WEB_STORE_PROMO_LOADED, | |
| 378 content::Source<Profile>(profile_), | |
| 379 content::NotificationService::NoDetails()); | |
| 380 } | |
| 381 | |
| 382 bool AppsPromoLogoFetcher::SupportsLogoURL() { | |
| 383 URLPattern allowed_urls(URLPattern::SCHEME_HTTPS, kValidLogoPattern); | |
| 384 return allowed_urls.MatchesURL(promo_data_.logo); | |
| 385 } | |
| OLD | NEW |