OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2011 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/web_resource/promo_resource_service.h" |
| 6 |
| 7 #include "base/string_number_conversions.h" |
| 8 #include "base/threading/thread_restrictions.h" |
| 9 #include "base/time.h" |
| 10 #include "base/values.h" |
| 11 #include "chrome/browser/browser_process.h" |
| 12 #include "chrome/browser/browser_thread.h" |
| 13 #include "chrome/browser/platform_util.h" |
| 14 #include "chrome/browser/prefs/pref_service.h" |
| 15 #include "chrome/browser/profiles/profile.h" |
| 16 #include "chrome/browser/sync/sync_ui_util.h" |
| 17 #include "chrome/common/notification_service.h" |
| 18 #include "chrome/common/notification_type.h" |
| 19 #include "chrome/common/pref_names.h" |
| 20 |
| 21 namespace { |
| 22 |
| 23 // Delay on first fetch so we don't interfere with startup. |
| 24 static const int kStartResourceFetchDelay = 5000; |
| 25 |
| 26 // Delay between calls to update the cache (48 hours). |
| 27 static const int kCacheUpdateDelay = 48 * 60 * 60 * 1000; |
| 28 |
| 29 // Users are randomly assigned to one of kNTPPromoGroupSize buckets, in order |
| 30 // to be able to roll out promos slowly, or display different promos to |
| 31 // different groups. |
| 32 static const int kNTPPromoGroupSize = 16; |
| 33 |
| 34 // Maximum number of hours for each time slice (4 weeks). |
| 35 static const int kMaxTimeSliceHours = 24 * 7 * 4; |
| 36 |
| 37 // Used to determine which build type should be shown a given promo. |
| 38 enum BuildType { |
| 39 DEV_BUILD = 1, |
| 40 BETA_BUILD = 1 << 1, |
| 41 STABLE_BUILD = 1 << 2, |
| 42 }; |
| 43 |
| 44 } // namespace |
| 45 |
| 46 const char* PromoResourceService::kCurrentTipPrefName = "current_tip"; |
| 47 const char* PromoResourceService::kTipCachePrefName = "tips"; |
| 48 |
| 49 // Server for dynamically loaded NTP HTML elements. TODO(mirandac): append |
| 50 // locale for future usage, when we're serving localizable strings. |
| 51 const char* PromoResourceService::kDefaultPromoResourceServer = |
| 52 "https://www.google.com/support/chrome/bin/topic/1142433/inproduct?hl="; |
| 53 |
| 54 PromoResourceService::PromoResourceService(Profile* profile) |
| 55 : WebResourceService(profile, |
| 56 PromoResourceService::kDefaultPromoResourceServer, |
| 57 true, // append locale to URL |
| 58 NotificationType::PROMO_RESOURCE_STATE_CHANGED, |
| 59 prefs::kNTPPromoResourceCacheUpdate, |
| 60 kStartResourceFetchDelay, |
| 61 kCacheUpdateDelay), |
| 62 web_resource_cache_(NULL) { |
| 63 Init(); |
| 64 } |
| 65 |
| 66 PromoResourceService::~PromoResourceService() { } |
| 67 |
| 68 void PromoResourceService::Init() { |
| 69 prefs_->RegisterStringPref(prefs::kNTPPromoResourceCacheUpdate, "0"); |
| 70 prefs_->RegisterDoublePref(prefs::kNTPCustomLogoStart, 0); |
| 71 prefs_->RegisterDoublePref(prefs::kNTPCustomLogoEnd, 0); |
| 72 prefs_->RegisterDoublePref(prefs::kNTPPromoStart, 0); |
| 73 prefs_->RegisterDoublePref(prefs::kNTPPromoEnd, 0); |
| 74 prefs_->RegisterStringPref(prefs::kNTPPromoLine, std::string()); |
| 75 prefs_->RegisterBooleanPref(prefs::kNTPPromoClosed, false); |
| 76 prefs_->RegisterIntegerPref(prefs::kNTPPromoGroup, -1); |
| 77 prefs_->RegisterIntegerPref(prefs::kNTPPromoBuild, |
| 78 DEV_BUILD | BETA_BUILD | STABLE_BUILD); |
| 79 prefs_->RegisterIntegerPref(prefs::kNTPPromoGroupTimeSlice, 0); |
| 80 |
| 81 // If the promo start is in the future, set a notification task to invalidate |
| 82 // the NTP cache at the time of the promo start. |
| 83 double promo_start = prefs_->GetDouble(prefs::kNTPPromoStart); |
| 84 double promo_end = prefs_->GetDouble(prefs::kNTPPromoEnd); |
| 85 ScheduleNotification(promo_start, promo_end); |
| 86 } |
| 87 |
| 88 void PromoResourceService::Unpack(const DictionaryValue& parsed_json) { |
| 89 UnpackLogoSignal(parsed_json); |
| 90 UnpackPromoSignal(parsed_json); |
| 91 } |
| 92 |
| 93 void PromoResourceService::ScheduleNotification(double promo_start, |
| 94 double promo_end) { |
| 95 if (promo_start > 0 && promo_end > 0) { |
| 96 int64 ms_until_start = |
| 97 static_cast<int64>((base::Time::FromDoubleT( |
| 98 promo_start) - base::Time::Now()).InMilliseconds()); |
| 99 int64 ms_until_end = |
| 100 static_cast<int64>((base::Time::FromDoubleT( |
| 101 promo_end) - base::Time::Now()).InMilliseconds()); |
| 102 if (ms_until_start > 0) |
| 103 PostNotification(ms_until_start); |
| 104 if (ms_until_end > 0) { |
| 105 PostNotification(ms_until_end); |
| 106 if (ms_until_start <= 0) { |
| 107 // Notify immediately if time is between start and end. |
| 108 PostNotification(0); |
| 109 } |
| 110 } |
| 111 } |
| 112 } |
| 113 |
| 114 void PromoResourceService::UnpackTips(const DictionaryValue& parsed_json) { |
| 115 // Get dictionary of cached preferences. |
| 116 web_resource_cache_ = |
| 117 prefs_->GetMutableDictionary(prefs::kNTPPromoResourceCache); |
| 118 |
| 119 // The list of individual tips. |
| 120 ListValue* tip_holder = new ListValue(); |
| 121 web_resource_cache_->Set(PromoResourceService::kTipCachePrefName, tip_holder); |
| 122 |
| 123 DictionaryValue* topic_dict; |
| 124 ListValue* answer_list; |
| 125 std::string topic_id; |
| 126 std::string answer_id; |
| 127 std::string inproduct; |
| 128 int tip_counter = 0; |
| 129 |
| 130 if (parsed_json.GetDictionary("topic", &topic_dict)) { |
| 131 if (topic_dict->GetString("topic_id", &topic_id)) |
| 132 web_resource_cache_->SetString("topic_id", topic_id); |
| 133 if (topic_dict->GetList("answers", &answer_list)) { |
| 134 for (ListValue::const_iterator tip_iter = answer_list->begin(); |
| 135 tip_iter != answer_list->end(); ++tip_iter) { |
| 136 if (!(*tip_iter)->IsType(Value::TYPE_DICTIONARY)) |
| 137 continue; |
| 138 DictionaryValue* a_dic = |
| 139 static_cast<DictionaryValue*>(*tip_iter); |
| 140 if (a_dic->GetString("inproduct", &inproduct)) { |
| 141 tip_holder->Append(Value::CreateStringValue(inproduct)); |
| 142 } |
| 143 tip_counter++; |
| 144 } |
| 145 // If tips exist, set current index to 0. |
| 146 if (!inproduct.empty()) { |
| 147 web_resource_cache_->SetInteger( |
| 148 PromoResourceService::kCurrentTipPrefName, 0); |
| 149 } |
| 150 } |
| 151 } |
| 152 } |
| 153 |
| 154 void PromoResourceService::UnpackPromoSignal( |
| 155 const DictionaryValue& parsed_json) { |
| 156 DictionaryValue* topic_dict; |
| 157 ListValue* answer_list; |
| 158 double old_promo_start = 0; |
| 159 double old_promo_end = 0; |
| 160 double promo_start = 0; |
| 161 double promo_end = 0; |
| 162 |
| 163 // Check for preexisting start and end values. |
| 164 if (prefs_->HasPrefPath(prefs::kNTPPromoStart) && |
| 165 prefs_->HasPrefPath(prefs::kNTPPromoEnd)) { |
| 166 old_promo_start = prefs_->GetDouble(prefs::kNTPPromoStart); |
| 167 old_promo_end = prefs_->GetDouble(prefs::kNTPPromoEnd); |
| 168 } |
| 169 |
| 170 // Check for newly received start and end values. |
| 171 if (parsed_json.GetDictionary("topic", &topic_dict)) { |
| 172 if (topic_dict->GetList("answers", &answer_list)) { |
| 173 std::string promo_start_string = ""; |
| 174 std::string promo_end_string = ""; |
| 175 std::string promo_string = ""; |
| 176 std::string promo_build = ""; |
| 177 int promo_build_type = 0; |
| 178 int time_slice_hrs = 0; |
| 179 for (ListValue::const_iterator tip_iter = answer_list->begin(); |
| 180 tip_iter != answer_list->end(); ++tip_iter) { |
| 181 if (!(*tip_iter)->IsType(Value::TYPE_DICTIONARY)) |
| 182 continue; |
| 183 DictionaryValue* a_dic = |
| 184 static_cast<DictionaryValue*>(*tip_iter); |
| 185 std::string promo_signal; |
| 186 if (a_dic->GetString("name", &promo_signal)) { |
| 187 if (promo_signal == "promo_start") { |
| 188 a_dic->GetString("question", &promo_build); |
| 189 size_t split = promo_build.find(":"); |
| 190 if (split != std::string::npos && |
| 191 base::StringToInt(promo_build.substr(0, split), |
| 192 &promo_build_type) && |
| 193 base::StringToInt(promo_build.substr(split+1), |
| 194 &time_slice_hrs) && |
| 195 promo_build_type >= 0 && |
| 196 promo_build_type <= (DEV_BUILD | BETA_BUILD | STABLE_BUILD) && |
| 197 time_slice_hrs >= 0 && |
| 198 time_slice_hrs <= kMaxTimeSliceHours) { |
| 199 prefs_->SetInteger(prefs::kNTPPromoBuild, promo_build_type); |
| 200 prefs_->SetInteger(prefs::kNTPPromoGroupTimeSlice, |
| 201 time_slice_hrs); |
| 202 } else { |
| 203 // If no time data or bad time data are set, show promo on all |
| 204 // builds with no time slicing. |
| 205 prefs_->SetInteger(prefs::kNTPPromoBuild, |
| 206 DEV_BUILD | BETA_BUILD | STABLE_BUILD); |
| 207 prefs_->SetInteger(prefs::kNTPPromoGroupTimeSlice, 0); |
| 208 } |
| 209 a_dic->GetString("inproduct", &promo_start_string); |
| 210 a_dic->GetString("tooltip", &promo_string); |
| 211 prefs_->SetString(prefs::kNTPPromoLine, promo_string); |
| 212 srand(static_cast<uint32>(time(NULL))); |
| 213 prefs_->SetInteger(prefs::kNTPPromoGroup, |
| 214 rand() % kNTPPromoGroupSize); |
| 215 } else if (promo_signal == "promo_end") { |
| 216 a_dic->GetString("inproduct", &promo_end_string); |
| 217 } |
| 218 } |
| 219 } |
| 220 if (!promo_start_string.empty() && |
| 221 promo_start_string.length() > 0 && |
| 222 !promo_end_string.empty() && |
| 223 promo_end_string.length() > 0) { |
| 224 base::Time start_time; |
| 225 base::Time end_time; |
| 226 if (base::Time::FromString( |
| 227 ASCIIToWide(promo_start_string).c_str(), &start_time) && |
| 228 base::Time::FromString( |
| 229 ASCIIToWide(promo_end_string).c_str(), &end_time)) { |
| 230 // Add group time slice, adjusted from hours to seconds. |
| 231 promo_start = start_time.ToDoubleT() + |
| 232 (prefs_->FindPreference(prefs::kNTPPromoGroup) ? |
| 233 prefs_->GetInteger(prefs::kNTPPromoGroup) * |
| 234 time_slice_hrs * 60 * 60 : 0); |
| 235 promo_end = end_time.ToDoubleT(); |
| 236 } |
| 237 } |
| 238 } |
| 239 } |
| 240 |
| 241 // If start or end times have changed, trigger a new web resource |
| 242 // notification, so that the logo on the NTP is updated. This check is |
| 243 // outside the reading of the web resource data, because the absence of |
| 244 // dates counts as a triggering change if there were dates before. |
| 245 // Also reset the promo closed preference, to signal a new promo. |
| 246 if (!(old_promo_start == promo_start) || |
| 247 !(old_promo_end == promo_end)) { |
| 248 prefs_->SetDouble(prefs::kNTPPromoStart, promo_start); |
| 249 prefs_->SetDouble(prefs::kNTPPromoEnd, promo_end); |
| 250 prefs_->SetBoolean(prefs::kNTPPromoClosed, false); |
| 251 ScheduleNotification(promo_start, promo_end); |
| 252 } |
| 253 } |
| 254 |
| 255 void PromoResourceService::UnpackLogoSignal( |
| 256 const DictionaryValue& parsed_json) { |
| 257 DictionaryValue* topic_dict; |
| 258 ListValue* answer_list; |
| 259 double old_logo_start = 0; |
| 260 double old_logo_end = 0; |
| 261 double logo_start = 0; |
| 262 double logo_end = 0; |
| 263 |
| 264 // Check for preexisting start and end values. |
| 265 if (prefs_->HasPrefPath(prefs::kNTPCustomLogoStart) && |
| 266 prefs_->HasPrefPath(prefs::kNTPCustomLogoEnd)) { |
| 267 old_logo_start = prefs_->GetDouble(prefs::kNTPCustomLogoStart); |
| 268 old_logo_end = prefs_->GetDouble(prefs::kNTPCustomLogoEnd); |
| 269 } |
| 270 |
| 271 // Check for newly received start and end values. |
| 272 if (parsed_json.GetDictionary("topic", &topic_dict)) { |
| 273 if (topic_dict->GetList("answers", &answer_list)) { |
| 274 std::string logo_start_string = ""; |
| 275 std::string logo_end_string = ""; |
| 276 for (ListValue::const_iterator tip_iter = answer_list->begin(); |
| 277 tip_iter != answer_list->end(); ++tip_iter) { |
| 278 if (!(*tip_iter)->IsType(Value::TYPE_DICTIONARY)) |
| 279 continue; |
| 280 DictionaryValue* a_dic = |
| 281 static_cast<DictionaryValue*>(*tip_iter); |
| 282 std::string logo_signal; |
| 283 if (a_dic->GetString("name", &logo_signal)) { |
| 284 if (logo_signal == "custom_logo_start") { |
| 285 a_dic->GetString("inproduct", &logo_start_string); |
| 286 } else if (logo_signal == "custom_logo_end") { |
| 287 a_dic->GetString("inproduct", &logo_end_string); |
| 288 } |
| 289 } |
| 290 } |
| 291 if (!logo_start_string.empty() && |
| 292 logo_start_string.length() > 0 && |
| 293 !logo_end_string.empty() && |
| 294 logo_end_string.length() > 0) { |
| 295 base::Time start_time; |
| 296 base::Time end_time; |
| 297 if (base::Time::FromString( |
| 298 ASCIIToWide(logo_start_string).c_str(), &start_time) && |
| 299 base::Time::FromString( |
| 300 ASCIIToWide(logo_end_string).c_str(), &end_time)) { |
| 301 logo_start = start_time.ToDoubleT(); |
| 302 logo_end = end_time.ToDoubleT(); |
| 303 } |
| 304 } |
| 305 } |
| 306 } |
| 307 |
| 308 // If logo start or end times have changed, trigger a new web resource |
| 309 // notification, so that the logo on the NTP is updated. This check is |
| 310 // outside the reading of the web resource data, because the absence of |
| 311 // dates counts as a triggering change if there were dates before. |
| 312 if (!(old_logo_start == logo_start) || |
| 313 !(old_logo_end == logo_end)) { |
| 314 prefs_->SetDouble(prefs::kNTPCustomLogoStart, logo_start); |
| 315 prefs_->SetDouble(prefs::kNTPCustomLogoEnd, logo_end); |
| 316 NotificationService* service = NotificationService::current(); |
| 317 service->Notify(NotificationType::PROMO_RESOURCE_STATE_CHANGED, |
| 318 Source<WebResourceService>(this), |
| 319 NotificationService::NoDetails()); |
| 320 } |
| 321 } |
| 322 |
| 323 namespace PromoResourceServiceUtil { |
| 324 |
| 325 bool CanShowPromo(Profile* profile) { |
| 326 bool promo_closed = false; |
| 327 PrefService* prefs = profile->GetPrefs(); |
| 328 if (prefs->HasPrefPath(prefs::kNTPPromoClosed)) |
| 329 promo_closed = prefs->GetBoolean(prefs::kNTPPromoClosed); |
| 330 |
| 331 // Only show if not synced. |
| 332 bool is_synced = |
| 333 (profile->HasProfileSyncService() && |
| 334 sync_ui_util::GetStatus( |
| 335 profile->GetProfileSyncService()) == sync_ui_util::SYNCED); |
| 336 |
| 337 // GetVersionStringModifier hits the registry. See http://crbug.com/70898. |
| 338 base::ThreadRestrictions::ScopedAllowIO allow_io; |
| 339 const std::string channel = platform_util::GetVersionStringModifier(); |
| 340 bool is_promo_build = false; |
| 341 if (prefs->HasPrefPath(prefs::kNTPPromoBuild)) { |
| 342 int builds_allowed = prefs->GetInteger(prefs::kNTPPromoBuild); |
| 343 if (channel == "dev") { |
| 344 is_promo_build = (DEV_BUILD & builds_allowed) != 0; |
| 345 } else if (channel == "beta") { |
| 346 is_promo_build = (BETA_BUILD & builds_allowed) != 0; |
| 347 } else if (channel == "stable") { |
| 348 is_promo_build = (STABLE_BUILD & builds_allowed) != 0; |
| 349 } else { |
| 350 is_promo_build = true; |
| 351 } |
| 352 } |
| 353 |
| 354 return !promo_closed && !is_synced && is_promo_build; |
| 355 } |
| 356 |
| 357 } // namespace PromoResourceServiceUtil |
| 358 |
OLD | NEW |