// 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 "components/web_resource/promo_resource_service.h" #include "base/bind.h" #include "base/command_line.h" #include "base/location.h" #include "base/prefs/pref_registry_simple.h" #include "base/prefs/pref_service.h" #include "base/single_thread_task_runner.h" #include "base/thread_task_runner_handle.h" #include "base/threading/thread_restrictions.h" #include "base/values.h" #include "components/pref_registry/pref_registry_syncable.h" #include "components/web_resource/notification_promo.h" #include "components/web_resource/web_resource_pref_names.h" #include "components/web_resource/web_resource_switches.h" #include "url/gurl.h" namespace web_resource { namespace { // Delay on first fetch so we don't interfere with startup. const int kStartResourceFetchDelay = 5000; // Delay between calls to fetch the promo json: 6 hours in production, and 3 min // in debug. const int kCacheUpdateDelay = 6 * 60 * 60 * 1000; const int kTestCacheUpdateDelay = 3 * 60 * 1000; // The promotion type used for Unpack() and ScheduleNotificationOnInit(). const NotificationPromo::PromoType kValidPromoTypes[] = { #if defined(OS_ANDROID) || defined(OS_IOS) NotificationPromo::MOBILE_NTP_SYNC_PROMO, #if defined(OS_IOS) NotificationPromo::MOBILE_NTP_WHATS_NEW_PROMO, #endif // defined(OS_IOS) #else NotificationPromo::NTP_NOTIFICATION_PROMO, NotificationPromo::NTP_BUBBLE_PROMO, #endif }; GURL GetPromoResourceURL(version_info::Channel channel) { const std::string promo_server_url = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( switches::kPromoServerURL); return promo_server_url.empty() ? NotificationPromo::PromoServerURL(channel) : GURL(promo_server_url); } bool IsTest() { return base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kPromoServerURL); } int GetCacheUpdateDelay() { return IsTest() ? kTestCacheUpdateDelay : kCacheUpdateDelay; } } // namespace // static void PromoResourceService::RegisterPrefs(PrefRegistrySimple* registry) { registry->RegisterStringPref(prefs::kNtpPromoResourceCacheUpdate, "0"); NotificationPromo::RegisterPrefs(registry); } // static void PromoResourceService::RegisterProfilePrefs( user_prefs::PrefRegistrySyncable* registry) { // TODO(dbeam): This is registered only for migration; remove in M28 // when all prefs have been cleared. http://crbug.com/168887 registry->RegisterStringPref(prefs::kNtpPromoResourceCacheUpdate, "0"); NotificationPromo::RegisterProfilePrefs(registry); } // static void PromoResourceService::MigrateUserPrefs(PrefService* user_prefs) { user_prefs->ClearPref(prefs::kNtpPromoResourceCacheUpdate); NotificationPromo::MigrateUserPrefs(user_prefs); } PromoResourceService::PromoResourceService( PrefService* local_state, version_info::Channel channel, const std::string& application_locale, net::URLRequestContextGetter* request_context, const char* disable_network_switch, const ParseJSONCallback& parse_json_callback) : WebResourceService(local_state, GetPromoResourceURL(channel), application_locale, // append locale to URL prefs::kNtpPromoResourceCacheUpdate, kStartResourceFetchDelay, GetCacheUpdateDelay(), request_context, disable_network_switch, parse_json_callback), weak_ptr_factory_(this) { ScheduleNotificationOnInit(); } PromoResourceService::~PromoResourceService() {} scoped_ptr PromoResourceService::RegisterStateChangedCallback( const base::Closure& closure) { return callback_list_.Add(closure); } void PromoResourceService::ScheduleNotification( const NotificationPromo& notification_promo) { const double promo_start = notification_promo.StartTimeForGroup(); const double promo_end = notification_promo.EndTime(); if (promo_start > 0 && promo_end > 0) { const int64 ms_until_start = static_cast( (base::Time::FromDoubleT(promo_start) - base::Time::Now()) .InMilliseconds()); const int64 ms_until_end = static_cast( (base::Time::FromDoubleT(promo_end) - base::Time::Now()) .InMilliseconds()); if (ms_until_start > 0) { // Schedule the next notification to happen at the start of promotion. PostNotification(ms_until_start); } else if (ms_until_end > 0) { if (ms_until_start <= 0) { // The promo is active. Notify immediately. PostNotification(0); } // Schedule the next notification to happen at the end of promotion. PostNotification(ms_until_end); } else { // The promo (if any) has finished. Notify immediately. PostNotification(0); } } else { // The promo (if any) was apparently cancelled. Notify immediately. PostNotification(0); } } void PromoResourceService::ScheduleNotificationOnInit() { // If the promo start is in the future, set a notification task to // invalidate the NTP cache at the time of the promo start. for (size_t i = 0; i < arraysize(kValidPromoTypes); ++i) { NotificationPromo notification_promo(prefs_); notification_promo.InitFromPrefs(kValidPromoTypes[i]); ScheduleNotification(notification_promo); } } void PromoResourceService::PostNotification(int64 delay_ms) { // Note that this could cause re-issuing a notification every time // we receive an update from a server if something goes wrong. // Given that this couldn't happen more frequently than every // kCacheUpdateDelay milliseconds, we should be fine. // TODO(achuith): This crashes if we post delay_ms = 0 to the message loop. // during startup. if (delay_ms > 0) { base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, base::Bind(&PromoResourceService::PromoResourceStateChange, weak_ptr_factory_.GetWeakPtr()), base::TimeDelta::FromMilliseconds(delay_ms)); } else if (delay_ms == 0) { PromoResourceStateChange(); } } void PromoResourceService::PromoResourceStateChange() { callback_list_.Notify(); } void PromoResourceService::Unpack(const base::DictionaryValue& parsed_json) { for (size_t i = 0; i < arraysize(kValidPromoTypes); ++i) { NotificationPromo notification_promo(prefs_); notification_promo.InitFromJson(parsed_json, kValidPromoTypes[i]); if (notification_promo.new_notification()) ScheduleNotification(notification_promo); } } } // namespace web_resource