Index: chrome/browser/ui/webui/ntp/android/promo_handler.cc |
diff --git a/chrome/browser/ui/webui/ntp/android/promo_handler.cc b/chrome/browser/ui/webui/ntp/android/promo_handler.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..fbafc450848c005c97d70b84d2af08520d05ea5a |
--- /dev/null |
+++ b/chrome/browser/ui/webui/ntp/android/promo_handler.cc |
@@ -0,0 +1,344 @@ |
+// 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/ui/webui/ntp/android/promo_handler.h" |
+ |
+#include "base/logging.h" |
+#include "base/memory/ref_counted_memory.h" |
+#include "base/string_number_conversions.h" |
+#include "base/string_util.h" |
+#include "chrome/browser/android/intent_helper.h" |
+#include "chrome/browser/prefs/pref_service.h" |
+#include "chrome/browser/profiles/profile.h" |
+#include "chrome/browser/profiles/profile_manager.h" |
+#include "chrome/browser/signin/signin_manager.h" |
+#include "chrome/browser/sync/glue/session_model_associator.h" |
+#include "chrome/browser/sync/glue/synced_session.h" |
+#include "chrome/browser/sync/profile_sync_service.h" |
+#include "chrome/browser/sync/profile_sync_service_factory.h" |
+#include "chrome/browser/web_resource/notification_promo.h" |
+#include "chrome/browser/web_resource/notification_promo_mobile_ntp.h" |
+#include "chrome/browser/web_resource/promo_resource_service.h" |
+#include "chrome/common/chrome_notification_types.h" |
+#include "chrome/common/pref_names.h" |
+#include "content/public/browser/browser_thread.h" |
+#include "content/public/browser/notification_service.h" |
+#include "content/public/browser/user_metrics.h" |
+#include "content/public/browser/web_contents.h" |
+ |
+using content::BrowserThread; |
+ |
+namespace { |
Dan Beam
2012/08/25 00:48:20
nit: \n
aruslan
2012/08/27 22:19:51
Done.
|
+// Helper to ask whether the promo is active. |
+bool CanShowNotificationPromo(Profile* profile) { |
+ NotificationPromo notification_promo(profile); |
+ notification_promo.InitFromPrefs(NotificationPromo::MOBILE_NTP_SYNC_PROMO); |
+ return notification_promo.CanShow(); |
+} |
+ |
+// Helper to send out promo resource change notification. |
+void Notify(PromoHandler* ph, chrome::NotificationType notification_type) { |
+ content::NotificationService* service = |
+ content::NotificationService::current(); |
+ service->Notify(notification_type, |
+ content::Source<PromoHandler>(ph), |
+ content::NotificationService::NoDetails()); |
+} |
+ |
+// Replaces all formatting markup in the promo with the corresponding HTML. |
+string16 ReplaceSimpleMarkupWithHTML(string16 text) { |
+ const string16 LINE_BREAK = ASCIIToUTF16("<br/>"); |
+ const string16 SYNCGRAPHIC_IMAGE = |
+ ASCIIToUTF16("<div class=\"promo-sync-graphic\"></div>"); |
+ const string16 BEGIN_HIGHLIGHT = |
+ ASCIIToUTF16( |
+ "<div style=\"text-align: center\"><button class=\"promo-button\">"); |
+ const string16 END_HIGHLIGHT = |
+ ASCIIToUTF16("</button></div>"); |
+ const string16 BEGIN_LINK = |
+ ASCIIToUTF16("<span style=\"color: blue; text-decoration: underline;\">"); |
+ const string16 END_LINK = |
+ ASCIIToUTF16("</span>"); |
+ const string16 BEGIN_PROMO_AREA = |
+ ASCIIToUTF16("<div class=\"promo-action-target\">"); |
+ const string16 END_PROMO_AREA = |
+ ASCIIToUTF16("</div>"); |
+ |
+ ReplaceSubstringsAfterOffset( |
+ &text, 0, ASCIIToUTF16("LINE_BREAK"), LINE_BREAK); |
+ ReplaceSubstringsAfterOffset( |
+ &text, 0, ASCIIToUTF16("SYNCGRAPHIC_IMAGE"), SYNCGRAPHIC_IMAGE); |
+ ReplaceSubstringsAfterOffset( |
+ &text, 0, ASCIIToUTF16("BEGIN_HIGHLIGHT"), BEGIN_HIGHLIGHT); |
+ ReplaceSubstringsAfterOffset( |
+ &text, 0, ASCIIToUTF16("END_HIGHLIGHT"), END_HIGHLIGHT); |
+ ReplaceSubstringsAfterOffset( |
+ &text, 0, ASCIIToUTF16("BEGIN_LINK"), BEGIN_LINK); |
+ ReplaceSubstringsAfterOffset( |
+ &text, 0, ASCIIToUTF16("END_LINK"), END_LINK); |
+ return BEGIN_PROMO_AREA + text + END_PROMO_AREA; |
+} |
Dan Beam
2012/08/25 00:48:20
nit: \n
aruslan
2012/08/27 22:19:51
Done.
|
+} // namespace |
+ |
+PromoHandler::PromoHandler() { |
+} |
+ |
+PromoHandler::~PromoHandler() { |
+} |
+ |
+void PromoHandler::RegisterMessages() { |
+ web_ui()->RegisterMessageCallback("getPromotions", |
+ base::Bind(&PromoHandler::InjectPromoDecorations, |
+ base::Unretained(this))); |
+ web_ui()->RegisterMessageCallback("recordImpression", |
+ base::Bind(&PromoHandler::RecordImpression, |
+ base::Unretained(this))); |
+ web_ui()->RegisterMessageCallback("promoActionTriggered", |
+ base::Bind(&PromoHandler::HandlePromoActionTriggered, |
+ base::Unretained(this))); |
+ web_ui()->RegisterMessageCallback("promoDisabled", |
+ base::Bind(&PromoHandler::HandlePromoDisabled, |
+ base::Unretained(this))); |
+ |
+ Profile* profile = Profile::FromBrowserContext( |
+ web_ui()->GetWebContents()->GetBrowserContext()); |
+ if (profile) { |
Dan Beam
2012/08/25 00:48:20
if this is the norm, might as well change to DCHEC
aruslan
2012/08/27 22:19:51
Good point; the profile is not even necessary here
|
+ // Watch for pref changes that cause us to need to re-inject promolines. |
Dan Beam
2012/08/25 00:48:20
why do you need to do this in registermessages rat
aruslan
2012/08/27 22:19:51
Done.
|
+ registrar_.Add(this, chrome::NOTIFICATION_PROMO_RESOURCE_STATE_CHANGED, |
+ content::NotificationService::AllSources()); |
+ // Watch for sync service updates that could cause re-injections |
+ registrar_.Add(this, chrome::NOTIFICATION_SYNC_CONFIGURE_DONE, |
+ content::NotificationService::AllSources()); |
+ registrar_.Add(this, chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED, |
+ content::NotificationService::AllSources()); |
+ } |
+} |
+ |
+// static |
+void PromoHandler::RegisterUserPrefs(PrefService* prefs) { |
+ prefs->RegisterBooleanPref(prefs::kNtpPromoDesktopSessionFound, |
+ false, |
+ PrefService::UNSYNCABLE_PREF); |
+} |
+ |
+void PromoHandler::Observe(int type, |
+ const content::NotificationSource& source, |
+ const content::NotificationDetails& details) { |
+ if (chrome::NOTIFICATION_PROMO_RESOURCE_STATE_CHANGED == type || |
+ chrome::NOTIFICATION_SYNC_CONFIGURE_DONE == type || |
+ chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED == type) { |
+ // A change occurred to one of the preferences we care about |
+ CheckDesktopSessions(); |
+ InjectPromoDecorations(NULL); |
+ } |
Dan Beam
2012/08/25 00:48:20
} else {
NOTREACHED() << "Unknown pref changed."
aruslan
2012/08/27 22:19:51
Done.
|
+} |
+ |
+void PromoHandler::HandlePromoSendEmail(const base::ListValue* args) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ Profile* profile = Profile::FromBrowserContext( |
+ web_ui()->GetWebContents()->GetBrowserContext()); |
+ if (!profile) |
+ return; |
+ |
+ string16 data_subject, data_body, data_inv; |
+ if (!args || args->GetSize() < 3) { |
+ LOG(ERROR) << "promoSendEmail: expected three args, got " |
Dan Beam
2012/08/25 00:48:20
probably DLOG or DVLOG(##) here, eh?
aruslan
2012/08/27 22:19:51
Done.
|
+ << (args ? args->GetSize() : 0); |
+ return; |
+ } |
+ args->GetString(0, &data_subject); |
+ args->GetString(1, &data_body); |
+ args->GetString(2, &data_inv); |
+ if (data_inv.empty() || (data_subject.empty() && data_body.empty())) |
+ return; |
+ |
+ std::string data_email; |
+ ProfileSyncService* service = |
+ ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile); |
+ if (service && service->signin()) |
+ data_email = service->signin()->GetAuthenticatedUsername(); |
+ |
+ chrome::android::SendEmail( |
Dan Beam
2012/08/25 00:48:20
cool!
aruslan
2012/08/27 22:19:51
:)
|
+ UTF8ToUTF16(data_email), data_subject, data_body, data_inv); |
+ RecordPromotionImpression("send_email"); |
+} |
+ |
+void PromoHandler::HandlePromoActionTriggered( |
+ const base::ListValue* /*args*/) { |
+ Profile* profile = Profile::FromWebUI(web_ui()); |
+ if (!profile || !CanShowNotificationPromo(profile)) |
+ return; |
+ |
+ NotificationPromoMobileNtp promo(profile); |
+ if (!promo.InitFromPrefs()) |
+ return; |
+ |
+ if (promo.action_type() == "ACTION_EMAIL") |
+ HandlePromoSendEmail(promo.action_args()); |
+} |
+ |
+void PromoHandler::HandlePromoDisabled( |
+ const base::ListValue* /*args*/) { |
+ Profile* profile = Profile::FromWebUI(web_ui()); |
+ if (!profile || !CanShowNotificationPromo(profile)) |
+ return; |
+ |
+ NotificationPromo::HandleClosed( |
+ profile, NotificationPromo::MOBILE_NTP_SYNC_PROMO); |
+ RecordPromotionImpression("disable_promo"); |
+ |
+ content::NotificationService* service = |
+ content::NotificationService::current(); |
+ service->Notify(chrome::NOTIFICATION_PROMO_RESOURCE_STATE_CHANGED, |
+ content::Source<PromoHandler>(this), |
+ content::NotificationService::NoDetails()); |
+} |
+ |
+void PromoHandler::InjectPromoDecorations( |
+ const base::ListValue* /*args*/) { |
+ DictionaryValue result; |
+ if (FetchPromotion(&result)) |
+ web_ui()->CallJavascriptFunction("ntp.setPromotions", result); |
+ else |
+ web_ui()->CallJavascriptFunction("ntp.clearPromotions"); |
+} |
+ |
+void PromoHandler::RecordImpression(const base::ListValue* args) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ if (args && !args->empty()) |
Dan Beam
2012/08/25 00:48:20
if you expect something be passed to this, perhaps
aruslan
2012/08/27 22:19:51
Done.
|
+ RecordPromotionImpression(UTF16ToASCII(ExtractStringValue(args))); |
+} |
+ |
+void PromoHandler::RecordPromotionImpression(const std::string& id) { |
+ // Update number of views a promotion has received and trigger refresh |
+ // if it exceeded max_views set for the promotion. |
+ Profile* profile = Profile::FromWebUI(web_ui()); |
+ if (profile && |
+ NotificationPromo::HandleViewed(profile, |
+ NotificationPromo::MOBILE_NTP_SYNC_PROMO)) |
Dan Beam
2012/08/25 00:48:20
nit: curlies
aruslan
2012/08/27 22:19:51
Done.
|
+ Notify(this, chrome::NOTIFICATION_PROMO_RESOURCE_STATE_CHANGED); |
+ |
+ if (!id.compare("most_visited")) |
Dan Beam
2012/08/25 00:48:20
nit: curlies on every level
aruslan
2012/08/27 22:19:51
Done.
|
+ content::RecordAction( |
+ content::UserMetricsAction("MobilePromoShownOnMostVisited")); |
+ else if (!id.compare("open_tabs")) |
+ content::RecordAction( |
+ content::UserMetricsAction("MobilePromoShownOnOpenTabs")); |
+ else if (!id.compare("sync_promo")) |
+ content::RecordAction( |
+ content::UserMetricsAction("MobilePromoShownOnSyncPromo")); |
+ else if (!id.compare("send_email")) |
+ content::RecordAction( |
+ content::UserMetricsAction("MobilePromoEmailLinkPressed")); |
+ else if (!id.compare("disable_promo")) |
+ content::RecordAction( |
+ content::UserMetricsAction("MobilePromoClosePressedOnOpenTabs")); |
+ else |
+ NOTREACHED() << "Unknown promotion impression: " << id; |
+} |
+ |
+bool PromoHandler::FetchPromotion(DictionaryValue* result) { |
+ DCHECK(result != NULL); |
+ Profile* profile = Profile::FromWebUI(web_ui()); |
+ if (!profile || !CanShowNotificationPromo(profile)) |
Dan Beam
2012/08/25 00:48:20
same general gist on the if () -> DCHECK() if appl
aruslan
2012/08/27 22:19:51
I'll find out more about circumstances except the
|
+ return false; |
+ |
+ NotificationPromoMobileNtp promo(profile); |
+ if (!promo.InitFromPrefs()) |
+ return false; |
+ if (!DoesChromePromoMatchCurrentSync( |
+ promo.requires_sync(), promo.requires_mobile_only_sync())) |
+ return false; |
+ |
+ string16 promo_line; |
+ string16 promo_line_long; |
+ const std::string utf8 = promo.text(); |
+ const std::string utf8long = promo.text_long(); |
+ if (utf8.empty() || |
+ !UTF8ToUTF16(utf8.c_str(), utf8.length(), &promo_line) || |
+ !UTF8ToUTF16(utf8long.c_str(), utf8long.length(), &promo_line_long)) |
Dan Beam
2012/08/25 00:48:20
nit: curlies
aruslan
2012/08/27 22:19:51
Done.
|
+ return false; |
+ |
+ promo_line = ReplaceSimpleMarkupWithHTML(promo_line); |
Dan Beam
2012/08/25 00:48:20
WithHtml would probably be the preferred capitaliz
aruslan
2012/08/27 22:19:51
Done.
|
+ promo_line_long = ReplaceSimpleMarkupWithHTML(promo_line_long); |
+ |
+ result->SetBoolean("promoIsAllowed", true); |
+ result->SetBoolean("promoIsAllowedOnMostVisited", |
+ promo.show_on_most_visited()); |
+ result->SetBoolean("promoIsAllowedOnOpenTabs", promo.show_on_open_tabs()); |
+ result->SetBoolean("promoIsAllowedAsVC", promo.show_as_virtual_computer()); |
+ result->SetString("promoVCTitle", promo.virtual_computer_title()); |
+ result->SetString("promoVCLastSynced", promo.virtual_computer_lastsync()); |
+ result->SetString("promoMessage", promo_line); |
+ result->SetString("promoMessageLong", promo_line_long); |
+ return true; |
+} |
+ |
+bool PromoHandler::DoesChromePromoMatchCurrentSync( |
+ bool promo_requires_sync, |
+ bool promo_requires_no_active_desktop_sync_sessions) { |
+ Profile* profile = Profile::FromWebUI(web_ui()); |
+ if (!profile) |
+ return false; |
+ |
+ // If the promo doesn't require any sync, we are good to go. |
Dan Beam
2012/08/25 00:48:20
nit: some prefer not to use "we", "you", or "us" i
aruslan
2012/08/27 22:19:51
Done.
|
+ if (!promo_requires_sync) |
+ return true; |
Dan Beam
2012/08/25 00:48:20
nit: \n after every if that might return from the
aruslan
2012/08/27 22:19:51
Done.
|
+ ProfileSyncService* service = |
+ ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile); |
+ if (!service || !service->ShouldPushChanges()) |
+ return false; |
+ |
+ // If the promo doesn't have specific requirements for the sync, we are good. |
+ if (!promo_requires_no_active_desktop_sync_sessions) |
+ return true; |
+ PrefService* prefs = profile->GetPrefs(); |
+ if (!prefs) |
+ return true; |
+ if (prefs->GetBoolean(prefs::kNtpPromoDesktopSessionFound)) |
+ return false; |
+ |
+ return true; |
+} |
+ |
+void PromoHandler::CheckDesktopSessions() { |
+ Profile* profile = Profile::FromWebUI(web_ui()); |
+ if (!profile) |
+ return; |
+ |
+ PrefService* prefs = profile->GetPrefs(); |
+ if (!prefs) |
+ return; |
+ if (prefs->GetBoolean(prefs::kNtpPromoDesktopSessionFound)) |
+ return; // already got a desktop session. |
Dan Beam
2012/08/25 00:48:20
nit: Already
aruslan
2012/08/27 22:19:51
Done.
|
+ |
+ ProfileSyncService* service = |
+ ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile); |
+ if (!service || !service->ShouldPushChanges()) |
+ return; |
+ |
+ browser_sync::SessionModelAssociator* associator = |
+ service->GetSessionModelAssociator(); |
+ // No sessions means no desktop sessions as well, so we are good to go. |
+ if (!associator) |
+ return; |
+ |
+ // Let's see if there are no desktop sessions. |
+ std::vector<const browser_sync::SyncedSession*> sessions; |
+ ListValue session_list; |
+ if (!associator->GetAllForeignSessions(&sessions)) |
+ return; |
+ |
+ for (size_t i = 0; i < sessions.size(); ++i) { |
+ const browser_sync::SyncedSession::DeviceType device_type = |
+ sessions[i]->device_type; |
+ if (device_type == browser_sync::SyncedSession::TYPE_WIN || |
+ device_type == browser_sync::SyncedSession::TYPE_MACOSX || |
+ device_type == browser_sync::SyncedSession::TYPE_LINUX) { |
+ // Found a desktop session: write out the pref. |
+ prefs->SetBoolean(prefs::kNtpPromoDesktopSessionFound, true); |
+ return; |
+ } |
+ } |
+} |