blob: c061dd8a877ccdc068d7a9a5524adf925b0ca540 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/notifications/extension_welcome_notification.h"
#include "base/guid.h"
#include "base/lazy_instance.h"
#include "base/message_loop/message_loop.h"
#include "base/prefs/pref_service.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/notifications/notification.h"
#include "chrome/browser/prefs/pref_service_syncable.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "grit/theme_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/notification.h"
#include "ui/message_center/notification_delegate.h"
#include "ui/message_center/notification_types.h"
const int ExtensionWelcomeNotification::kRequestedShowTimeDays = 14;
const char ExtensionWelcomeNotification::kChromeNowExtensionID[] =
"pafkbggdmjlpgkdkcbjmhmfcdpncadgh";
namespace {
class NotificationCallbacks
: public message_center::NotificationDelegate {
public:
NotificationCallbacks(
Profile* profile,
const message_center::NotifierId notifier_id,
const std::string& welcome_notification_id,
ExtensionWelcomeNotification::Delegate* delegate)
: profile_(profile),
notifier_id_(notifier_id.type, notifier_id.id),
welcome_notification_id_(welcome_notification_id),
delegate_(delegate) {
}
// Overridden from NotificationDelegate:
void Display() override {}
void Error() override {}
void Close(bool by_user) override {
if (by_user) {
// Setting the preference here may cause the notification erasing
// to reenter. Posting a task avoids this issue.
delegate_->PostTask(
FROM_HERE,
base::Bind(&NotificationCallbacks::MarkAsDismissed, this));
}
}
void Click() override {}
void ButtonClick(int index) override {
if (index == 0) {
OpenNotificationLearnMoreTab();
} else if (index == 1) {
DisableNotificationProvider();
Close(true);
} else {
NOTREACHED();
}
}
private:
void MarkAsDismissed() {
profile_->GetPrefs()->SetBoolean(prefs::kWelcomeNotificationDismissedLocal,
true);
}
void OpenNotificationLearnMoreTab() {
chrome::NavigateParams params(
profile_,
GURL(chrome::kNotificationWelcomeLearnMoreURL),
ui::PAGE_TRANSITION_LINK);
params.disposition = NEW_FOREGROUND_TAB;
params.window_action = chrome::NavigateParams::SHOW_WINDOW;
chrome::Navigate(&params);
}
void DisableNotificationProvider() {
message_center::Notifier notifier(notifier_id_, base::string16(), true);
message_center::MessageCenter* message_center =
delegate_->GetMessageCenter();
message_center->DisableNotificationsByNotifier(notifier_id_);
message_center->RemoveNotification(welcome_notification_id_, false);
message_center->GetNotifierSettingsProvider()->SetNotifierEnabled(
notifier, false);
}
~NotificationCallbacks() override {}
Profile* const profile_;
const message_center::NotifierId notifier_id_;
std::string welcome_notification_id_;
// Weak ref owned by ExtensionWelcomeNotification.
ExtensionWelcomeNotification::Delegate* const delegate_;
DISALLOW_COPY_AND_ASSIGN(NotificationCallbacks);
};
class DefaultDelegate : public ExtensionWelcomeNotification::Delegate {
public:
DefaultDelegate() {}
message_center::MessageCenter* GetMessageCenter() override {
return g_browser_process->message_center();
}
base::Time GetCurrentTime() override { return base::Time::Now(); }
void PostTask(const tracked_objects::Location& from_here,
const base::Closure& task) override {
base::MessageLoop::current()->PostTask(from_here, task);
}
private:
DISALLOW_COPY_AND_ASSIGN(DefaultDelegate);
};
} // namespace
ExtensionWelcomeNotification::ExtensionWelcomeNotification(
Profile* const profile,
ExtensionWelcomeNotification::Delegate* const delegate)
: notifier_id_(message_center::NotifierId::APPLICATION,
kChromeNowExtensionID),
profile_(profile),
delegate_(delegate) {
welcome_notification_dismissed_pref_.Init(
prefs::kWelcomeNotificationDismissed,
profile_->GetPrefs(),
base::Bind(
&ExtensionWelcomeNotification::OnWelcomeNotificationDismissedChanged,
base::Unretained(this)));
welcome_notification_dismissed_local_pref_.Init(
prefs::kWelcomeNotificationDismissedLocal,
profile_->GetPrefs());
}
// static
ExtensionWelcomeNotification* ExtensionWelcomeNotification::Create(
Profile* const profile) {
return Create(profile, new DefaultDelegate());
}
// static
ExtensionWelcomeNotification* ExtensionWelcomeNotification::Create(
Profile* const profile, Delegate* const delegate) {
return new ExtensionWelcomeNotification(profile, delegate);
}
ExtensionWelcomeNotification::~ExtensionWelcomeNotification() {
if (delayed_notification_) {
delayed_notification_.reset();
PrefServiceSyncable::FromProfile(profile_)->RemoveObserver(this);
} else {
HideWelcomeNotification();
}
}
void ExtensionWelcomeNotification::OnIsSyncingChanged() {
DCHECK(delayed_notification_);
PrefServiceSyncable* const pref_service_syncable =
PrefServiceSyncable::FromProfile(profile_);
if (pref_service_syncable->IsSyncing()) {
pref_service_syncable->RemoveObserver(this);
scoped_ptr<Notification> previous_notification(
delayed_notification_.release());
ShowWelcomeNotificationIfNecessary(*(previous_notification.get()));
}
}
void ExtensionWelcomeNotification::ShowWelcomeNotificationIfNecessary(
const Notification& notification) {
if ((notification.notifier_id() == notifier_id_) && !delayed_notification_) {
PrefServiceSyncable* const pref_service_syncable =
PrefServiceSyncable::FromProfile(profile_);
if (pref_service_syncable->IsSyncing()) {
PrefService* const pref_service = profile_->GetPrefs();
if (!UserHasDismissedWelcomeNotification()) {
const PopUpRequest pop_up_request =
pref_service->GetBoolean(
prefs::kWelcomeNotificationPreviouslyPoppedUp)
? POP_UP_HIDDEN
: POP_UP_SHOWN;
if (pop_up_request == POP_UP_SHOWN) {
pref_service->SetBoolean(
prefs::kWelcomeNotificationPreviouslyPoppedUp, true);
}
if (IsWelcomeNotificationExpired()) {
ExpireWelcomeNotification();
} else {
ShowWelcomeNotification(
notification.display_source(), pop_up_request);
}
}
} else {
delayed_notification_.reset(new Notification(notification));
pref_service_syncable->AddObserver(this);
}
}
}
// static
void ExtensionWelcomeNotification::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* prefs) {
prefs->RegisterBooleanPref(prefs::kWelcomeNotificationDismissed,
false,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
prefs->RegisterBooleanPref(prefs::kWelcomeNotificationDismissedLocal,
false,
user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
prefs->RegisterBooleanPref(prefs::kWelcomeNotificationPreviouslyPoppedUp,
false,
user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
prefs->RegisterInt64Pref(prefs::kWelcomeNotificationExpirationTimestamp,
0,
user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
}
message_center::MessageCenter*
ExtensionWelcomeNotification::GetMessageCenter() const {
return delegate_->GetMessageCenter();
}
void ExtensionWelcomeNotification::ShowWelcomeNotification(
const base::string16& display_source,
const PopUpRequest pop_up_request) {
message_center::ButtonInfo learn_more(
l10n_util::GetStringUTF16(IDS_NOTIFICATION_WELCOME_BUTTON_LEARN_MORE));
learn_more.icon = ui::ResourceBundle::GetSharedInstance().GetImageNamed(
IDR_NOTIFICATION_WELCOME_LEARN_MORE);
message_center::ButtonInfo disable(
l10n_util::GetStringUTF16(IDS_NOTIFIER_WELCOME_BUTTON));
disable.icon = ui::ResourceBundle::GetSharedInstance().GetImageNamed(
IDR_NOTIFIER_BLOCK_BUTTON);
message_center::RichNotificationData rich_notification_data;
rich_notification_data.priority = 2;
rich_notification_data.buttons.push_back(learn_more);
rich_notification_data.buttons.push_back(disable);
if (welcome_notification_id_.empty())
welcome_notification_id_ = base::GenerateGUID();
if (!welcome_notification_id_.empty()) {
scoped_ptr<message_center::Notification> message_center_notification(
new message_center::Notification(
message_center::NOTIFICATION_TYPE_BASE_FORMAT,
welcome_notification_id_,
l10n_util::GetStringUTF16(IDS_NOTIFICATION_WELCOME_TITLE),
l10n_util::GetStringUTF16(IDS_NOTIFICATION_WELCOME_BODY),
ui::ResourceBundle::GetSharedInstance().GetImageNamed(
IDR_NOTIFICATION_WELCOME_ICON),
display_source,
notifier_id_,
rich_notification_data,
new NotificationCallbacks(
profile_, notifier_id_, welcome_notification_id_,
delegate_.get())));
if (pop_up_request == POP_UP_HIDDEN)
message_center_notification->set_shown_as_popup(true);
GetMessageCenter()->AddNotification(message_center_notification.Pass());
StartExpirationTimer();
}
}
void ExtensionWelcomeNotification::HideWelcomeNotification() {
if (!welcome_notification_id_.empty() &&
GetMessageCenter()->FindVisibleNotificationById(
welcome_notification_id_) != NULL) {
GetMessageCenter()->RemoveNotification(welcome_notification_id_, false);
StopExpirationTimer();
}
}
bool ExtensionWelcomeNotification::UserHasDismissedWelcomeNotification() const {
// This was previously a syncable preference; now it's per-machine.
// Only the local pref will be written moving forward, but check for both so
// users won't be double-toasted.
bool shown_synced = profile_->GetPrefs()->GetBoolean(
prefs::kWelcomeNotificationDismissed);
bool shown_local = profile_->GetPrefs()->GetBoolean(
prefs::kWelcomeNotificationDismissedLocal);
return (shown_synced || shown_local);
}
void ExtensionWelcomeNotification::OnWelcomeNotificationDismissedChanged() {
if (UserHasDismissedWelcomeNotification()) {
HideWelcomeNotification();
}
}
void ExtensionWelcomeNotification::StartExpirationTimer() {
if (!expiration_timer_ && !IsWelcomeNotificationExpired()) {
base::Time expiration_timestamp = GetExpirationTimestamp();
if (expiration_timestamp.is_null()) {
SetExpirationTimestampFromNow();
expiration_timestamp = GetExpirationTimestamp();
DCHECK(!expiration_timestamp.is_null());
}
expiration_timer_.reset(
new base::OneShotTimer<ExtensionWelcomeNotification>());
expiration_timer_->Start(
FROM_HERE,
expiration_timestamp - delegate_->GetCurrentTime(),
this,
&ExtensionWelcomeNotification::ExpireWelcomeNotification);
}
}
void ExtensionWelcomeNotification::StopExpirationTimer() {
if (expiration_timer_) {
expiration_timer_->Stop();
expiration_timer_.reset();
}
}
void ExtensionWelcomeNotification::ExpireWelcomeNotification() {
DCHECK(IsWelcomeNotificationExpired());
profile_->GetPrefs()->SetBoolean(
prefs::kWelcomeNotificationDismissedLocal, true);
HideWelcomeNotification();
}
base::Time ExtensionWelcomeNotification::GetExpirationTimestamp() const {
PrefService* const pref_service = profile_->GetPrefs();
const int64 expiration_timestamp =
pref_service->GetInt64(prefs::kWelcomeNotificationExpirationTimestamp);
return (expiration_timestamp == 0)
? base::Time()
: base::Time::FromInternalValue(expiration_timestamp);
}
void ExtensionWelcomeNotification::SetExpirationTimestampFromNow() {
PrefService* const pref_service = profile_->GetPrefs();
pref_service->SetInt64(
prefs::kWelcomeNotificationExpirationTimestamp,
(delegate_->GetCurrentTime() +
base::TimeDelta::FromDays(kRequestedShowTimeDays)).ToInternalValue());
}
bool ExtensionWelcomeNotification::IsWelcomeNotificationExpired() const {
const base::Time expiration_timestamp = GetExpirationTimestamp();
return !expiration_timestamp.is_null() &&
(expiration_timestamp <= delegate_->GetCurrentTime());
}