• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "chrome/browser/notifications/extension_welcome_notification.h"
6 
7 #include "base/guid.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/pref_registry/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"
28 
29 const int ExtensionWelcomeNotification::kRequestedShowTimeDays = 14;
30 
31 namespace {
32 
33 class NotificationCallbacks
34     : public message_center::NotificationDelegate {
35  public:
NotificationCallbacks(Profile * profile,const message_center::NotifierId notifier_id,const std::string & welcome_notification_id,ExtensionWelcomeNotification::Delegate * delegate)36   NotificationCallbacks(
37       Profile* profile,
38       const message_center::NotifierId notifier_id,
39       const std::string& welcome_notification_id,
40       ExtensionWelcomeNotification::Delegate* delegate)
41       : profile_(profile),
42         notifier_id_(notifier_id.type, notifier_id.id),
43         welcome_notification_id_(welcome_notification_id),
44         delegate_(delegate) {
45   }
46 
47   // Overridden from NotificationDelegate:
Display()48   virtual void Display() OVERRIDE {}
Error()49   virtual void Error() OVERRIDE {}
50 
Close(bool by_user)51   virtual void Close(bool by_user) OVERRIDE {
52     if (by_user) {
53       // Setting the preference here may cause the notification erasing
54       // to reenter. Posting a task avoids this issue.
55       delegate_->PostTask(
56           FROM_HERE,
57           base::Bind(&NotificationCallbacks::MarkAsDismissed, this));
58     }
59   }
60 
Click()61   virtual void Click() OVERRIDE {}
ButtonClick(int index)62   virtual void ButtonClick(int index) OVERRIDE {
63     if (index == 0) {
64       OpenNotificationLearnMoreTab();
65     } else if (index == 1) {
66       DisableNotificationProvider();
67       Close(true);
68     } else {
69       NOTREACHED();
70     }
71   }
72 
73  private:
MarkAsDismissed()74   void MarkAsDismissed() {
75     profile_->GetPrefs()->SetBoolean(prefs::kWelcomeNotificationDismissedLocal,
76                                      true);
77   }
78 
OpenNotificationLearnMoreTab()79   void OpenNotificationLearnMoreTab() {
80     chrome::NavigateParams params(
81         profile_,
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(&params);
87   }
88 
DisableNotificationProvider()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(
96         notifier, false);
97   }
98 
~NotificationCallbacks()99   virtual ~NotificationCallbacks() {}
100 
101   Profile* const profile_;
102 
103   const message_center::NotifierId notifier_id_;
104 
105   std::string welcome_notification_id_;
106 
107   // Weak ref owned by ExtensionWelcomeNotification.
108   ExtensionWelcomeNotification::Delegate* const delegate_;
109 
110   DISALLOW_COPY_AND_ASSIGN(NotificationCallbacks);
111 };
112 
113 class DefaultDelegate : public ExtensionWelcomeNotification::Delegate {
114  public:
DefaultDelegate()115   DefaultDelegate() {}
116 
GetMessageCenter()117   virtual message_center::MessageCenter* GetMessageCenter() OVERRIDE {
118     return g_browser_process->message_center();
119   }
120 
GetCurrentTime()121   virtual base::Time GetCurrentTime() OVERRIDE {
122     return base::Time::Now();
123   }
124 
PostTask(const tracked_objects::Location & from_here,const base::Closure & task)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);
129   }
130 
131  private:
132   DISALLOW_COPY_AND_ASSIGN(DefaultDelegate);
133 };
134 
135 }  // namespace
136 
ExtensionWelcomeNotification(const std::string & extension_id,Profile * const profile,ExtensionWelcomeNotification::Delegate * const delegate)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),
142       profile_(profile),
143       delegate_(delegate) {
144   welcome_notification_dismissed_pref_.Init(
145       prefs::kWelcomeNotificationDismissed,
146       profile_->GetPrefs(),
147       base::Bind(
148           &ExtensionWelcomeNotification::OnWelcomeNotificationDismissedChanged,
149           base::Unretained(this)));
150   welcome_notification_dismissed_local_pref_.Init(
151       prefs::kWelcomeNotificationDismissedLocal,
152       profile_->GetPrefs());
153 }
154 
155 // static
Create(const std::string & extension_id,Profile * const profile)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();
160 }
161 
162 // static
Create(const std::string & extension_id,Profile * const profile,Delegate * const delegate)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();
169 }
170 
~ExtensionWelcomeNotification()171 ExtensionWelcomeNotification::~ExtensionWelcomeNotification() {
172   if (delayed_notification_) {
173     delayed_notification_.reset();
174     PrefServiceSyncable::FromProfile(profile_)->RemoveObserver(this);
175   } else {
176     HideWelcomeNotification();
177   }
178 }
179 
OnIsSyncingChanged()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()));
189   }
190 }
191 
ShowWelcomeNotificationIfNecessary(const Notification & notification)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)
203                 ? POP_UP_HIDDEN
204                 : POP_UP_SHOWN;
205         if (pop_up_request == POP_UP_SHOWN) {
206           pref_service->SetBoolean(
207               prefs::kWelcomeNotificationPreviouslyPoppedUp, true);
208         }
209 
210         if (IsWelcomeNotificationExpired()) {
211           ExpireWelcomeNotification();
212         } else {
213           ShowWelcomeNotification(
214               notification.display_source(), pop_up_request);
215         }
216       }
217     } else {
218       delayed_notification_.reset(new Notification(notification));
219       pref_service_syncable->AddObserver(this);
220     }
221   }
222 }
223 
224 // static
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * prefs)225 void ExtensionWelcomeNotification::RegisterProfilePrefs(
226     user_prefs::PrefRegistrySyncable* prefs) {
227   prefs->RegisterBooleanPref(prefs::kWelcomeNotificationDismissed,
228                              false,
229                              user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
230   prefs->RegisterBooleanPref(prefs::kWelcomeNotificationDismissedLocal,
231                              false,
232                              user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
233   prefs->RegisterBooleanPref(prefs::kWelcomeNotificationPreviouslyPoppedUp,
234                              false,
235                              user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
236   prefs->RegisterInt64Pref(prefs::kWelcomeNotificationExpirationTimestamp,
237                            0,
238                            user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
239 }
240 
241 message_center::MessageCenter*
GetMessageCenter() const242 ExtensionWelcomeNotification::GetMessageCenter() const {
243   return delegate_->GetMessageCenter();
244 }
245 
ShowWelcomeNotification(const base::string16 & display_source,const PopUpRequest pop_up_request)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);
257 
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);
262 
263   if (welcome_notification_id_.empty())
264     welcome_notification_id_ = base::GenerateGUID();
265 
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),
275             display_source,
276             notifier_id_,
277             rich_notification_data,
278             new NotificationCallbacks(
279                 profile_, notifier_id_, welcome_notification_id_,
280                 delegate_.get())));
281 
282     if (pop_up_request == POP_UP_HIDDEN)
283       message_center_notification->set_shown_as_popup(true);
284 
285     GetMessageCenter()->AddNotification(message_center_notification.Pass());
286     StartExpirationTimer();
287   }
288 }
289 
HideWelcomeNotification()290 void ExtensionWelcomeNotification::HideWelcomeNotification() {
291   if (!welcome_notification_id_.empty() &&
292       GetMessageCenter()->FindVisibleNotificationById(
293           welcome_notification_id_) != NULL) {
294     GetMessageCenter()->RemoveNotification(welcome_notification_id_, false);
295     StopExpirationTimer();
296   }
297 }
298 
UserHasDismissedWelcomeNotification() const299 bool ExtensionWelcomeNotification::UserHasDismissedWelcomeNotification() const {
300   // This was previously a syncable preference; now it's per-machine.
301   // Only the local pref will be written moving forward, but check for both so
302   // users won't be double-toasted.
303   bool shown_synced = profile_->GetPrefs()->GetBoolean(
304       prefs::kWelcomeNotificationDismissed);
305   bool shown_local = profile_->GetPrefs()->GetBoolean(
306       prefs::kWelcomeNotificationDismissedLocal);
307   return (shown_synced || shown_local);
308 }
309 
OnWelcomeNotificationDismissedChanged()310 void ExtensionWelcomeNotification::OnWelcomeNotificationDismissedChanged() {
311   if (UserHasDismissedWelcomeNotification()) {
312     HideWelcomeNotification();
313   }
314 }
315 
StartExpirationTimer()316 void ExtensionWelcomeNotification::StartExpirationTimer() {
317   if (!expiration_timer_ && !IsWelcomeNotificationExpired()) {
318     base::Time expiration_timestamp = GetExpirationTimestamp();
319     if (expiration_timestamp.is_null()) {
320       SetExpirationTimestampFromNow();
321       expiration_timestamp = GetExpirationTimestamp();
322       DCHECK(!expiration_timestamp.is_null());
323     }
324     expiration_timer_.reset(
325         new base::OneShotTimer<ExtensionWelcomeNotification>());
326     expiration_timer_->Start(
327         FROM_HERE,
328         expiration_timestamp - delegate_->GetCurrentTime(),
329         this,
330         &ExtensionWelcomeNotification::ExpireWelcomeNotification);
331   }
332 }
333 
StopExpirationTimer()334 void ExtensionWelcomeNotification::StopExpirationTimer() {
335   if (expiration_timer_) {
336     expiration_timer_->Stop();
337     expiration_timer_.reset();
338   }
339 }
340 
ExpireWelcomeNotification()341 void ExtensionWelcomeNotification::ExpireWelcomeNotification() {
342   DCHECK(IsWelcomeNotificationExpired());
343   profile_->GetPrefs()->SetBoolean(
344       prefs::kWelcomeNotificationDismissedLocal, true);
345   HideWelcomeNotification();
346 }
347 
GetExpirationTimestamp() const348 base::Time ExtensionWelcomeNotification::GetExpirationTimestamp() const {
349   PrefService* const pref_service = profile_->GetPrefs();
350   const int64 expiration_timestamp =
351       pref_service->GetInt64(prefs::kWelcomeNotificationExpirationTimestamp);
352   return (expiration_timestamp == 0)
353       ? base::Time()
354       : base::Time::FromInternalValue(expiration_timestamp);
355 }
356 
SetExpirationTimestampFromNow()357 void ExtensionWelcomeNotification::SetExpirationTimestampFromNow() {
358   PrefService* const pref_service = profile_->GetPrefs();
359   pref_service->SetInt64(
360       prefs::kWelcomeNotificationExpirationTimestamp,
361       (delegate_->GetCurrentTime() +
362           base::TimeDelta::FromDays(kRequestedShowTimeDays)).ToInternalValue());
363 }
364 
IsWelcomeNotificationExpired() const365 bool ExtensionWelcomeNotification::IsWelcomeNotificationExpired() const {
366   const base::Time expiration_timestamp = GetExpirationTimestamp();
367   return !expiration_timestamp.is_null() &&
368          (expiration_timestamp <= delegate_->GetCurrentTime());
369 }
370