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/web_resource/promo_resource_service.h" 6 7#include "base/bind.h" 8#include "base/command_line.h" 9#include "base/message_loop/message_loop.h" 10#include "base/prefs/pref_registry_simple.h" 11#include "base/prefs/pref_service.h" 12#include "base/threading/thread_restrictions.h" 13#include "base/values.h" 14#include "chrome/browser/browser_process.h" 15#include "chrome/browser/chrome_notification_types.h" 16#include "chrome/browser/web_resource/notification_promo.h" 17#include "chrome/common/chrome_switches.h" 18#include "chrome/common/pref_names.h" 19#include "components/pref_registry/pref_registry_syncable.h" 20#include "content/public/browser/notification_service.h" 21#include "url/gurl.h" 22 23namespace { 24 25// Delay on first fetch so we don't interfere with startup. 26const int kStartResourceFetchDelay = 5000; 27 28// Delay between calls to fetch the promo json: 6 hours in production, and 3 min 29// in debug. 30const int kCacheUpdateDelay = 6 * 60 * 60 * 1000; 31const int kTestCacheUpdateDelay = 3 * 60 * 1000; 32 33// The promotion type used for Unpack() and ScheduleNotificationOnInit(). 34const NotificationPromo::PromoType kValidPromoTypes[] = { 35#if defined(OS_ANDROID) || defined(OS_IOS) 36 NotificationPromo::MOBILE_NTP_SYNC_PROMO, 37#if defined(OS_IOS) 38 NotificationPromo::MOBILE_NTP_WHATS_NEW_PROMO, 39#endif // defined(OS_IOS) 40#else 41 NotificationPromo::NTP_NOTIFICATION_PROMO, 42 NotificationPromo::NTP_BUBBLE_PROMO, 43#endif 44}; 45 46GURL GetPromoResourceURL() { 47 const std::string promo_server_url = CommandLine::ForCurrentProcess()-> 48 GetSwitchValueASCII(switches::kPromoServerURL); 49 return promo_server_url.empty() ? 50 NotificationPromo::PromoServerURL() : GURL(promo_server_url); 51} 52 53bool IsTest() { 54 return CommandLine::ForCurrentProcess()->HasSwitch(switches::kPromoServerURL); 55} 56 57int GetCacheUpdateDelay() { 58 return IsTest() ? kTestCacheUpdateDelay : kCacheUpdateDelay; 59} 60 61} // namespace 62 63// static 64void PromoResourceService::RegisterPrefs(PrefRegistrySimple* registry) { 65 registry->RegisterStringPref(prefs::kNtpPromoResourceCacheUpdate, "0"); 66 NotificationPromo::RegisterPrefs(registry); 67} 68 69// static 70void PromoResourceService::RegisterProfilePrefs( 71 user_prefs::PrefRegistrySyncable* registry) { 72 // TODO(dbeam): This is registered only for migration; remove in M28 73 // when all prefs have been cleared. http://crbug.com/168887 74 registry->RegisterStringPref( 75 prefs::kNtpPromoResourceCacheUpdate, 76 "0", 77 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 78 NotificationPromo::RegisterProfilePrefs(registry); 79} 80 81// static 82void PromoResourceService::MigrateUserPrefs(PrefService* user_prefs) { 83 user_prefs->ClearPref(prefs::kNtpPromoResourceCacheUpdate); 84 NotificationPromo::MigrateUserPrefs(user_prefs); 85} 86 87PromoResourceService::PromoResourceService() 88 : WebResourceService(g_browser_process->local_state(), 89 GetPromoResourceURL(), 90 true, // append locale to URL 91 prefs::kNtpPromoResourceCacheUpdate, 92 kStartResourceFetchDelay, 93 GetCacheUpdateDelay()), 94 weak_ptr_factory_(this) { 95 ScheduleNotificationOnInit(); 96} 97 98PromoResourceService::~PromoResourceService() { 99} 100 101void PromoResourceService::ScheduleNotification( 102 const NotificationPromo& notification_promo) { 103 const double promo_start = notification_promo.StartTimeForGroup(); 104 const double promo_end = notification_promo.EndTime(); 105 106 if (promo_start > 0 && promo_end > 0) { 107 const int64 ms_until_start = 108 static_cast<int64>((base::Time::FromDoubleT( 109 promo_start) - base::Time::Now()).InMilliseconds()); 110 const int64 ms_until_end = 111 static_cast<int64>((base::Time::FromDoubleT( 112 promo_end) - base::Time::Now()).InMilliseconds()); 113 if (ms_until_start > 0) { 114 // Schedule the next notification to happen at the start of promotion. 115 PostNotification(ms_until_start); 116 } else if (ms_until_end > 0) { 117 if (ms_until_start <= 0) { 118 // The promo is active. Notify immediately. 119 PostNotification(0); 120 } 121 // Schedule the next notification to happen at the end of promotion. 122 PostNotification(ms_until_end); 123 } else { 124 // The promo (if any) has finished. Notify immediately. 125 PostNotification(0); 126 } 127 } else { 128 // The promo (if any) was apparently cancelled. Notify immediately. 129 PostNotification(0); 130 } 131} 132 133void PromoResourceService::ScheduleNotificationOnInit() { 134 // If the promo start is in the future, set a notification task to 135 // invalidate the NTP cache at the time of the promo start. 136 for (size_t i = 0; i < arraysize(kValidPromoTypes); ++i) { 137 NotificationPromo notification_promo; 138 notification_promo.InitFromPrefs(kValidPromoTypes[i]); 139 ScheduleNotification(notification_promo); 140 } 141} 142 143void PromoResourceService::PostNotification(int64 delay_ms) { 144 // Note that this could cause re-issuing a notification every time 145 // we receive an update from a server if something goes wrong. 146 // Given that this couldn't happen more frequently than every 147 // kCacheUpdateDelay milliseconds, we should be fine. 148 // TODO(achuith): This crashes if we post delay_ms = 0 to the message loop. 149 // during startup. 150 if (delay_ms > 0) { 151 base::MessageLoop::current()->PostDelayedTask( 152 FROM_HERE, 153 base::Bind(&PromoResourceService::PromoResourceStateChange, 154 weak_ptr_factory_.GetWeakPtr()), 155 base::TimeDelta::FromMilliseconds(delay_ms)); 156 } else if (delay_ms == 0) { 157 PromoResourceStateChange(); 158 } 159} 160 161void PromoResourceService::PromoResourceStateChange() { 162 content::NotificationService* service = 163 content::NotificationService::current(); 164 service->Notify(chrome::NOTIFICATION_PROMO_RESOURCE_STATE_CHANGED, 165 content::Source<WebResourceService>(this), 166 content::NotificationService::NoDetails()); 167} 168 169void PromoResourceService::Unpack(const base::DictionaryValue& parsed_json) { 170 for (size_t i = 0; i < arraysize(kValidPromoTypes); ++i) { 171 NotificationPromo notification_promo; 172 notification_promo.InitFromJson(parsed_json, kValidPromoTypes[i]); 173 if (notification_promo.new_notification()) 174 ScheduleNotification(notification_promo); 175 } 176} 177