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 |