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