1 // Copyright 2014 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/notifications/extension_welcome_notification.h"
8 #include "base/lazy_instance.h"
9 #include "base/message_loop/message_loop.h"
10 #include "base/prefs/pref_service.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/browser_process.h"
13 #include "chrome/browser/notifications/notification.h"
14 #include "chrome/browser/prefs/pref_service_syncable.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/ui/browser_navigator.h"
17 #include "chrome/common/pref_names.h"
18 #include "chrome/common/url_constants.h"
19 #include "components/user_prefs/pref_registry_syncable.h"
20 #include "grit/generated_resources.h"
21 #include "grit/theme_resources.h"
22 #include "ui/base/l10n/l10n_util.h"
23 #include "ui/base/resource/resource_bundle.h"
24 #include "ui/message_center/message_center.h"
25 #include "ui/message_center/notification.h"
26 #include "ui/message_center/notification_delegate.h"
27 #include "ui/message_center/notification_types.h"
29 const int ExtensionWelcomeNotification::kRequestedShowTimeDays
= 14;
33 class NotificationCallbacks
34 : public message_center::NotificationDelegate
{
36 NotificationCallbacks(
38 const message_center::NotifierId notifier_id
,
39 const std::string
& welcome_notification_id
,
40 ExtensionWelcomeNotification::Delegate
* delegate
)
42 notifier_id_(notifier_id
.type
, notifier_id
.id
),
43 welcome_notification_id_(welcome_notification_id
),
47 // Overridden from NotificationDelegate:
48 virtual void Display() OVERRIDE
{}
49 virtual void Error() OVERRIDE
{}
51 virtual void Close(bool by_user
) OVERRIDE
{
53 // Setting the preference here may cause the notification erasing
54 // to reenter. Posting a task avoids this issue.
57 base::Bind(&NotificationCallbacks::MarkAsDismissed
, this));
61 virtual void Click() OVERRIDE
{}
62 virtual void ButtonClick(int index
) OVERRIDE
{
64 OpenNotificationLearnMoreTab();
65 } else if (index
== 1) {
66 DisableNotificationProvider();
74 void MarkAsDismissed() {
75 profile_
->GetPrefs()->SetBoolean(prefs::kWelcomeNotificationDismissedLocal
,
79 void OpenNotificationLearnMoreTab() {
80 chrome::NavigateParams
params(
82 GURL(chrome::kNotificationWelcomeLearnMoreURL
),
83 content::PAGE_TRANSITION_LINK
);
84 params
.disposition
= NEW_FOREGROUND_TAB
;
85 params
.window_action
= chrome::NavigateParams::SHOW_WINDOW
;
86 chrome::Navigate(¶ms
);
89 void DisableNotificationProvider() {
90 message_center::Notifier
notifier(notifier_id_
, base::string16(), true);
91 message_center::MessageCenter
* message_center
=
92 delegate_
->GetMessageCenter();
93 message_center
->DisableNotificationsByNotifier(notifier_id_
);
94 message_center
->RemoveNotification(welcome_notification_id_
, false);
95 message_center
->GetNotifierSettingsProvider()->SetNotifierEnabled(
99 virtual ~NotificationCallbacks() {}
101 Profile
* const profile_
;
103 const message_center::NotifierId notifier_id_
;
105 std::string welcome_notification_id_
;
107 // Weak ref owned by ExtensionWelcomeNotification.
108 ExtensionWelcomeNotification::Delegate
* const delegate_
;
110 DISALLOW_COPY_AND_ASSIGN(NotificationCallbacks
);
113 class DefaultDelegate
: public ExtensionWelcomeNotification::Delegate
{
117 virtual message_center::MessageCenter
* GetMessageCenter() OVERRIDE
{
118 return g_browser_process
->message_center();
121 virtual base::Time
GetCurrentTime() OVERRIDE
{
122 return base::Time::Now();
125 virtual void PostTask(
126 const tracked_objects::Location
& from_here
,
127 const base::Closure
& task
) OVERRIDE
{
128 base::MessageLoop::current()->PostTask(from_here
, task
);
132 DISALLOW_COPY_AND_ASSIGN(DefaultDelegate
);
137 ExtensionWelcomeNotification::ExtensionWelcomeNotification(
138 const std::string
& extension_id
,
139 Profile
* const profile
,
140 ExtensionWelcomeNotification::Delegate
* const delegate
)
141 : notifier_id_(message_center::NotifierId::APPLICATION
, extension_id
),
143 delegate_(delegate
) {
144 welcome_notification_dismissed_pref_
.Init(
145 prefs::kWelcomeNotificationDismissed
,
146 profile_
->GetPrefs(),
148 &ExtensionWelcomeNotification::OnWelcomeNotificationDismissedChanged
,
149 base::Unretained(this)));
150 welcome_notification_dismissed_local_pref_
.Init(
151 prefs::kWelcomeNotificationDismissedLocal
,
152 profile_
->GetPrefs());
156 scoped_ptr
<ExtensionWelcomeNotification
> ExtensionWelcomeNotification::Create(
157 const std::string
& extension_id
,
158 Profile
* const profile
) {
159 return Create(extension_id
, profile
, new DefaultDelegate()).Pass();
163 scoped_ptr
<ExtensionWelcomeNotification
> ExtensionWelcomeNotification::Create(
164 const std::string
& extension_id
,
165 Profile
* const profile
,
166 Delegate
* const delegate
) {
167 return scoped_ptr
<ExtensionWelcomeNotification
>(
168 new ExtensionWelcomeNotification(extension_id
, profile
, delegate
)).Pass();
171 ExtensionWelcomeNotification::~ExtensionWelcomeNotification() {
172 if (delayed_notification_
) {
173 delayed_notification_
.reset();
174 PrefServiceSyncable::FromProfile(profile_
)->RemoveObserver(this);
176 HideWelcomeNotification();
180 void ExtensionWelcomeNotification::OnIsSyncingChanged() {
181 DCHECK(delayed_notification_
);
182 PrefServiceSyncable
* const pref_service_syncable
=
183 PrefServiceSyncable::FromProfile(profile_
);
184 if (pref_service_syncable
->IsSyncing()) {
185 pref_service_syncable
->RemoveObserver(this);
186 scoped_ptr
<Notification
> previous_notification(
187 delayed_notification_
.release());
188 ShowWelcomeNotificationIfNecessary(*(previous_notification
.get()));
192 void ExtensionWelcomeNotification::ShowWelcomeNotificationIfNecessary(
193 const Notification
& notification
) {
194 if ((notification
.notifier_id() == notifier_id_
) && !delayed_notification_
) {
195 PrefServiceSyncable
* const pref_service_syncable
=
196 PrefServiceSyncable::FromProfile(profile_
);
197 if (pref_service_syncable
->IsSyncing()) {
198 PrefService
* const pref_service
= profile_
->GetPrefs();
199 if (!UserHasDismissedWelcomeNotification()) {
200 const PopUpRequest pop_up_request
=
201 pref_service
->GetBoolean(
202 prefs::kWelcomeNotificationPreviouslyPoppedUp
)
205 if (pop_up_request
== POP_UP_SHOWN
) {
206 pref_service
->SetBoolean(
207 prefs::kWelcomeNotificationPreviouslyPoppedUp
, true);
210 if (IsWelcomeNotificationExpired()) {
211 ExpireWelcomeNotification();
213 ShowWelcomeNotification(
214 notification
.display_source(), pop_up_request
);
218 delayed_notification_
.reset(new Notification(notification
));
219 pref_service_syncable
->AddObserver(this);
225 void ExtensionWelcomeNotification::RegisterProfilePrefs(
226 user_prefs::PrefRegistrySyncable
* prefs
) {
227 prefs
->RegisterBooleanPref(prefs::kWelcomeNotificationDismissed
,
229 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF
);
230 prefs
->RegisterBooleanPref(prefs::kWelcomeNotificationDismissedLocal
,
232 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
233 prefs
->RegisterBooleanPref(prefs::kWelcomeNotificationPreviouslyPoppedUp
,
235 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
236 prefs
->RegisterInt64Pref(prefs::kWelcomeNotificationExpirationTimestamp
,
238 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
241 message_center::MessageCenter
*
242 ExtensionWelcomeNotification::GetMessageCenter() const {
243 return delegate_
->GetMessageCenter();
246 void ExtensionWelcomeNotification::ShowWelcomeNotification(
247 const base::string16
& display_source
,
248 const PopUpRequest pop_up_request
) {
249 message_center::ButtonInfo
learn_more(
250 l10n_util::GetStringUTF16(IDS_NOTIFICATION_WELCOME_BUTTON_LEARN_MORE
));
251 learn_more
.icon
= ui::ResourceBundle::GetSharedInstance().GetImageNamed(
252 IDR_NOTIFICATION_WELCOME_LEARN_MORE
);
253 message_center::ButtonInfo
disable(
254 l10n_util::GetStringUTF16(IDS_NOTIFIER_WELCOME_BUTTON
));
255 disable
.icon
= ui::ResourceBundle::GetSharedInstance().GetImageNamed(
256 IDR_NOTIFIER_BLOCK_BUTTON
);
258 message_center::RichNotificationData rich_notification_data
;
259 rich_notification_data
.priority
= 2;
260 rich_notification_data
.buttons
.push_back(learn_more
);
261 rich_notification_data
.buttons
.push_back(disable
);
263 if (welcome_notification_id_
.empty())
264 welcome_notification_id_
= base::GenerateGUID();
266 if (!welcome_notification_id_
.empty()) {
267 scoped_ptr
<message_center::Notification
> message_center_notification(
268 new message_center::Notification(
269 message_center::NOTIFICATION_TYPE_BASE_FORMAT
,
270 welcome_notification_id_
,
271 l10n_util::GetStringUTF16(IDS_NOTIFICATION_WELCOME_TITLE
),
272 l10n_util::GetStringUTF16(IDS_NOTIFICATION_WELCOME_BODY
),
273 ui::ResourceBundle::GetSharedInstance().GetImageNamed(
274 IDR_NOTIFICATION_WELCOME_ICON
),
277 rich_notification_data
,
278 new NotificationCallbacks(
279 profile_
, notifier_id_
, welcome_notification_id_
,
282 if (pop_up_request
== POP_UP_HIDDEN
)
283 message_center_notification
->set_shown_as_popup(true);
285 GetMessageCenter()->AddNotification(message_center_notification
.Pass());
286 StartExpirationTimer();
290 void ExtensionWelcomeNotification::HideWelcomeNotification() {
291 if (!welcome_notification_id_
.empty() &&
292 GetMessageCenter()->HasNotification(welcome_notification_id_
)) {
293 GetMessageCenter()->RemoveNotification(welcome_notification_id_
, false);
294 StopExpirationTimer();
298 bool ExtensionWelcomeNotification::UserHasDismissedWelcomeNotification() const {
299 // This was previously a syncable preference; now it's per-machine.
300 // Only the local pref will be written moving forward, but check for both so
301 // users won't be double-toasted.
302 bool shown_synced
= profile_
->GetPrefs()->GetBoolean(
303 prefs::kWelcomeNotificationDismissed
);
304 bool shown_local
= profile_
->GetPrefs()->GetBoolean(
305 prefs::kWelcomeNotificationDismissedLocal
);
306 return (shown_synced
|| shown_local
);
309 void ExtensionWelcomeNotification::OnWelcomeNotificationDismissedChanged() {
310 if (UserHasDismissedWelcomeNotification()) {
311 HideWelcomeNotification();
315 void ExtensionWelcomeNotification::StartExpirationTimer() {
316 if (!expiration_timer_
&& !IsWelcomeNotificationExpired()) {
317 base::Time expiration_timestamp
= GetExpirationTimestamp();
318 if (expiration_timestamp
.is_null()) {
319 SetExpirationTimestampFromNow();
320 expiration_timestamp
= GetExpirationTimestamp();
321 DCHECK(!expiration_timestamp
.is_null());
323 expiration_timer_
.reset(
324 new base::OneShotTimer
<ExtensionWelcomeNotification
>());
325 expiration_timer_
->Start(
327 expiration_timestamp
- delegate_
->GetCurrentTime(),
329 &ExtensionWelcomeNotification::ExpireWelcomeNotification
);
333 void ExtensionWelcomeNotification::StopExpirationTimer() {
334 if (expiration_timer_
) {
335 expiration_timer_
->Stop();
336 expiration_timer_
.reset();
340 void ExtensionWelcomeNotification::ExpireWelcomeNotification() {
341 DCHECK(IsWelcomeNotificationExpired());
342 profile_
->GetPrefs()->SetBoolean(
343 prefs::kWelcomeNotificationDismissedLocal
, true);
344 HideWelcomeNotification();
347 base::Time
ExtensionWelcomeNotification::GetExpirationTimestamp() const {
348 PrefService
* const pref_service
= profile_
->GetPrefs();
349 const int64 expiration_timestamp
=
350 pref_service
->GetInt64(prefs::kWelcomeNotificationExpirationTimestamp
);
351 return (expiration_timestamp
== 0)
353 : base::Time::FromInternalValue(expiration_timestamp
);
356 void ExtensionWelcomeNotification::SetExpirationTimestampFromNow() {
357 PrefService
* const pref_service
= profile_
->GetPrefs();
358 pref_service
->SetInt64(
359 prefs::kWelcomeNotificationExpirationTimestamp
,
360 (delegate_
->GetCurrentTime() +
361 base::TimeDelta::FromDays(kRequestedShowTimeDays
)).ToInternalValue());
364 bool ExtensionWelcomeNotification::IsWelcomeNotificationExpired() const {
365 const base::Time expiration_timestamp
= GetExpirationTimestamp();
366 return !expiration_timestamp
.is_null() &&
367 (expiration_timestamp
<= delegate_
->GetCurrentTime());