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/ui/webui/ntp/android/promo_handler.h" | |
6 | |
7 #include "base/logging.h" | |
8 #include "base/memory/ref_counted_memory.h" | |
9 #include "base/string_number_conversions.h" | |
10 #include "base/string_util.h" | |
11 #include "chrome/browser/android/intent_helper.h" | |
12 #include "chrome/browser/prefs/pref_service.h" | |
13 #include "chrome/browser/profiles/profile.h" | |
14 #include "chrome/browser/profiles/profile_manager.h" | |
15 #include "chrome/browser/signin/signin_manager.h" | |
16 #include "chrome/browser/sync/glue/session_model_associator.h" | |
17 #include "chrome/browser/sync/glue/synced_session.h" | |
18 #include "chrome/browser/sync/profile_sync_service.h" | |
19 #include "chrome/browser/sync/profile_sync_service_factory.h" | |
20 #include "chrome/browser/web_resource/notification_promo.h" | |
21 #include "chrome/browser/web_resource/notification_promo_mobile_ntp.h" | |
22 #include "chrome/browser/web_resource/promo_resource_service.h" | |
23 #include "chrome/common/chrome_notification_types.h" | |
24 #include "chrome/common/pref_names.h" | |
25 #include "content/public/browser/browser_thread.h" | |
26 #include "content/public/browser/notification_service.h" | |
27 #include "content/public/browser/user_metrics.h" | |
28 #include "content/public/browser/web_contents.h" | |
29 | |
30 using content::BrowserThread; | |
31 | |
32 namespace { | |
33 | |
34 // Helper to ask whether the promo is active. | |
35 bool CanShowNotificationPromo(Profile* profile) { | |
36 NotificationPromo notification_promo(profile); | |
37 notification_promo.InitFromPrefs(NotificationPromo::MOBILE_NTP_SYNC_PROMO); | |
38 return notification_promo.CanShow(); | |
39 } | |
40 | |
41 // Helper to send out promo resource change notification. | |
42 void Notify(PromoHandler* ph, chrome::NotificationType notification_type) { | |
43 content::NotificationService* service = | |
44 content::NotificationService::current(); | |
45 service->Notify(notification_type, | |
46 content::Source<PromoHandler>(ph), | |
47 content::NotificationService::NoDetails()); | |
48 } | |
49 | |
50 // Replaces all formatting markup in the promo with the corresponding HTML. | |
51 string16 ReplaceSimpleMarkupWithHtml(string16 text) { | |
52 const string16 LINE_BREAK = ASCIIToUTF16("<br/>"); | |
53 const string16 SYNCGRAPHIC_IMAGE = | |
54 ASCIIToUTF16("<div class=\"promo-sync-graphic\"></div>"); | |
55 const string16 BEGIN_HIGHLIGHT = | |
56 ASCIIToUTF16( | |
57 "<div style=\"text-align: center\"><button class=\"promo-button\">"); | |
58 const string16 END_HIGHLIGHT = | |
59 ASCIIToUTF16("</button></div>"); | |
60 const string16 BEGIN_LINK = | |
61 ASCIIToUTF16("<span style=\"color: blue; text-decoration: underline;\">"); | |
62 const string16 END_LINK = | |
63 ASCIIToUTF16("</span>"); | |
64 const string16 BEGIN_PROMO_AREA = | |
65 ASCIIToUTF16("<div class=\"promo-action-target\">"); | |
66 const string16 END_PROMO_AREA = | |
67 ASCIIToUTF16("</div>"); | |
68 | |
69 ReplaceSubstringsAfterOffset( | |
70 &text, 0, ASCIIToUTF16("LINE_BREAK"), LINE_BREAK); | |
71 ReplaceSubstringsAfterOffset( | |
72 &text, 0, ASCIIToUTF16("SYNCGRAPHIC_IMAGE"), SYNCGRAPHIC_IMAGE); | |
73 ReplaceSubstringsAfterOffset( | |
74 &text, 0, ASCIIToUTF16("BEGIN_HIGHLIGHT"), BEGIN_HIGHLIGHT); | |
75 ReplaceSubstringsAfterOffset( | |
76 &text, 0, ASCIIToUTF16("END_HIGHLIGHT"), END_HIGHLIGHT); | |
77 ReplaceSubstringsAfterOffset( | |
78 &text, 0, ASCIIToUTF16("BEGIN_LINK"), BEGIN_LINK); | |
79 ReplaceSubstringsAfterOffset( | |
80 &text, 0, ASCIIToUTF16("END_LINK"), END_LINK); | |
81 return BEGIN_PROMO_AREA + text + END_PROMO_AREA; | |
82 } | |
83 | |
84 } // namespace | |
85 | |
86 PromoHandler::PromoHandler() { | |
87 // Watch for pref changes that cause us to need to re-inject promolines. | |
88 registrar_.Add(this, chrome::NOTIFICATION_PROMO_RESOURCE_STATE_CHANGED, | |
89 content::NotificationService::AllSources()); | |
90 | |
91 // Watch for sync service updates that could cause re-injections | |
92 registrar_.Add(this, chrome::NOTIFICATION_SYNC_CONFIGURE_DONE, | |
93 content::NotificationService::AllSources()); | |
94 registrar_.Add(this, chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED, | |
95 content::NotificationService::AllSources()); | |
96 } | |
97 | |
98 PromoHandler::~PromoHandler() { | |
99 } | |
100 | |
101 void PromoHandler::RegisterMessages() { | |
102 web_ui()->RegisterMessageCallback("getPromotions", | |
103 base::Bind(&PromoHandler::InjectPromoDecorations, | |
104 base::Unretained(this))); | |
105 web_ui()->RegisterMessageCallback("recordImpression", | |
106 base::Bind(&PromoHandler::RecordImpression, | |
107 base::Unretained(this))); | |
108 web_ui()->RegisterMessageCallback("promoActionTriggered", | |
109 base::Bind(&PromoHandler::HandlePromoActionTriggered, | |
110 base::Unretained(this))); | |
111 web_ui()->RegisterMessageCallback("promoDisabled", | |
112 base::Bind(&PromoHandler::HandlePromoDisabled, | |
113 base::Unretained(this))); | |
114 } | |
115 | |
116 // static | |
117 void PromoHandler::RegisterUserPrefs(PrefService* prefs) { | |
118 prefs->RegisterBooleanPref(prefs::kNtpPromoDesktopSessionFound, | |
119 false, | |
120 PrefService::UNSYNCABLE_PREF); | |
121 } | |
122 | |
123 void PromoHandler::Observe(int type, | |
124 const content::NotificationSource& source, | |
125 const content::NotificationDetails& details) { | |
126 if (chrome::NOTIFICATION_PROMO_RESOURCE_STATE_CHANGED == type || | |
127 chrome::NOTIFICATION_SYNC_CONFIGURE_DONE == type || | |
128 chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED == type) { | |
129 // A change occurred to one of the preferences we care about | |
130 CheckDesktopSessions(); | |
131 InjectPromoDecorations(NULL); | |
132 } else { | |
133 NOTREACHED() << "Unknown pref changed."; | |
134 } | |
135 } | |
136 | |
137 void PromoHandler::HandlePromoSendEmail(const base::ListValue* args) { | |
138 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
139 Profile* profile = Profile::FromBrowserContext( | |
140 web_ui()->GetWebContents()->GetBrowserContext()); | |
141 if (!profile) | |
142 return; | |
143 | |
144 string16 data_subject, data_body, data_inv; | |
145 if (!args || args->GetSize() < 3) { | |
146 DVLOG(1) << "promoSendEmail: expected three args, got " | |
147 << (args ? args->GetSize() : 0); | |
148 return; | |
149 } | |
150 | |
151 args->GetString(0, &data_subject); | |
152 args->GetString(1, &data_body); | |
153 args->GetString(2, &data_inv); | |
154 if (data_inv.empty() || (data_subject.empty() && data_body.empty())) | |
155 return; | |
156 | |
157 std::string data_email; | |
158 ProfileSyncService* service = | |
159 ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile); | |
160 if (service && service->signin()) | |
161 data_email = service->signin()->GetAuthenticatedUsername(); | |
162 | |
163 chrome::android::SendEmail( | |
164 UTF8ToUTF16(data_email), data_subject, data_body, data_inv); | |
165 RecordPromotionImpression("send_email"); | |
166 } | |
167 | |
168 void PromoHandler::HandlePromoActionTriggered( | |
169 const base::ListValue* /*args*/) { | |
170 Profile* profile = Profile::FromWebUI(web_ui()); | |
171 if (!profile || !CanShowNotificationPromo(profile)) | |
172 return; | |
173 | |
174 NotificationPromoMobileNtp promo(profile); | |
175 if (!promo.InitFromPrefs()) | |
176 return; | |
177 | |
178 if (promo.action_type() == "ACTION_EMAIL") | |
179 HandlePromoSendEmail(promo.action_args()); | |
180 } | |
181 | |
182 void PromoHandler::HandlePromoDisabled( | |
183 const base::ListValue* /*args*/) { | |
184 Profile* profile = Profile::FromWebUI(web_ui()); | |
185 if (!profile || !CanShowNotificationPromo(profile)) | |
186 return; | |
187 | |
188 NotificationPromo::HandleClosed( | |
189 profile, NotificationPromo::MOBILE_NTP_SYNC_PROMO); | |
190 RecordPromotionImpression("disable_promo"); | |
191 | |
192 content::NotificationService* service = | |
193 content::NotificationService::current(); | |
194 service->Notify(chrome::NOTIFICATION_PROMO_RESOURCE_STATE_CHANGED, | |
195 content::Source<PromoHandler>(this), | |
196 content::NotificationService::NoDetails()); | |
197 } | |
198 | |
199 void PromoHandler::InjectPromoDecorations( | |
200 const base::ListValue* /*args*/) { | |
201 DictionaryValue result; | |
202 if (FetchPromotion(&result)) | |
203 web_ui()->CallJavascriptFunction("ntp.setPromotions", result); | |
204 else | |
205 web_ui()->CallJavascriptFunction("ntp.clearPromotions"); | |
206 } | |
207 | |
208 void PromoHandler::RecordImpression(const base::ListValue* args) { | |
209 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
210 DCHECK(args && !args->empty()); | |
211 RecordPromotionImpression(UTF16ToASCII(ExtractStringValue(args))); | |
212 } | |
213 | |
214 void PromoHandler::RecordPromotionImpression(const std::string& id) { | |
215 // Update number of views a promotion has received and trigger refresh | |
216 // if it exceeded max_views set for the promotion. | |
217 Profile* profile = Profile::FromWebUI(web_ui()); | |
218 if (profile && | |
219 NotificationPromo::HandleViewed(profile, | |
220 NotificationPromo::MOBILE_NTP_SYNC_PROMO)) { | |
221 Notify(this, chrome::NOTIFICATION_PROMO_RESOURCE_STATE_CHANGED); | |
222 } | |
223 | |
224 if (!id.compare("most_visited")) { | |
Evan Stade
2012/08/28 17:25:55
use ==
aruslan
2012/08/28 19:16:49
Done.
| |
225 content::RecordAction( | |
226 content::UserMetricsAction("MobilePromoShownOnMostVisited")); | |
Evan Stade
2012/08/28 17:25:55
please factor out the content::RecordAction(conten
aruslan
2012/08/28 19:16:49
This copy-pasted mess is believed to be required b
Evan Stade
2012/08/28 20:29:53
ok, no problem. Thanks for the explanation.
| |
227 } else if (!id.compare("open_tabs")) { | |
228 content::RecordAction( | |
229 content::UserMetricsAction("MobilePromoShownOnOpenTabs")); | |
230 } else if (!id.compare("sync_promo")) { | |
231 content::RecordAction( | |
232 content::UserMetricsAction("MobilePromoShownOnSyncPromo")); | |
233 } else if (!id.compare("send_email")) { | |
234 content::RecordAction( | |
235 content::UserMetricsAction("MobilePromoEmailLinkPressed")); | |
236 } else if (!id.compare("disable_promo")) { | |
237 content::RecordAction( | |
238 content::UserMetricsAction("MobilePromoClosePressedOnOpenTabs")); | |
239 } else { | |
240 NOTREACHED() << "Unknown promotion impression: " << id; | |
241 } | |
242 } | |
243 | |
244 bool PromoHandler::FetchPromotion(DictionaryValue* result) { | |
245 DCHECK(result != NULL); | |
246 Profile* profile = Profile::FromWebUI(web_ui()); | |
247 if (!profile || !CanShowNotificationPromo(profile)) | |
248 return false; | |
249 | |
250 NotificationPromoMobileNtp promo(profile); | |
251 if (!promo.InitFromPrefs()) | |
252 return false; | |
253 if (!DoesChromePromoMatchCurrentSync( | |
254 promo.requires_sync(), promo.requires_mobile_only_sync())) { | |
255 return false; | |
256 } | |
257 | |
258 string16 promo_line; | |
259 string16 promo_line_long; | |
260 const std::string utf8 = promo.text(); | |
Evan Stade
2012/08/28 17:25:55
I don't know why you are bothering with the tempor
aruslan
2012/08/28 19:16:49
Done.
| |
261 const std::string utf8long = promo.text_long(); | |
262 if (utf8.empty() || | |
263 !UTF8ToUTF16(utf8.c_str(), utf8.length(), &promo_line) || | |
264 !UTF8ToUTF16(utf8long.c_str(), utf8long.length(), &promo_line_long)) { | |
Evan Stade
2012/08/28 17:25:55
actually, why are you even converting to utf16? wh
aruslan
2012/08/28 19:16:49
Done.
| |
265 return false; | |
Evan Stade
2012/08/28 17:25:55
probably a suitable place for an error log.
aruslan
2012/08/28 19:16:49
Done.
| |
266 } | |
267 | |
268 promo_line = ReplaceSimpleMarkupWithHtml(promo_line); | |
269 promo_line_long = ReplaceSimpleMarkupWithHtml(promo_line_long); | |
270 | |
271 result->SetBoolean("promoIsAllowed", true); | |
272 result->SetBoolean("promoIsAllowedOnMostVisited", | |
273 promo.show_on_most_visited()); | |
Evan Stade
2012/08/28 17:25:55
indent to align with previous arg.
aruslan
2012/08/28 19:16:49
Done.
| |
274 result->SetBoolean("promoIsAllowedOnOpenTabs", promo.show_on_open_tabs()); | |
275 result->SetBoolean("promoIsAllowedAsVC", promo.show_as_virtual_computer()); | |
276 result->SetString("promoVCTitle", promo.virtual_computer_title()); | |
277 result->SetString("promoVCLastSynced", promo.virtual_computer_lastsync()); | |
278 result->SetString("promoMessage", promo_line); | |
279 result->SetString("promoMessageLong", promo_line_long); | |
280 return true; | |
281 } | |
282 | |
283 bool PromoHandler::DoesChromePromoMatchCurrentSync( | |
284 bool promo_requires_sync, | |
285 bool promo_requires_no_active_desktop_sync_sessions) { | |
286 Profile* profile = Profile::FromWebUI(web_ui()); | |
287 if (!profile) | |
288 return false; | |
289 | |
290 // If the promo doesn't require any sync, the requirements are fulfilled. | |
291 if (!promo_requires_sync) | |
292 return true; | |
293 | |
294 // The promo requires the sync; check that the sync service is active. | |
295 ProfileSyncService* service = | |
296 ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile); | |
297 if (!service || !service->ShouldPushChanges()) | |
298 return false; | |
299 | |
300 // If the promo doesn't have specific requirements for the sync, it matches. | |
301 if (!promo_requires_no_active_desktop_sync_sessions) | |
302 return true; | |
303 | |
304 // If the promo requires mobile-only sync, | |
305 // check that no desktop sessions are found. | |
306 PrefService* prefs = profile->GetPrefs(); | |
307 return !prefs || | |
308 !prefs->GetBoolean(prefs::kNtpPromoDesktopSessionFound); | |
309 } | |
310 | |
311 void PromoHandler::CheckDesktopSessions() { | |
312 Profile* profile = Profile::FromWebUI(web_ui()); | |
313 if (!profile) | |
314 return; | |
315 | |
316 // Check if desktop sessions have already been found. | |
317 PrefService* prefs = profile->GetPrefs(); | |
318 if (!prefs || prefs->GetBoolean(prefs::kNtpPromoDesktopSessionFound)) | |
319 return; | |
320 | |
321 // Check if the sync is currently active. | |
322 ProfileSyncService* service = | |
323 ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile); | |
324 if (!service || !service->ShouldPushChanges()) | |
325 return; | |
326 | |
327 // Check if the sync has any open sessions. | |
328 browser_sync::SessionModelAssociator* associator = | |
329 service->GetSessionModelAssociator(); | |
330 if (!associator) | |
331 return; | |
332 | |
333 // Let's see if there are no desktop sessions. | |
334 std::vector<const browser_sync::SyncedSession*> sessions; | |
335 ListValue session_list; | |
336 if (!associator->GetAllForeignSessions(&sessions)) | |
337 return; | |
338 | |
339 for (size_t i = 0; i < sessions.size(); ++i) { | |
340 const browser_sync::SyncedSession::DeviceType device_type = | |
341 sessions[i]->device_type; | |
342 if (device_type == browser_sync::SyncedSession::TYPE_WIN || | |
343 device_type == browser_sync::SyncedSession::TYPE_MACOSX || | |
344 device_type == browser_sync::SyncedSession::TYPE_LINUX) { | |
345 // Found a desktop session: write out the pref. | |
346 prefs->SetBoolean(prefs::kNtpPromoDesktopSessionFound, true); | |
347 return; | |
348 } | |
349 } | |
350 } | |
OLD | NEW |