// Copyright 2013 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/local_discovery/privet_notifications.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/message_loop/message_loop.h"
#include "base/metrics/histogram.h"
#include "base/prefs/pref_service.h"
#include "base/rand_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/local_discovery/privet_device_lister_impl.h"
#include "chrome/browser/local_discovery/privet_http_asynchronous_factory.h"
#include "chrome/browser/local_discovery/privet_traffic_detector.h"
#include "chrome/browser/local_discovery/service_discovery_shared_client.h"
#include "chrome/browser/notifications/notification.h"
#include "chrome/browser/notifications/notification_ui_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/host_desktop.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/webui/local_discovery/local_discovery_ui_handler.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/page_transition_types.h"
#include "grit/generated_resources.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_util.h"
#include "ui/message_center/notifier_settings.h"
namespace local_discovery {
namespace {
const int kTenMinutesInSeconds = 600;
const char kPrivetInfoKeyUptime[] = "uptime";
const char kPrivetNotificationID[] = "privet_notification";
const char kPrivetNotificationOriginUrl[] = "chrome://devices";
const int kStartDelaySeconds = 5;
enum PrivetNotificationsEvent {
PRIVET_SERVICE_STARTED,
PRIVET_LISTER_STARTED,
PRIVET_DEVICE_CHANGED,
PRIVET_INFO_DONE,
PRIVET_NOTIFICATION_SHOWN,
PRIVET_NOTIFICATION_CANCELED,
PRIVET_NOTIFICATION_CLICKED,
PRIVET_DISABLE_NOTIFICATIONS_CLICKED,
PRIVET_EVENT_MAX,
};
void ReportPrivetUmaEvent(PrivetNotificationsEvent privet_event) {
UMA_HISTOGRAM_ENUMERATION("LocalDiscovery.PrivetNotificationsEvent",
privet_event, PRIVET_EVENT_MAX);
}
} // namespace
PrivetNotificationsListener::PrivetNotificationsListener(
scoped_ptr<PrivetHTTPAsynchronousFactory> privet_http_factory,
Delegate* delegate) : delegate_(delegate), devices_active_(0) {
privet_http_factory_.swap(privet_http_factory);
}
PrivetNotificationsListener::~PrivetNotificationsListener() {
}
void PrivetNotificationsListener::DeviceChanged(
bool added,
const std::string& name,
const DeviceDescription& description) {
ReportPrivetUmaEvent(PRIVET_DEVICE_CHANGED);
DeviceContextMap::iterator found = devices_seen_.find(name);
if (found != devices_seen_.end()) {
if (!description.id.empty() && // Device is registered
found->second->notification_may_be_active) {
found->second->notification_may_be_active = false;
NotifyDeviceRemoved();
}
return; // Already saw this device.
}
linked_ptr<DeviceContext> device_context(new DeviceContext);
device_context->notification_may_be_active = false;
device_context->registered = !description.id.empty();
devices_seen_.insert(make_pair(name, device_context));
if (!device_context->registered) {
device_context->privet_http_resolution =
privet_http_factory_->CreatePrivetHTTP(
name,
description.address,
base::Bind(&PrivetNotificationsListener::CreateInfoOperation,
base::Unretained(this)));
device_context->privet_http_resolution->Start();
}
}
void PrivetNotificationsListener::CreateInfoOperation(
scoped_ptr<PrivetHTTPClient> http_client) {
std::string name = http_client->GetName();
DeviceContextMap::iterator device_iter = devices_seen_.find(name);
DCHECK(device_iter != devices_seen_.end());
DeviceContext* device = device_iter->second.get();
device->privet_http.swap(http_client);
device->info_operation =
device->privet_http->CreateInfoOperation(this);
device->info_operation->Start();
}
void PrivetNotificationsListener::OnPrivetInfoDone(
PrivetInfoOperation* operation,
int http_code,
const base::DictionaryValue* json_value) {
ReportPrivetUmaEvent(PRIVET_INFO_DONE);
std::string name = operation->GetHTTPClient()->GetName();
DeviceContextMap::iterator device_iter = devices_seen_.find(name);
DCHECK(device_iter != devices_seen_.end());
DeviceContext* device = device_iter->second.get();
int uptime;
if (!json_value ||
!json_value->GetInteger(kPrivetInfoKeyUptime, &uptime) ||
uptime > kTenMinutesInSeconds) {
return;
}
DCHECK(!device->notification_may_be_active);
device->notification_may_be_active = true;
devices_active_++;
delegate_->PrivetNotify(devices_active_ > 1, true);
}
void PrivetNotificationsListener::DeviceRemoved(const std::string& name) {
DCHECK_EQ(1u, devices_seen_.count(name));
DeviceContextMap::iterator device_iter = devices_seen_.find(name);
DCHECK(device_iter != devices_seen_.end());
DeviceContext* device = device_iter->second.get();
device->info_operation.reset();
device->privet_http_resolution.reset();
device->notification_may_be_active = false;
NotifyDeviceRemoved();
}
void PrivetNotificationsListener::DeviceCacheFlushed() {
for (DeviceContextMap::iterator i = devices_seen_.begin();
i != devices_seen_.end(); ++i) {
DeviceContext* device = i->second.get();
device->info_operation.reset();
device->privet_http_resolution.reset();
if (device->notification_may_be_active) {
device->notification_may_be_active = false;
}
}
devices_active_ = 0;
delegate_->PrivetRemoveNotification();
}
void PrivetNotificationsListener::NotifyDeviceRemoved() {
devices_active_--;
if (devices_active_ == 0) {
delegate_->PrivetRemoveNotification();
} else {
delegate_->PrivetNotify(devices_active_ > 1, true);
}
}
PrivetNotificationsListener::DeviceContext::DeviceContext() {
}
PrivetNotificationsListener::DeviceContext::~DeviceContext() {
}
PrivetNotificationService::PrivetNotificationService(
content::BrowserContext* profile)
: profile_(profile) {
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&PrivetNotificationService::Start, AsWeakPtr()),
base::TimeDelta::FromSeconds(kStartDelaySeconds +
base::RandInt(0, kStartDelaySeconds/4)));
}
PrivetNotificationService::~PrivetNotificationService() {
}
void PrivetNotificationService::DeviceChanged(
bool added,
const std::string& name,
const DeviceDescription& description) {
privet_notifications_listener_->DeviceChanged(added, name, description);
}
void PrivetNotificationService::DeviceRemoved(const std::string& name) {
privet_notifications_listener_->DeviceRemoved(name);
}
void PrivetNotificationService::DeviceCacheFlushed() {
privet_notifications_listener_->DeviceCacheFlushed();
}
// static
bool PrivetNotificationService::IsEnabled() {
CommandLine* command_line = CommandLine::ForCurrentProcess();
return !command_line->HasSwitch(switches::kDisableDeviceDiscovery) &&
!command_line->HasSwitch(
switches::kDisableDeviceDiscoveryNotifications) &&
message_center::IsRichNotificationEnabled();
}
// static
bool PrivetNotificationService::IsForced() {
CommandLine* command_line = CommandLine::ForCurrentProcess();
return command_line->HasSwitch(switches::kEnableDeviceDiscoveryNotifications);
}
void PrivetNotificationService::PrivetNotify(bool has_multiple,
bool added) {
base::string16 product_name = l10n_util::GetStringUTF16(
IDS_LOCAL_DISOCVERY_SERVICE_NAME_PRINTER);
int title_resource = has_multiple ?
IDS_LOCAL_DISOCVERY_NOTIFICATION_TITLE_PRINTER_MULTIPLE :
IDS_LOCAL_DISOCVERY_NOTIFICATION_TITLE_PRINTER;
int body_resource = has_multiple ?
IDS_LOCAL_DISOCVERY_NOTIFICATION_CONTENTS_PRINTER_MULTIPLE :
IDS_LOCAL_DISOCVERY_NOTIFICATION_CONTENTS_PRINTER;
base::string16 title = l10n_util::GetStringUTF16(title_resource);
base::string16 body = l10n_util::GetStringUTF16(body_resource);
Profile* profile_object = Profile::FromBrowserContext(profile_);
message_center::RichNotificationData rich_notification_data;
rich_notification_data.buttons.push_back(
message_center::ButtonInfo(l10n_util::GetStringUTF16(
IDS_LOCAL_DISOCVERY_NOTIFICATION_BUTTON_PRINTER)));
rich_notification_data.buttons.push_back(
message_center::ButtonInfo(l10n_util::GetStringUTF16(
IDS_LOCAL_DISCOVERY_NOTIFICATIONS_DISABLE_BUTTON_LABEL)));
Notification notification(
message_center::NOTIFICATION_TYPE_SIMPLE,
GURL(kPrivetNotificationOriginUrl),
title,
body,
ui::ResourceBundle::GetSharedInstance().GetImageNamed(
IDR_LOCAL_DISCOVERY_CLOUDPRINT_ICON),
blink::WebTextDirectionDefault,
message_center::NotifierId(GURL(kPrivetNotificationOriginUrl)),
product_name,
UTF8ToUTF16(kPrivetNotificationID),
rich_notification_data,
new PrivetNotificationDelegate(profile_));
bool updated = g_browser_process->notification_ui_manager()->Update(
notification, profile_object);
if (!updated && added && !LocalDiscoveryUIHandler::GetHasVisible()) {
ReportPrivetUmaEvent(PRIVET_NOTIFICATION_SHOWN);
g_browser_process->notification_ui_manager()->Add(notification,
profile_object);
}
}
void PrivetNotificationService::PrivetRemoveNotification() {
ReportPrivetUmaEvent(PRIVET_NOTIFICATION_CANCELED);
g_browser_process->notification_ui_manager()->CancelById(
kPrivetNotificationID);
}
void PrivetNotificationService::Start() {
enable_privet_notification_member_.Init(
prefs::kLocalDiscoveryNotificationsEnabled,
Profile::FromBrowserContext(profile_)->GetPrefs(),
base::Bind(&PrivetNotificationService::OnNotificationsEnabledChanged,
base::Unretained(this)));
OnNotificationsEnabledChanged();
}
void PrivetNotificationService::OnNotificationsEnabledChanged() {
if (IsForced()) {
StartLister();
} else if (*enable_privet_notification_member_) {
ReportPrivetUmaEvent(PRIVET_SERVICE_STARTED);
traffic_detector_ =
new PrivetTrafficDetector(
net::ADDRESS_FAMILY_IPV4,
base::Bind(&PrivetNotificationService::StartLister, AsWeakPtr()));
traffic_detector_->Start();
} else {
traffic_detector_ = NULL;
device_lister_.reset();
service_discovery_client_ = NULL;
privet_notifications_listener_.reset();
}
}
void PrivetNotificationService::StartLister() {
ReportPrivetUmaEvent(PRIVET_LISTER_STARTED);
traffic_detector_ = NULL;
DCHECK(!service_discovery_client_);
service_discovery_client_ = ServiceDiscoverySharedClient::GetInstance();
device_lister_.reset(new PrivetDeviceListerImpl(service_discovery_client_,
this));
device_lister_->Start();
device_lister_->DiscoverNewDevices(false);
scoped_ptr<PrivetHTTPAsynchronousFactory> http_factory(
PrivetHTTPAsynchronousFactory::CreateInstance(
service_discovery_client_.get(), profile_->GetRequestContext()));
privet_notifications_listener_.reset(new PrivetNotificationsListener(
http_factory.Pass(), this));
}
PrivetNotificationDelegate::PrivetNotificationDelegate(
content::BrowserContext* profile)
: profile_(profile) {
}
PrivetNotificationDelegate::~PrivetNotificationDelegate() {
}
std::string PrivetNotificationDelegate::id() const {
return kPrivetNotificationID;
}
content::RenderViewHost* PrivetNotificationDelegate::GetRenderViewHost() const {
return NULL;
}
void PrivetNotificationDelegate::Display() {
}
void PrivetNotificationDelegate::Error() {
LOG(ERROR) << "Error displaying privet notification";
}
void PrivetNotificationDelegate::Close(bool by_user) {
}
void PrivetNotificationDelegate::Click() {
}
void PrivetNotificationDelegate::ButtonClick(int button_index) {
if (button_index == 0) {
ReportPrivetUmaEvent(PRIVET_NOTIFICATION_CLICKED);
OpenTab(GURL(kPrivetNotificationOriginUrl));
} else if (button_index == 1) {
ReportPrivetUmaEvent(PRIVET_DISABLE_NOTIFICATIONS_CLICKED);
DisableNotifications();
}
}
void PrivetNotificationDelegate::OpenTab(const GURL& url) {
Profile* profile_obj = Profile::FromBrowserContext(profile_);
chrome::NavigateParams params(profile_obj,
url,
content::PAGE_TRANSITION_AUTO_TOPLEVEL);
params.disposition = NEW_FOREGROUND_TAB;
chrome::Navigate(¶ms);
}
void PrivetNotificationDelegate::DisableNotifications() {
Profile* profile_obj = Profile::FromBrowserContext(profile_);
profile_obj->GetPrefs()->SetBoolean(
prefs::kLocalDiscoveryNotificationsEnabled,
false);
}
} // namespace local_discovery