// 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