Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / web_resource / notification_promo.cc
blobcdae454d874b6cc4d56643bc1ce98c6c7654b5cd
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 "chrome/browser/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 "chrome/browser/browser_process.h"
21 #include "chrome/browser/ui/app_list/app_list_util.h"
22 #include "chrome/browser/web_resource/promo_resource_service.h"
23 #include "chrome/common/chrome_version_info.h"
24 #include "chrome/common/pref_names.h"
25 #include "components/pref_registry/pref_registry_syncable.h"
26 #include "content/public/browser/user_metrics.h"
27 #include "net/base/url_util.h"
28 #include "ui/base/device_form_factor.h"
29 #include "url/gurl.h"
31 using base::UserMetricsAction;
33 namespace {
35 const int kDefaultGroupSize = 100;
37 const char promo_server_url[] = "https://clients3.google.com/crsignal/client";
39 // The name of the preference that stores the promotion object.
40 const char kPrefPromoObject[] = "promo";
42 // Keys in the kPrefPromoObject dictionary; used only here.
43 const char kPrefPromoText[] = "text";
44 const char kPrefPromoPayload[] = "payload";
45 const char kPrefPromoStart[] = "start";
46 const char kPrefPromoEnd[] = "end";
47 const char kPrefPromoNumGroups[] = "num_groups";
48 const char kPrefPromoSegment[] = "segment";
49 const char kPrefPromoIncrement[] = "increment";
50 const char kPrefPromoIncrementFrequency[] = "increment_frequency";
51 const char kPrefPromoIncrementMax[] = "increment_max";
52 const char kPrefPromoMaxViews[] = "max_views";
53 const char kPrefPromoMaxSeconds[] = "max_seconds";
54 const char kPrefPromoFirstViewTime[] = "first_view_time";
55 const char kPrefPromoGroup[] = "group";
56 const char kPrefPromoViews[] = "views";
57 const char kPrefPromoClosed[] = "closed";
59 // Returns a string suitable for the Promo Server URL 'osname' value.
60 std::string PlatformString() {
61 #if defined(OS_WIN)
62 return "win";
63 #elif defined(OS_ANDROID)
64 ui::DeviceFormFactor form_factor = ui::GetDeviceFormFactor();
65 return std::string("android-") +
66 (form_factor == ui::DEVICE_FORM_FACTOR_TABLET ? "tablet" : "phone");
67 #elif defined(OS_IOS)
68 ui::DeviceFormFactor form_factor = ui::GetDeviceFormFactor();
69 return std::string("ios-") +
70 (form_factor == ui::DEVICE_FORM_FACTOR_TABLET ? "tablet" : "phone");
71 #elif defined(OS_MACOSX)
72 return "mac";
73 #elif defined(OS_CHROMEOS)
74 return "chromeos";
75 #elif defined(OS_LINUX)
76 return "linux";
77 #else
78 return "none";
79 #endif
82 // Returns a string suitable for the Promo Server URL 'dist' value.
83 const char* ChannelString() {
84 #if defined (OS_WIN)
85 // GetChannel hits the registry on Windows. See http://crbug.com/70898.
86 // TODO(achuith): Move NotificationPromo::PromoServerURL to the blocking pool.
87 base::ThreadRestrictions::ScopedAllowIO allow_io;
88 #endif
89 const chrome::VersionInfo::Channel channel =
90 chrome::VersionInfo::GetChannel();
91 switch (channel) {
92 case chrome::VersionInfo::CHANNEL_CANARY:
93 return "canary";
94 case chrome::VersionInfo::CHANNEL_DEV:
95 return "dev";
96 case chrome::VersionInfo::CHANNEL_BETA:
97 return "beta";
98 case chrome::VersionInfo::CHANNEL_STABLE:
99 return "stable";
100 default:
101 return "none";
105 struct PromoMapEntry {
106 NotificationPromo::PromoType promo_type;
107 const char* promo_type_str;
110 const PromoMapEntry kPromoMap[] = {
111 { NotificationPromo::NO_PROMO, "" },
112 { NotificationPromo::NTP_NOTIFICATION_PROMO, "ntp_notification_promo" },
113 { NotificationPromo::NTP_BUBBLE_PROMO, "ntp_bubble_promo" },
114 { NotificationPromo::MOBILE_NTP_SYNC_PROMO, "mobile_ntp_sync_promo" },
115 { NotificationPromo::MOBILE_NTP_WHATS_NEW_PROMO,
116 "mobile_ntp_whats_new_promo" },
119 // Convert PromoType to appropriate string.
120 const char* PromoTypeToString(NotificationPromo::PromoType promo_type) {
121 for (size_t i = 0; i < arraysize(kPromoMap); ++i) {
122 if (kPromoMap[i].promo_type == promo_type)
123 return kPromoMap[i].promo_type_str;
125 NOTREACHED();
126 return "";
129 // Deep-copies a node, replacing any "value" that is a key
130 // into "strings" dictionary with its value from "strings".
131 // E.g. for
132 // {promo_action_args:['MSG_SHORT']} + strings:{MSG_SHORT:'yes'}
133 // it will return
134 // {promo_action_args:['yes']}
135 // |node| - a value to be deep copied and resolved.
136 // |strings| - a dictionary of strings to be used for resolution.
137 // Returns a _new_ object that is a deep copy with replacements.
138 // TODO(aruslan): http://crbug.com/144320 Consider moving it to values.cc/h.
139 base::Value* DeepCopyAndResolveStrings(
140 const base::Value* node,
141 const base::DictionaryValue* strings) {
142 switch (node->GetType()) {
143 case base::Value::TYPE_LIST: {
144 const base::ListValue* list = static_cast<const base::ListValue*>(node);
145 base::ListValue* copy = new base::ListValue;
146 for (base::ListValue::const_iterator it = list->begin();
147 it != list->end();
148 ++it) {
149 base::Value* child_copy = DeepCopyAndResolveStrings(*it, strings);
150 copy->Append(child_copy);
152 return copy;
155 case base::Value::TYPE_DICTIONARY: {
156 const base::DictionaryValue* dict =
157 static_cast<const base::DictionaryValue*>(node);
158 base::DictionaryValue* copy = new base::DictionaryValue;
159 for (base::DictionaryValue::Iterator it(*dict);
160 !it.IsAtEnd();
161 it.Advance()) {
162 base::Value* child_copy = DeepCopyAndResolveStrings(&it.value(),
163 strings);
164 copy->SetWithoutPathExpansion(it.key(), child_copy);
166 return copy;
169 case base::Value::TYPE_STRING: {
170 std::string value;
171 bool rv = node->GetAsString(&value);
172 DCHECK(rv);
173 std::string actual_value;
174 if (!strings || !strings->GetString(value, &actual_value))
175 actual_value = value;
176 return new base::StringValue(actual_value);
179 default:
180 // For everything else, just make a copy.
181 return node->DeepCopy();
185 void AppendQueryParameter(GURL* url,
186 const std::string& param,
187 const std::string& value) {
188 *url = net::AppendQueryParameter(*url, param, value);
191 } // namespace
193 NotificationPromo::NotificationPromo()
194 : prefs_(g_browser_process->local_state()),
195 promo_type_(NO_PROMO),
196 promo_payload_(new base::DictionaryValue()),
197 start_(0.0),
198 end_(0.0),
199 num_groups_(kDefaultGroupSize),
200 initial_segment_(0),
201 increment_(1),
202 time_slice_(0),
203 max_group_(0),
204 max_views_(0),
205 max_seconds_(0),
206 first_view_time_(0),
207 group_(0),
208 views_(0),
209 closed_(false),
210 new_notification_(false) {
211 DCHECK(prefs_);
214 NotificationPromo::~NotificationPromo() {}
216 void NotificationPromo::InitFromJson(const base::DictionaryValue& json,
217 PromoType promo_type) {
218 promo_type_ = promo_type;
219 const base::ListValue* promo_list = NULL;
220 DVLOG(1) << "InitFromJson " << PromoTypeToString(promo_type_);
221 if (!json.GetList(PromoTypeToString(promo_type_), &promo_list))
222 return;
224 // No support for multiple promos yet. Only consider the first one.
225 const base::DictionaryValue* promo = NULL;
226 if (!promo_list->GetDictionary(0, &promo))
227 return;
229 // Date.
230 const base::ListValue* date_list = NULL;
231 if (promo->GetList("date", &date_list)) {
232 const base::DictionaryValue* date;
233 if (date_list->GetDictionary(0, &date)) {
234 std::string time_str;
235 base::Time time;
236 if (date->GetString("start", &time_str) &&
237 base::Time::FromString(time_str.c_str(), &time)) {
238 start_ = time.ToDoubleT();
239 DVLOG(1) << "start str=" << time_str
240 << ", start_="<< base::DoubleToString(start_);
242 if (date->GetString("end", &time_str) &&
243 base::Time::FromString(time_str.c_str(), &time)) {
244 end_ = time.ToDoubleT();
245 DVLOG(1) << "end str =" << time_str
246 << ", end_=" << base::DoubleToString(end_);
251 // Grouping.
252 const base::DictionaryValue* grouping = NULL;
253 if (promo->GetDictionary("grouping", &grouping)) {
254 grouping->GetInteger("buckets", &num_groups_);
255 grouping->GetInteger("segment", &initial_segment_);
256 grouping->GetInteger("increment", &increment_);
257 grouping->GetInteger("increment_frequency", &time_slice_);
258 grouping->GetInteger("increment_max", &max_group_);
260 DVLOG(1) << "num_groups_ = " << num_groups_
261 << ", initial_segment_ = " << initial_segment_
262 << ", increment_ = " << increment_
263 << ", time_slice_ = " << time_slice_
264 << ", max_group_ = " << max_group_;
267 // Strings.
268 const base::DictionaryValue* strings = NULL;
269 promo->GetDictionary("strings", &strings);
271 // Payload.
272 const base::DictionaryValue* payload = NULL;
273 if (promo->GetDictionary("payload", &payload)) {
274 base::Value* ppcopy = DeepCopyAndResolveStrings(payload, strings);
275 DCHECK(ppcopy && ppcopy->IsType(base::Value::TYPE_DICTIONARY));
276 promo_payload_.reset(static_cast<base::DictionaryValue*>(ppcopy));
279 if (!promo_payload_->GetString("promo_message_short", &promo_text_) &&
280 strings) {
281 // For compatibility with the legacy desktop version,
282 // if no |payload.promo_message_short| is specified,
283 // the first string in |strings| is used.
284 base::DictionaryValue::Iterator iter(*strings);
285 iter.value().GetAsString(&promo_text_);
287 DVLOG(1) << "promo_text_=" << promo_text_;
289 promo->GetInteger("max_views", &max_views_);
290 DVLOG(1) << "max_views_ " << max_views_;
292 promo->GetInteger("max_seconds", &max_seconds_);
293 DVLOG(1) << "max_seconds_ " << max_seconds_;
295 CheckForNewNotification();
298 void NotificationPromo::CheckForNewNotification() {
299 NotificationPromo old_promo;
300 old_promo.InitFromPrefs(promo_type_);
301 const double old_start = old_promo.start_;
302 const double old_end = old_promo.end_;
303 const std::string old_promo_text = old_promo.promo_text_;
305 new_notification_ =
306 old_start != start_ || old_end != end_ || old_promo_text != promo_text_;
307 if (new_notification_)
308 OnNewNotification();
311 void NotificationPromo::OnNewNotification() {
312 DVLOG(1) << "OnNewNotification";
313 // Create a new promo group.
314 group_ = base::RandInt(0, num_groups_ - 1);
315 WritePrefs();
318 // static
319 void NotificationPromo::RegisterPrefs(PrefRegistrySimple* registry) {
320 registry->RegisterDictionaryPref(kPrefPromoObject);
323 // static
324 void NotificationPromo::RegisterProfilePrefs(
325 user_prefs::PrefRegistrySyncable* registry) {
326 // TODO(dbeam): Registered only for migration. Remove in M28 when
327 // we're reasonably sure all prefs are gone.
328 // http://crbug.com/168887
329 registry->RegisterDictionaryPref(kPrefPromoObject);
332 // static
333 void NotificationPromo::MigrateUserPrefs(PrefService* user_prefs) {
334 user_prefs->ClearPref(kPrefPromoObject);
337 void NotificationPromo::WritePrefs() {
338 base::DictionaryValue* ntp_promo = new base::DictionaryValue;
339 ntp_promo->SetString(kPrefPromoText, promo_text_);
340 ntp_promo->Set(kPrefPromoPayload, promo_payload_->DeepCopy());
341 ntp_promo->SetDouble(kPrefPromoStart, start_);
342 ntp_promo->SetDouble(kPrefPromoEnd, end_);
344 ntp_promo->SetInteger(kPrefPromoNumGroups, num_groups_);
345 ntp_promo->SetInteger(kPrefPromoSegment, initial_segment_);
346 ntp_promo->SetInteger(kPrefPromoIncrement, increment_);
347 ntp_promo->SetInteger(kPrefPromoIncrementFrequency, time_slice_);
348 ntp_promo->SetInteger(kPrefPromoIncrementMax, max_group_);
350 ntp_promo->SetInteger(kPrefPromoMaxViews, max_views_);
351 ntp_promo->SetInteger(kPrefPromoMaxSeconds, max_seconds_);
352 ntp_promo->SetDouble(kPrefPromoFirstViewTime, first_view_time_);
354 ntp_promo->SetInteger(kPrefPromoGroup, group_);
355 ntp_promo->SetInteger(kPrefPromoViews, views_);
356 ntp_promo->SetBoolean(kPrefPromoClosed, closed_);
358 base::ListValue* promo_list = new base::ListValue;
359 promo_list->Set(0, ntp_promo); // Only support 1 promo for now.
361 base::DictionaryValue promo_dict;
362 promo_dict.MergeDictionary(prefs_->GetDictionary(kPrefPromoObject));
363 promo_dict.Set(PromoTypeToString(promo_type_), promo_list);
364 prefs_->Set(kPrefPromoObject, promo_dict);
365 DVLOG(1) << "WritePrefs " << promo_dict;
368 void NotificationPromo::InitFromPrefs(PromoType promo_type) {
369 promo_type_ = promo_type;
370 const base::DictionaryValue* promo_dict =
371 prefs_->GetDictionary(kPrefPromoObject);
372 if (!promo_dict)
373 return;
375 const base::ListValue* promo_list = NULL;
376 promo_dict->GetList(PromoTypeToString(promo_type_), &promo_list);
377 if (!promo_list)
378 return;
380 const base::DictionaryValue* ntp_promo = NULL;
381 promo_list->GetDictionary(0, &ntp_promo);
382 if (!ntp_promo)
383 return;
385 ntp_promo->GetString(kPrefPromoText, &promo_text_);
386 const base::DictionaryValue* promo_payload = NULL;
387 if (ntp_promo->GetDictionary(kPrefPromoPayload, &promo_payload))
388 promo_payload_.reset(promo_payload->DeepCopy());
390 ntp_promo->GetDouble(kPrefPromoStart, &start_);
391 ntp_promo->GetDouble(kPrefPromoEnd, &end_);
393 ntp_promo->GetInteger(kPrefPromoNumGroups, &num_groups_);
394 ntp_promo->GetInteger(kPrefPromoSegment, &initial_segment_);
395 ntp_promo->GetInteger(kPrefPromoIncrement, &increment_);
396 ntp_promo->GetInteger(kPrefPromoIncrementFrequency, &time_slice_);
397 ntp_promo->GetInteger(kPrefPromoIncrementMax, &max_group_);
399 ntp_promo->GetInteger(kPrefPromoMaxViews, &max_views_);
400 ntp_promo->GetInteger(kPrefPromoMaxSeconds, &max_seconds_);
401 ntp_promo->GetDouble(kPrefPromoFirstViewTime, &first_view_time_);
403 ntp_promo->GetInteger(kPrefPromoGroup, &group_);
404 ntp_promo->GetInteger(kPrefPromoViews, &views_);
405 ntp_promo->GetBoolean(kPrefPromoClosed, &closed_);
408 bool NotificationPromo::CheckAppLauncher() const {
409 bool is_app_launcher_promo = false;
410 if (!promo_payload_->GetBoolean("is_app_launcher_promo",
411 &is_app_launcher_promo))
412 return true;
413 return !is_app_launcher_promo || !IsAppLauncherEnabled();
416 bool NotificationPromo::CanShow() const {
417 return !closed_ &&
418 !promo_text_.empty() &&
419 !ExceedsMaxGroup() &&
420 !ExceedsMaxViews() &&
421 !ExceedsMaxSeconds() &&
422 CheckAppLauncher() &&
423 base::Time::FromDoubleT(StartTimeForGroup()) < base::Time::Now() &&
424 base::Time::FromDoubleT(EndTime()) > base::Time::Now();
427 // static
428 void NotificationPromo::HandleClosed(PromoType promo_type) {
429 content::RecordAction(UserMetricsAction("NTPPromoClosed"));
430 NotificationPromo promo;
431 promo.InitFromPrefs(promo_type);
432 if (!promo.closed_) {
433 promo.closed_ = true;
434 promo.WritePrefs();
438 // static
439 bool NotificationPromo::HandleViewed(PromoType promo_type) {
440 content::RecordAction(UserMetricsAction("NTPPromoShown"));
441 NotificationPromo promo;
442 promo.InitFromPrefs(promo_type);
443 ++promo.views_;
444 if (promo.first_view_time_ == 0) {
445 promo.first_view_time_ = base::Time::Now().ToDoubleT();
447 promo.WritePrefs();
448 return promo.ExceedsMaxViews() || promo.ExceedsMaxSeconds();
451 bool NotificationPromo::ExceedsMaxGroup() const {
452 return (max_group_ == 0) ? false : group_ >= max_group_;
455 bool NotificationPromo::ExceedsMaxViews() const {
456 return (max_views_ == 0) ? false : views_ >= max_views_;
459 bool NotificationPromo::ExceedsMaxSeconds() const {
460 if (max_seconds_ == 0 || first_view_time_ == 0)
461 return false;
463 const base::Time last_view_time = base::Time::FromDoubleT(first_view_time_) +
464 base::TimeDelta::FromSeconds(max_seconds_);
465 return last_view_time < base::Time::Now();
468 // static
469 GURL NotificationPromo::PromoServerURL() {
470 GURL url(promo_server_url);
471 AppendQueryParameter(&url, "dist", ChannelString());
472 AppendQueryParameter(&url, "osname", PlatformString());
473 AppendQueryParameter(&url, "branding", chrome::VersionInfo().Version());
474 AppendQueryParameter(&url, "osver", base::SysInfo::OperatingSystemVersion());
475 DVLOG(1) << "PromoServerURL=" << url.spec();
476 // Note that locale param is added by WebResourceService.
477 return url;
480 double NotificationPromo::StartTimeForGroup() const {
481 if (group_ < initial_segment_)
482 return start_;
483 return start_ +
484 std::ceil(static_cast<float>(group_ - initial_segment_ + 1) / increment_)
485 * time_slice_;
488 double NotificationPromo::EndTime() const {
489 return end_;