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(¶ms); 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