Roll src/third_party/WebKit eac3800:0237a66 (svn 202606:202607)
[chromium-blink-merge.git] / components / web_resource / notification_promo.cc
blob73e7d4b98a4866da35bd1c2ac7a2f9dbcc76a38d
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.
5 #include "components/web_resource/notification_promo.h"
7 #include <cmath>
8 #include <vector>
10 #include "base/bind.h"
11 #include "base/prefs/pref_registry_simple.h"
12 #include "base/prefs/pref_service.h"
13 #include "base/rand_util.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_util.h"
16 #include "base/sys_info.h"
17 #include "base/threading/thread_restrictions.h"
18 #include "base/time/time.h"
19 #include "base/values.h"
20 #include "components/pref_registry/pref_registry_syncable.h"
21 #include "components/version_info/version_info.h"
22 #include "components/web_resource/promo_resource_service.h"
23 #include "net/base/url_util.h"
24 #include "ui/base/device_form_factor.h"
25 #include "url/gurl.h"
27 namespace web_resource {
29 namespace {
31 const int kDefaultGroupSize = 100;
33 const char promo_server_url[] = "https://clients3.google.com/crsignal/client";
35 // The name of the preference that stores the promotion object.
36 const char kPrefPromoObject[] = "promo";
38 // Keys in the kPrefPromoObject dictionary; used only here.
39 const char kPrefPromoText[] = "text";
40 const char kPrefPromoPayload[] = "payload";
41 const char kPrefPromoStart[] = "start";
42 const char kPrefPromoEnd[] = "end";
43 const char kPrefPromoNumGroups[] = "num_groups";
44 const char kPrefPromoSegment[] = "segment";
45 const char kPrefPromoIncrement[] = "increment";
46 const char kPrefPromoIncrementFrequency[] = "increment_frequency";
47 const char kPrefPromoIncrementMax[] = "increment_max";
48 const char kPrefPromoMaxViews[] = "max_views";
49 const char kPrefPromoMaxSeconds[] = "max_seconds";
50 const char kPrefPromoFirstViewTime[] = "first_view_time";
51 const char kPrefPromoGroup[] = "group";
52 const char kPrefPromoViews[] = "views";
53 const char kPrefPromoClosed[] = "closed";
55 // Returns a string suitable for the Promo Server URL 'osname' value.
56 std::string PlatformString() {
57 #if defined(OS_WIN)
58 return "win";
59 #elif defined(OS_ANDROID)
60 ui::DeviceFormFactor form_factor = ui::GetDeviceFormFactor();
61 return std::string("android-") +
62 (form_factor == ui::DEVICE_FORM_FACTOR_TABLET ? "tablet" : "phone");
63 #elif defined(OS_IOS)
64 ui::DeviceFormFactor form_factor = ui::GetDeviceFormFactor();
65 return std::string("ios-") +
66 (form_factor == ui::DEVICE_FORM_FACTOR_TABLET ? "tablet" : "phone");
67 #elif defined(OS_MACOSX)
68 return "mac";
69 #elif defined(OS_CHROMEOS)
70 return "chromeos";
71 #elif defined(OS_LINUX)
72 return "linux";
73 #else
74 return "none";
75 #endif
78 // Returns a string suitable for the Promo Server URL 'dist' value.
79 const char* ChannelString(version_info::Channel channel) {
80 #if defined(OS_WIN)
81 // GetChannel hits the registry on Windows. See http://crbug.com/70898.
82 // TODO(achuith): Move NotificationPromo::PromoServerURL to the blocking pool.
83 base::ThreadRestrictions::ScopedAllowIO allow_io;
84 #endif
85 switch (channel) {
86 case version_info::Channel::CANARY:
87 return "canary";
88 case version_info::Channel::DEV:
89 return "dev";
90 case version_info::Channel::BETA:
91 return "beta";
92 case version_info::Channel::STABLE:
93 return "stable";
94 default:
95 return "none";
99 struct PromoMapEntry {
100 NotificationPromo::PromoType promo_type;
101 const char* promo_type_str;
104 const PromoMapEntry kPromoMap[] = {
105 {NotificationPromo::NO_PROMO, ""},
106 {NotificationPromo::NTP_NOTIFICATION_PROMO, "ntp_notification_promo"},
107 {NotificationPromo::NTP_BUBBLE_PROMO, "ntp_bubble_promo"},
108 {NotificationPromo::MOBILE_NTP_SYNC_PROMO, "mobile_ntp_sync_promo"},
109 {NotificationPromo::MOBILE_NTP_WHATS_NEW_PROMO,
110 "mobile_ntp_whats_new_promo"},
113 // Convert PromoType to appropriate string.
114 const char* PromoTypeToString(NotificationPromo::PromoType promo_type) {
115 for (size_t i = 0; i < arraysize(kPromoMap); ++i) {
116 if (kPromoMap[i].promo_type == promo_type)
117 return kPromoMap[i].promo_type_str;
119 NOTREACHED();
120 return "";
123 // Deep-copies a node, replacing any "value" that is a key
124 // into "strings" dictionary with its value from "strings".
125 // E.g. for
126 // {promo_action_args:['MSG_SHORT']} + strings:{MSG_SHORT:'yes'}
127 // it will return
128 // {promo_action_args:['yes']}
129 // |node| - a value to be deep copied and resolved.
130 // |strings| - a dictionary of strings to be used for resolution.
131 // Returns a _new_ object that is a deep copy with replacements.
132 // TODO(aruslan): http://crbug.com/144320 Consider moving it to values.cc/h.
133 base::Value* DeepCopyAndResolveStrings(const base::Value* node,
134 const base::DictionaryValue* strings) {
135 switch (node->GetType()) {
136 case base::Value::TYPE_LIST: {
137 const base::ListValue* list = static_cast<const base::ListValue*>(node);
138 base::ListValue* copy = new base::ListValue;
139 for (base::ListValue::const_iterator it = list->begin();
140 it != list->end(); ++it) {
141 base::Value* child_copy = DeepCopyAndResolveStrings(*it, strings);
142 copy->Append(child_copy);
144 return copy;
147 case base::Value::TYPE_DICTIONARY: {
148 const base::DictionaryValue* dict =
149 static_cast<const base::DictionaryValue*>(node);
150 base::DictionaryValue* copy = new base::DictionaryValue;
151 for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd();
152 it.Advance()) {
153 base::Value* child_copy =
154 DeepCopyAndResolveStrings(&it.value(), strings);
155 copy->SetWithoutPathExpansion(it.key(), child_copy);
157 return copy;
160 case base::Value::TYPE_STRING: {
161 std::string value;
162 bool rv = node->GetAsString(&value);
163 DCHECK(rv);
164 std::string actual_value;
165 if (!strings || !strings->GetString(value, &actual_value))
166 actual_value = value;
167 return new base::StringValue(actual_value);
170 default:
171 // For everything else, just make a copy.
172 return node->DeepCopy();
176 void AppendQueryParameter(GURL* url,
177 const std::string& param,
178 const std::string& value) {
179 *url = net::AppendQueryParameter(*url, param, value);
182 } // namespace
184 NotificationPromo::NotificationPromo(PrefService* local_state)
185 : local_state_(local_state),
186 promo_type_(NO_PROMO),
187 promo_payload_(new base::DictionaryValue()),
188 start_(0.0),
189 end_(0.0),
190 num_groups_(kDefaultGroupSize),
191 initial_segment_(0),
192 increment_(1),
193 time_slice_(0),
194 max_group_(0),
195 max_views_(0),
196 max_seconds_(0),
197 first_view_time_(0),
198 group_(0),
199 views_(0),
200 closed_(false),
201 new_notification_(false) {
202 DCHECK(local_state_);
205 NotificationPromo::~NotificationPromo() {}
207 void NotificationPromo::InitFromJson(const base::DictionaryValue& json,
208 PromoType promo_type) {
209 promo_type_ = promo_type;
210 const base::ListValue* promo_list = NULL;
211 DVLOG(1) << "InitFromJson " << PromoTypeToString(promo_type_);
212 if (!json.GetList(PromoTypeToString(promo_type_), &promo_list))
213 return;
215 // No support for multiple promos yet. Only consider the first one.
216 const base::DictionaryValue* promo = NULL;
217 if (!promo_list->GetDictionary(0, &promo))
218 return;
220 // Date.
221 const base::ListValue* date_list = NULL;
222 if (promo->GetList("date", &date_list)) {
223 const base::DictionaryValue* date;
224 if (date_list->GetDictionary(0, &date)) {
225 std::string time_str;
226 base::Time time;
227 if (date->GetString("start", &time_str) &&
228 base::Time::FromString(time_str.c_str(), &time)) {
229 start_ = time.ToDoubleT();
230 DVLOG(1) << "start str=" << time_str
231 << ", start_=" << base::DoubleToString(start_);
233 if (date->GetString("end", &time_str) &&
234 base::Time::FromString(time_str.c_str(), &time)) {
235 end_ = time.ToDoubleT();
236 DVLOG(1) << "end str =" << time_str
237 << ", end_=" << base::DoubleToString(end_);
242 // Grouping.
243 const base::DictionaryValue* grouping = NULL;
244 if (promo->GetDictionary("grouping", &grouping)) {
245 grouping->GetInteger("buckets", &num_groups_);
246 grouping->GetInteger("segment", &initial_segment_);
247 grouping->GetInteger("increment", &increment_);
248 grouping->GetInteger("increment_frequency", &time_slice_);
249 grouping->GetInteger("increment_max", &max_group_);
251 DVLOG(1) << "num_groups_ = " << num_groups_
252 << ", initial_segment_ = " << initial_segment_
253 << ", increment_ = " << increment_
254 << ", time_slice_ = " << time_slice_
255 << ", max_group_ = " << max_group_;
258 // Strings.
259 const base::DictionaryValue* strings = NULL;
260 promo->GetDictionary("strings", &strings);
262 // Payload.
263 const base::DictionaryValue* payload = NULL;
264 if (promo->GetDictionary("payload", &payload)) {
265 base::Value* ppcopy = DeepCopyAndResolveStrings(payload, strings);
266 DCHECK(ppcopy && ppcopy->IsType(base::Value::TYPE_DICTIONARY));
267 promo_payload_.reset(static_cast<base::DictionaryValue*>(ppcopy));
270 if (!promo_payload_->GetString("promo_message_short", &promo_text_) &&
271 strings) {
272 // For compatibility with the legacy desktop version,
273 // if no |payload.promo_message_short| is specified,
274 // the first string in |strings| is used.
275 base::DictionaryValue::Iterator iter(*strings);
276 iter.value().GetAsString(&promo_text_);
278 DVLOG(1) << "promo_text_=" << promo_text_;
280 promo->GetInteger("max_views", &max_views_);
281 DVLOG(1) << "max_views_ " << max_views_;
283 promo->GetInteger("max_seconds", &max_seconds_);
284 DVLOG(1) << "max_seconds_ " << max_seconds_;
286 CheckForNewNotification();
289 void NotificationPromo::CheckForNewNotification() {
290 NotificationPromo old_promo(local_state_);
291 old_promo.InitFromPrefs(promo_type_);
292 const double old_start = old_promo.start_;
293 const double old_end = old_promo.end_;
294 const std::string old_promo_text = old_promo.promo_text_;
296 new_notification_ =
297 old_start != start_ || old_end != end_ || old_promo_text != promo_text_;
298 if (new_notification_)
299 OnNewNotification();
302 void NotificationPromo::OnNewNotification() {
303 DVLOG(1) << "OnNewNotification";
304 // Create a new promo group.
305 group_ = base::RandInt(0, num_groups_ - 1);
306 WritePrefs();
309 // static
310 void NotificationPromo::RegisterPrefs(PrefRegistrySimple* registry) {
311 registry->RegisterDictionaryPref(kPrefPromoObject);
314 // static
315 void NotificationPromo::RegisterProfilePrefs(
316 user_prefs::PrefRegistrySyncable* registry) {
317 // TODO(dbeam): Registered only for migration. Remove in M28 when
318 // we're reasonably sure all prefs are gone.
319 // http://crbug.com/168887
320 registry->RegisterDictionaryPref(kPrefPromoObject);
323 // static
324 void NotificationPromo::MigrateUserPrefs(PrefService* user_prefs) {
325 user_prefs->ClearPref(kPrefPromoObject);
328 void NotificationPromo::WritePrefs() {
329 base::DictionaryValue* ntp_promo = new base::DictionaryValue;
330 ntp_promo->SetString(kPrefPromoText, promo_text_);
331 ntp_promo->Set(kPrefPromoPayload, promo_payload_->DeepCopy());
332 ntp_promo->SetDouble(kPrefPromoStart, start_);
333 ntp_promo->SetDouble(kPrefPromoEnd, end_);
335 ntp_promo->SetInteger(kPrefPromoNumGroups, num_groups_);
336 ntp_promo->SetInteger(kPrefPromoSegment, initial_segment_);
337 ntp_promo->SetInteger(kPrefPromoIncrement, increment_);
338 ntp_promo->SetInteger(kPrefPromoIncrementFrequency, time_slice_);
339 ntp_promo->SetInteger(kPrefPromoIncrementMax, max_group_);
341 ntp_promo->SetInteger(kPrefPromoMaxViews, max_views_);
342 ntp_promo->SetInteger(kPrefPromoMaxSeconds, max_seconds_);
343 ntp_promo->SetDouble(kPrefPromoFirstViewTime, first_view_time_);
345 ntp_promo->SetInteger(kPrefPromoGroup, group_);
346 ntp_promo->SetInteger(kPrefPromoViews, views_);
347 ntp_promo->SetBoolean(kPrefPromoClosed, closed_);
349 base::ListValue* promo_list = new base::ListValue;
350 promo_list->Set(0, ntp_promo); // Only support 1 promo for now.
352 base::DictionaryValue promo_dict;
353 promo_dict.MergeDictionary(local_state_->GetDictionary(kPrefPromoObject));
354 promo_dict.Set(PromoTypeToString(promo_type_), promo_list);
355 local_state_->Set(kPrefPromoObject, promo_dict);
356 DVLOG(1) << "WritePrefs " << promo_dict;
359 void NotificationPromo::InitFromPrefs(PromoType promo_type) {
360 promo_type_ = promo_type;
361 const base::DictionaryValue* promo_dict =
362 local_state_->GetDictionary(kPrefPromoObject);
363 if (!promo_dict)
364 return;
366 const base::ListValue* promo_list = NULL;
367 promo_dict->GetList(PromoTypeToString(promo_type_), &promo_list);
368 if (!promo_list)
369 return;
371 const base::DictionaryValue* ntp_promo = NULL;
372 promo_list->GetDictionary(0, &ntp_promo);
373 if (!ntp_promo)
374 return;
376 ntp_promo->GetString(kPrefPromoText, &promo_text_);
377 const base::DictionaryValue* promo_payload = NULL;
378 if (ntp_promo->GetDictionary(kPrefPromoPayload, &promo_payload))
379 promo_payload_.reset(promo_payload->DeepCopy());
381 ntp_promo->GetDouble(kPrefPromoStart, &start_);
382 ntp_promo->GetDouble(kPrefPromoEnd, &end_);
384 ntp_promo->GetInteger(kPrefPromoNumGroups, &num_groups_);
385 ntp_promo->GetInteger(kPrefPromoSegment, &initial_segment_);
386 ntp_promo->GetInteger(kPrefPromoIncrement, &increment_);
387 ntp_promo->GetInteger(kPrefPromoIncrementFrequency, &time_slice_);
388 ntp_promo->GetInteger(kPrefPromoIncrementMax, &max_group_);
390 ntp_promo->GetInteger(kPrefPromoMaxViews, &max_views_);
391 ntp_promo->GetInteger(kPrefPromoMaxSeconds, &max_seconds_);
392 ntp_promo->GetDouble(kPrefPromoFirstViewTime, &first_view_time_);
394 ntp_promo->GetInteger(kPrefPromoGroup, &group_);
395 ntp_promo->GetInteger(kPrefPromoViews, &views_);
396 ntp_promo->GetBoolean(kPrefPromoClosed, &closed_);
399 bool NotificationPromo::CanShow() const {
400 return !closed_ && !promo_text_.empty() && !ExceedsMaxGroup() &&
401 !ExceedsMaxViews() && !ExceedsMaxSeconds() &&
402 base::Time::FromDoubleT(StartTimeForGroup()) < base::Time::Now() &&
403 base::Time::FromDoubleT(EndTime()) > base::Time::Now();
406 // static
407 void NotificationPromo::HandleClosed(PromoType promo_type,
408 PrefService* local_state) {
409 NotificationPromo promo(local_state);
410 promo.InitFromPrefs(promo_type);
411 if (!promo.closed_) {
412 promo.closed_ = true;
413 promo.WritePrefs();
417 // static
418 bool NotificationPromo::HandleViewed(PromoType promo_type,
419 PrefService* local_state) {
420 NotificationPromo promo(local_state);
421 promo.InitFromPrefs(promo_type);
422 ++promo.views_;
423 if (promo.first_view_time_ == 0) {
424 promo.first_view_time_ = base::Time::Now().ToDoubleT();
426 promo.WritePrefs();
427 return promo.ExceedsMaxViews() || promo.ExceedsMaxSeconds();
430 bool NotificationPromo::ExceedsMaxGroup() const {
431 return (max_group_ == 0) ? false : group_ >= max_group_;
434 bool NotificationPromo::ExceedsMaxViews() const {
435 return (max_views_ == 0) ? false : views_ >= max_views_;
438 bool NotificationPromo::ExceedsMaxSeconds() const {
439 if (max_seconds_ == 0 || first_view_time_ == 0)
440 return false;
442 const base::Time last_view_time = base::Time::FromDoubleT(first_view_time_) +
443 base::TimeDelta::FromSeconds(max_seconds_);
444 return last_view_time < base::Time::Now();
447 // static
448 GURL NotificationPromo::PromoServerURL(version_info::Channel channel) {
449 GURL url(promo_server_url);
450 AppendQueryParameter(&url, "dist", ChannelString(channel));
451 AppendQueryParameter(&url, "osname", PlatformString());
452 AppendQueryParameter(&url, "branding", version_info::GetVersionNumber());
453 AppendQueryParameter(&url, "osver", base::SysInfo::OperatingSystemVersion());
454 DVLOG(1) << "PromoServerURL=" << url.spec();
455 // Note that locale param is added by WebResourceService.
456 return url;
459 double NotificationPromo::StartTimeForGroup() const {
460 if (group_ < initial_segment_)
461 return start_;
462 return start_ +
463 std::ceil(static_cast<float>(group_ - initial_segment_ + 1) /
464 increment_) *
465 time_slice_;
468 double NotificationPromo::EndTime() const {
469 return end_;
472 } // namespace web_resource