Home | History | Annotate | Download | only in notifications
      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 "chrome/grit/generated_resources.h"
     20 #include "components/pref_registry/pref_registry_syncable.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 const char ExtensionWelcomeNotification::kChromeNowExtensionID[] =
     31     "pafkbggdmjlpgkdkcbjmhmfcdpncadgh";
     32 
     33 namespace {
     34 
     35 class NotificationCallbacks
     36     : public message_center::NotificationDelegate {
     37  public:
     38   NotificationCallbacks(
     39       Profile* profile,
     40       const message_center::NotifierId notifier_id,
     41       const std::string& welcome_notification_id,
     42       ExtensionWelcomeNotification::Delegate* delegate)
     43       : profile_(profile),
     44         notifier_id_(notifier_id.type, notifier_id.id),
     45         welcome_notification_id_(welcome_notification_id),
     46         delegate_(delegate) {
     47   }
     48 
     49   // Overridden from NotificationDelegate:
     50   virtual void Display() OVERRIDE {}
     51   virtual void Error() OVERRIDE {}
     52 
     53   virtual void Close(bool by_user) OVERRIDE {
     54     if (by_user) {
     55       // Setting the preference here may cause the notification erasing
     56       // to reenter. Posting a task avoids this issue.
     57       delegate_->PostTask(
     58           FROM_HERE,
     59           base::Bind(&NotificationCallbacks::MarkAsDismissed, this));
     60     }
     61   }
     62 
     63   virtual void Click() OVERRIDE {}
     64   virtual void ButtonClick(int index) OVERRIDE {
     65     if (index == 0) {
     66       OpenNotificationLearnMoreTab();
     67     } else if (index == 1) {
     68       DisableNotificationProvider();
     69       Close(true);
     70     } else {
     71       NOTREACHED();
     72     }
     73   }
     74 
     75  private:
     76   void MarkAsDismissed() {
     77     profile_->GetPrefs()->SetBoolean(prefs::kWelcomeNotificationDismissedLocal,
     78                                      true);
     79   }
     80 
     81   void OpenNotificationLearnMoreTab() {
     82     chrome::NavigateParams params(
     83         profile_,
     84         GURL(chrome::kNotificationWelcomeLearnMoreURL),
     85         ui::PAGE_TRANSITION_LINK);
     86     params.disposition = NEW_FOREGROUND_TAB;
     87     params.window_action = chrome::NavigateParams::SHOW_WINDOW;
     88     chrome::Navigate(&params);
     89   }
     90 
     91   void DisableNotificationProvider() {
     92     message_center::Notifier notifier(notifier_id_, base::string16(), true);
     93     message_center::MessageCenter* message_center =
     94         delegate_->GetMessageCenter();
     95     message_center->DisableNotificationsByNotifier(notifier_id_);
     96     message_center->RemoveNotification(welcome_notification_id_, false);
     97     message_center->GetNotifierSettingsProvider()->SetNotifierEnabled(
     98         notifier, false);
     99   }
    100 
    101   virtual ~NotificationCallbacks() {}
    102 
    103   Profile* const profile_;
    104 
    105   const message_center::NotifierId notifier_id_;
    106 
    107   std::string welcome_notification_id_;
    108 
    109   // Weak ref owned by ExtensionWelcomeNotification.
    110   ExtensionWelcomeNotification::Delegate* const delegate_;
    111 
    112   DISALLOW_COPY_AND_ASSIGN(NotificationCallbacks);
    113 };
    114 
    115 class DefaultDelegate : public ExtensionWelcomeNotification::Delegate {
    116  public:
    117   DefaultDelegate() {}
    118 
    119   virtual message_center::MessageCenter* GetMessageCenter() OVERRIDE {
    120     return g_browser_process->message_center();
    121   }
    122 
    123   virtual base::Time GetCurrentTime() OVERRIDE {
    124     return base::Time::Now();
    125   }
    126 
    127   virtual void PostTask(
    128       const tracked_objects::Location& from_here,
    129       const base::Closure& task) OVERRIDE {
    130     base::MessageLoop::current()->PostTask(from_here, task);
    131   }
    132 
    133  private:
    134   DISALLOW_COPY_AND_ASSIGN(DefaultDelegate);
    135 };
    136 
    137 }  // namespace
    138 
    139 ExtensionWelcomeNotification::ExtensionWelcomeNotification(
    140     Profile* const profile,
    141     ExtensionWelcomeNotification::Delegate* const delegate)
    142     : notifier_id_(message_center::NotifierId::APPLICATION,
    143           kChromeNowExtensionID),
    144       profile_(profile),
    145       delegate_(delegate) {
    146   welcome_notification_dismissed_pref_.Init(
    147       prefs::kWelcomeNotificationDismissed,
    148       profile_->GetPrefs(),
    149       base::Bind(
    150           &ExtensionWelcomeNotification::OnWelcomeNotificationDismissedChanged,
    151           base::Unretained(this)));
    152   welcome_notification_dismissed_local_pref_.Init(
    153       prefs::kWelcomeNotificationDismissedLocal,
    154       profile_->GetPrefs());
    155 }
    156 
    157 // static
    158 ExtensionWelcomeNotification* ExtensionWelcomeNotification::Create(
    159     Profile* const profile) {
    160   return Create(profile, new DefaultDelegate());
    161 }
    162 
    163 // static
    164 ExtensionWelcomeNotification* ExtensionWelcomeNotification::Create(
    165     Profile* const profile, Delegate* const delegate) {
    166   return new ExtensionWelcomeNotification(profile, delegate);
    167 }
    168 
    169 ExtensionWelcomeNotification::~ExtensionWelcomeNotification() {
    170   if (delayed_notification_) {
    171     delayed_notification_.reset();
    172     PrefServiceSyncable::FromProfile(profile_)->RemoveObserver(this);
    173   } else {
    174     HideWelcomeNotification();
    175   }
    176 }
    177 
    178 void ExtensionWelcomeNotification::OnIsSyncingChanged() {
    179   DCHECK(delayed_notification_);
    180   PrefServiceSyncable* const pref_service_syncable =
    181       PrefServiceSyncable::FromProfile(profile_);
    182   if (pref_service_syncable->IsSyncing()) {
    183     pref_service_syncable->RemoveObserver(this);
    184     scoped_ptr<Notification> previous_notification(
    185         delayed_notification_.release());
    186     ShowWelcomeNotificationIfNecessary(*(previous_notification.get()));
    187   }
    188 }
    189 
    190 void ExtensionWelcomeNotification::ShowWelcomeNotificationIfNecessary(
    191     const Notification& notification) {
    192   if ((notification.notifier_id() == notifier_id_) && !delayed_notification_) {
    193     PrefServiceSyncable* const pref_service_syncable =
    194         PrefServiceSyncable::FromProfile(profile_);
    195     if (pref_service_syncable->IsSyncing()) {
    196       PrefService* const pref_service = profile_->GetPrefs();
    197       if (!UserHasDismissedWelcomeNotification()) {
    198         const PopUpRequest pop_up_request =
    199             pref_service->GetBoolean(
    200                 prefs::kWelcomeNotificationPreviouslyPoppedUp)
    201                 ? POP_UP_HIDDEN
    202                 : POP_UP_SHOWN;
    203         if (pop_up_request == POP_UP_SHOWN) {
    204           pref_service->SetBoolean(
    205               prefs::kWelcomeNotificationPreviouslyPoppedUp, true);
    206         }
    207 
    208         if (IsWelcomeNotificationExpired()) {
    209           ExpireWelcomeNotification();
    210         } else {
    211           ShowWelcomeNotification(
    212               notification.display_source(), pop_up_request);
    213         }
    214       }
    215     } else {
    216       delayed_notification_.reset(new Notification(notification));
    217       pref_service_syncable->AddObserver(this);
    218     }
    219   }
    220 }
    221 
    222 // static
    223 void ExtensionWelcomeNotification::RegisterProfilePrefs(
    224     user_prefs::PrefRegistrySyncable* prefs) {
    225   prefs->RegisterBooleanPref(prefs::kWelcomeNotificationDismissed,
    226                              false,
    227                              user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
    228   prefs->RegisterBooleanPref(prefs::kWelcomeNotificationDismissedLocal,
    229                              false,
    230                              user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
    231   prefs->RegisterBooleanPref(prefs::kWelcomeNotificationPreviouslyPoppedUp,
    232                              false,
    233                              user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
    234   prefs->RegisterInt64Pref(prefs::kWelcomeNotificationExpirationTimestamp,
    235                            0,
    236                            user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
    237 }
    238 
    239 message_center::MessageCenter*
    240 ExtensionWelcomeNotification::GetMessageCenter() const {
    241   return delegate_->GetMessageCenter();
    242 }
    243 
    244 void ExtensionWelcomeNotification::ShowWelcomeNotification(
    245     const base::string16& display_source,
    246     const PopUpRequest pop_up_request) {
    247   message_center::ButtonInfo learn_more(
    248       l10n_util::GetStringUTF16(IDS_NOTIFICATION_WELCOME_BUTTON_LEARN_MORE));
    249   learn_more.icon = ui::ResourceBundle::GetSharedInstance().GetImageNamed(
    250       IDR_NOTIFICATION_WELCOME_LEARN_MORE);
    251   message_center::ButtonInfo disable(
    252       l10n_util::GetStringUTF16(IDS_NOTIFIER_WELCOME_BUTTON));
    253   disable.icon = ui::ResourceBundle::GetSharedInstance().GetImageNamed(
    254       IDR_NOTIFIER_BLOCK_BUTTON);
    255 
    256   message_center::RichNotificationData rich_notification_data;
    257   rich_notification_data.priority = 2;
    258   rich_notification_data.buttons.push_back(learn_more);
    259   rich_notification_data.buttons.push_back(disable);
    260 
    261   if (welcome_notification_id_.empty())
    262     welcome_notification_id_ = base::GenerateGUID();
    263 
    264   if (!welcome_notification_id_.empty()) {
    265     scoped_ptr<message_center::Notification> message_center_notification(
    266         new message_center::Notification(
    267             message_center::NOTIFICATION_TYPE_BASE_FORMAT,
    268             welcome_notification_id_,
    269             l10n_util::GetStringUTF16(IDS_NOTIFICATION_WELCOME_TITLE),
    270             l10n_util::GetStringUTF16(IDS_NOTIFICATION_WELCOME_BODY),
    271             ui::ResourceBundle::GetSharedInstance().GetImageNamed(
    272                 IDR_NOTIFICATION_WELCOME_ICON),
    273             display_source,
    274             notifier_id_,
    275             rich_notification_data,
    276             new NotificationCallbacks(
    277                 profile_, notifier_id_, welcome_notification_id_,
    278                 delegate_.get())));
    279 
    280     if (pop_up_request == POP_UP_HIDDEN)
    281       message_center_notification->set_shown_as_popup(true);
    282 
    283     GetMessageCenter()->AddNotification(message_center_notification.Pass());
    284     StartExpirationTimer();
    285   }
    286 }
    287 
    288 void ExtensionWelcomeNotification::HideWelcomeNotification() {
    289   if (!welcome_notification_id_.empty() &&
    290       GetMessageCenter()->FindVisibleNotificationById(
    291           welcome_notification_id_) != NULL) {
    292     GetMessageCenter()->RemoveNotification(welcome_notification_id_, false);
    293     StopExpirationTimer();
    294   }
    295 }
    296 
    297 bool ExtensionWelcomeNotification::UserHasDismissedWelcomeNotification() const {
    298   // This was previously a syncable preference; now it's per-machine.
    299   // Only the local pref will be written moving forward, but check for both so
    300   // users won't be double-toasted.
    301   bool shown_synced = profile_->GetPrefs()->GetBoolean(
    302       prefs::kWelcomeNotificationDismissed);
    303   bool shown_local = profile_->GetPrefs()->GetBoolean(
    304       prefs::kWelcomeNotificationDismissedLocal);
    305   return (shown_synced || shown_local);
    306 }
    307 
    308 void ExtensionWelcomeNotification::OnWelcomeNotificationDismissedChanged() {
    309   if (UserHasDismissedWelcomeNotification()) {
    310     HideWelcomeNotification();
    311   }
    312 }
    313 
    314 void ExtensionWelcomeNotification::StartExpirationTimer() {
    315   if (!expiration_timer_ && !IsWelcomeNotificationExpired()) {
    316     base::Time expiration_timestamp = GetExpirationTimestamp();
    317     if (expiration_timestamp.is_null()) {
    318       SetExpirationTimestampFromNow();
    319       expiration_timestamp = GetExpirationTimestamp();
    320       DCHECK(!expiration_timestamp.is_null());
    321     }
    322     expiration_timer_.reset(
    323         new base::OneShotTimer<ExtensionWelcomeNotification>());
    324     expiration_timer_->Start(
    325         FROM_HERE,
    326         expiration_timestamp - delegate_->GetCurrentTime(),
    327         this,
    328         &ExtensionWelcomeNotification::ExpireWelcomeNotification);
    329   }
    330 }
    331 
    332 void ExtensionWelcomeNotification::StopExpirationTimer() {
    333   if (expiration_timer_) {
    334     expiration_timer_->Stop();
    335     expiration_timer_.reset();
    336   }
    337 }
    338 
    339 void ExtensionWelcomeNotification::ExpireWelcomeNotification() {
    340   DCHECK(IsWelcomeNotificationExpired());
    341   profile_->GetPrefs()->SetBoolean(
    342       prefs::kWelcomeNotificationDismissedLocal, true);
    343   HideWelcomeNotification();
    344 }
    345 
    346 base::Time ExtensionWelcomeNotification::GetExpirationTimestamp() const {
    347   PrefService* const pref_service = profile_->GetPrefs();
    348   const int64 expiration_timestamp =
    349       pref_service->GetInt64(prefs::kWelcomeNotificationExpirationTimestamp);
    350   return (expiration_timestamp == 0)
    351       ? base::Time()
    352       : base::Time::FromInternalValue(expiration_timestamp);
    353 }
    354 
    355 void ExtensionWelcomeNotification::SetExpirationTimestampFromNow() {
    356   PrefService* const pref_service = profile_->GetPrefs();
    357   pref_service->SetInt64(
    358       prefs::kWelcomeNotificationExpirationTimestamp,
    359       (delegate_->GetCurrentTime() +
    360           base::TimeDelta::FromDays(kRequestedShowTimeDays)).ToInternalValue());
    361 }
    362 
    363 bool ExtensionWelcomeNotification::IsWelcomeNotificationExpired() const {
    364   const base::Time expiration_timestamp = GetExpirationTimestamp();
    365   return !expiration_timestamp.is_null() &&
    366          (expiration_timestamp <= delegate_->GetCurrentTime());
    367 }
    368