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 29const int ExtensionWelcomeNotification::kRequestedShowTimeDays = 14; 30const char ExtensionWelcomeNotification::kChromeNowExtensionID[] = 31 "pafkbggdmjlpgkdkcbjmhmfcdpncadgh"; 32 33namespace { 34 35class 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 115class 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 139ExtensionWelcomeNotification::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 158ExtensionWelcomeNotification* ExtensionWelcomeNotification::Create( 159 Profile* const profile) { 160 return Create(profile, new DefaultDelegate()); 161} 162 163// static 164ExtensionWelcomeNotification* ExtensionWelcomeNotification::Create( 165 Profile* const profile, Delegate* const delegate) { 166 return new ExtensionWelcomeNotification(profile, delegate); 167} 168 169ExtensionWelcomeNotification::~ExtensionWelcomeNotification() { 170 if (delayed_notification_) { 171 delayed_notification_.reset(); 172 PrefServiceSyncable::FromProfile(profile_)->RemoveObserver(this); 173 } else { 174 HideWelcomeNotification(); 175 } 176} 177 178void 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 190void 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 223void 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 239message_center::MessageCenter* 240ExtensionWelcomeNotification::GetMessageCenter() const { 241 return delegate_->GetMessageCenter(); 242} 243 244void 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 288void 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 297bool 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 308void ExtensionWelcomeNotification::OnWelcomeNotificationDismissedChanged() { 309 if (UserHasDismissedWelcomeNotification()) { 310 HideWelcomeNotification(); 311 } 312} 313 314void 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 332void ExtensionWelcomeNotification::StopExpirationTimer() { 333 if (expiration_timer_) { 334 expiration_timer_->Stop(); 335 expiration_timer_.reset(); 336 } 337} 338 339void ExtensionWelcomeNotification::ExpireWelcomeNotification() { 340 DCHECK(IsWelcomeNotificationExpired()); 341 profile_->GetPrefs()->SetBoolean( 342 prefs::kWelcomeNotificationDismissedLocal, true); 343 HideWelcomeNotification(); 344} 345 346base::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 355void 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 363bool ExtensionWelcomeNotification::IsWelcomeNotificationExpired() const { 364 const base::Time expiration_timestamp = GetExpirationTimestamp(); 365 return !expiration_timestamp.is_null() && 366 (expiration_timestamp <= delegate_->GetCurrentTime()); 367} 368