privet_notifications.cc revision 68043e1e95eeb07d5cae7aca370b26518b0867d6
1// Copyright 2013 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/local_discovery/privet_notifications.h" 6 7#include "base/bind.h" 8#include "base/command_line.h" 9#include "base/message_loop/message_loop.h" 10#include "base/metrics/histogram.h" 11#include "base/prefs/pref_service.h" 12#include "base/rand_util.h" 13#include "base/strings/utf_string_conversions.h" 14#include "chrome/browser/browser_process.h" 15#include "chrome/browser/local_discovery/privet_device_lister_impl.h" 16#include "chrome/browser/local_discovery/privet_http_asynchronous_factory.h" 17#include "chrome/browser/local_discovery/privet_traffic_detector.h" 18#include "chrome/browser/local_discovery/service_discovery_shared_client.h" 19#include "chrome/browser/notifications/notification.h" 20#include "chrome/browser/notifications/notification_ui_manager.h" 21#include "chrome/browser/profiles/profile.h" 22#include "chrome/browser/ui/browser.h" 23#include "chrome/browser/ui/browser_finder.h" 24#include "chrome/browser/ui/host_desktop.h" 25#include "chrome/browser/ui/tabs/tab_strip_model.h" 26#include "chrome/browser/ui/webui/local_discovery/local_discovery_ui_handler.h" 27#include "chrome/common/chrome_switches.h" 28#include "chrome/common/pref_names.h" 29#include "content/public/browser/browser_context.h" 30#include "content/public/browser/navigation_controller.h" 31#include "content/public/browser/web_contents.h" 32#include "content/public/common/page_transition_types.h" 33#include "grit/generated_resources.h" 34#include "grit/theme_resources.h" 35#include "ui/base/l10n/l10n_util.h" 36#include "ui/base/resource/resource_bundle.h" 37#include "ui/message_center/message_center_util.h" 38#include "ui/message_center/notifier_settings.h" 39 40namespace local_discovery { 41 42namespace { 43 44const int kTenMinutesInSeconds = 600; 45const char kPrivetInfoKeyUptime[] = "uptime"; 46const char kPrivetNotificationID[] = "privet_notification"; 47const char kPrivetNotificationOriginUrl[] = "chrome://devices"; 48const int kStartDelaySeconds = 5; 49 50enum PrivetNotificationsEvent { 51 PRIVET_SERVICE_STARTED, 52 PRIVET_LISTER_STARTED, 53 PRIVET_DEVICE_CHANGED, 54 PRIVET_INFO_DONE, 55 PRIVET_NOTIFICATION_SHOWN, 56 PRIVET_NOTIFICATION_CANCELED, 57 PRIVET_NOTIFICATION_CLICKED, 58 PRIVET_DISABLE_NOTIFICATIONS_CLICKED, 59 PRIVET_EVENT_MAX, 60}; 61 62void ReportPrivetUmaEvent(PrivetNotificationsEvent privet_event) { 63 UMA_HISTOGRAM_ENUMERATION("LocalDiscovery.PrivetNotificationsEvent", 64 privet_event, PRIVET_EVENT_MAX); 65} 66 67} // namespace 68 69PrivetNotificationsListener::PrivetNotificationsListener( 70 scoped_ptr<PrivetHTTPAsynchronousFactory> privet_http_factory, 71 Delegate* delegate) : delegate_(delegate), devices_active_(0) { 72 privet_http_factory_.swap(privet_http_factory); 73} 74 75PrivetNotificationsListener::~PrivetNotificationsListener() { 76} 77 78void PrivetNotificationsListener::DeviceChanged( 79 bool added, 80 const std::string& name, 81 const DeviceDescription& description) { 82 ReportPrivetUmaEvent(PRIVET_DEVICE_CHANGED); 83 DeviceContextMap::iterator found = devices_seen_.find(name); 84 if (found != devices_seen_.end()) { 85 if (!description.id.empty() && // Device is registered 86 found->second->notification_may_be_active) { 87 found->second->notification_may_be_active = false; 88 NotifyDeviceRemoved(); 89 } 90 return; // Already saw this device. 91 } 92 93 linked_ptr<DeviceContext> device_context(new DeviceContext); 94 95 device_context->notification_may_be_active = false; 96 device_context->registered = !description.id.empty(); 97 98 devices_seen_.insert(make_pair(name, device_context)); 99 100 if (!device_context->registered) { 101 device_context->privet_http_resolution = 102 privet_http_factory_->CreatePrivetHTTP( 103 name, 104 description.address, 105 base::Bind(&PrivetNotificationsListener::CreateInfoOperation, 106 base::Unretained(this))); 107 108 device_context->privet_http_resolution->Start(); 109 } 110} 111 112void PrivetNotificationsListener::CreateInfoOperation( 113 scoped_ptr<PrivetHTTPClient> http_client) { 114 std::string name = http_client->GetName(); 115 DeviceContextMap::iterator device_iter = devices_seen_.find(name); 116 DCHECK(device_iter != devices_seen_.end()); 117 DeviceContext* device = device_iter->second.get(); 118 device->privet_http.swap(http_client); 119 device->info_operation = 120 device->privet_http->CreateInfoOperation(this); 121 device->info_operation->Start(); 122} 123 124void PrivetNotificationsListener::OnPrivetInfoDone( 125 PrivetInfoOperation* operation, 126 int http_code, 127 const base::DictionaryValue* json_value) { 128 ReportPrivetUmaEvent(PRIVET_INFO_DONE); 129 std::string name = operation->GetHTTPClient()->GetName(); 130 DeviceContextMap::iterator device_iter = devices_seen_.find(name); 131 DCHECK(device_iter != devices_seen_.end()); 132 DeviceContext* device = device_iter->second.get(); 133 134 int uptime; 135 136 if (!json_value || 137 !json_value->GetInteger(kPrivetInfoKeyUptime, &uptime) || 138 uptime > kTenMinutesInSeconds) { 139 return; 140 } 141 142 DCHECK(!device->notification_may_be_active); 143 device->notification_may_be_active = true; 144 devices_active_++; 145 delegate_->PrivetNotify(devices_active_ > 1, true); 146} 147 148void PrivetNotificationsListener::DeviceRemoved(const std::string& name) { 149 DCHECK_EQ(1u, devices_seen_.count(name)); 150 DeviceContextMap::iterator device_iter = devices_seen_.find(name); 151 DCHECK(device_iter != devices_seen_.end()); 152 DeviceContext* device = device_iter->second.get(); 153 154 device->info_operation.reset(); 155 device->privet_http_resolution.reset(); 156 device->notification_may_be_active = false; 157 NotifyDeviceRemoved(); 158} 159 160void PrivetNotificationsListener::DeviceCacheFlushed() { 161 for (DeviceContextMap::iterator i = devices_seen_.begin(); 162 i != devices_seen_.end(); ++i) { 163 DeviceContext* device = i->second.get(); 164 165 device->info_operation.reset(); 166 device->privet_http_resolution.reset(); 167 if (device->notification_may_be_active) { 168 device->notification_may_be_active = false; 169 } 170 } 171 172 devices_active_ = 0; 173 delegate_->PrivetRemoveNotification(); 174} 175 176void PrivetNotificationsListener::NotifyDeviceRemoved() { 177 devices_active_--; 178 if (devices_active_ == 0) { 179 delegate_->PrivetRemoveNotification(); 180 } else { 181 delegate_->PrivetNotify(devices_active_ > 1, true); 182 } 183} 184 185PrivetNotificationsListener::DeviceContext::DeviceContext() { 186} 187 188PrivetNotificationsListener::DeviceContext::~DeviceContext() { 189} 190 191PrivetNotificationService::PrivetNotificationService( 192 content::BrowserContext* profile) 193 : profile_(profile) { 194 base::MessageLoop::current()->PostDelayedTask( 195 FROM_HERE, 196 base::Bind(&PrivetNotificationService::Start, AsWeakPtr()), 197 base::TimeDelta::FromSeconds(kStartDelaySeconds + 198 base::RandInt(0, kStartDelaySeconds/4))); 199} 200 201PrivetNotificationService::~PrivetNotificationService() { 202} 203 204void PrivetNotificationService::DeviceChanged( 205 bool added, 206 const std::string& name, 207 const DeviceDescription& description) { 208 privet_notifications_listener_->DeviceChanged(added, name, description); 209} 210 211void PrivetNotificationService::DeviceRemoved(const std::string& name) { 212 privet_notifications_listener_->DeviceRemoved(name); 213} 214 215void PrivetNotificationService::DeviceCacheFlushed() { 216 privet_notifications_listener_->DeviceCacheFlushed(); 217} 218 219// static 220bool PrivetNotificationService::IsEnabled() { 221 CommandLine* command_line = CommandLine::ForCurrentProcess(); 222 return !command_line->HasSwitch(switches::kDisableDeviceDiscovery) && 223 !command_line->HasSwitch( 224 switches::kDisableDeviceDiscoveryNotifications) && 225 message_center::IsRichNotificationEnabled(); 226} 227 228// static 229bool PrivetNotificationService::IsForced() { 230 CommandLine* command_line = CommandLine::ForCurrentProcess(); 231 return command_line->HasSwitch(switches::kEnableDeviceDiscoveryNotifications); 232} 233 234void PrivetNotificationService::PrivetNotify(bool has_multiple, 235 bool added) { 236 string16 product_name = l10n_util::GetStringUTF16( 237 IDS_LOCAL_DISOCVERY_PRODUCT_NAME_PRINTER); 238 239 int title_resource = has_multiple ? 240 IDS_LOCAL_DISOCVERY_NOTIFICATION_TITLE_PRINTER_MULTIPLE : 241 IDS_LOCAL_DISOCVERY_NOTIFICATION_TITLE_PRINTER; 242 243 int body_resource = has_multiple ? 244 IDS_LOCAL_DISOCVERY_NOTIFICATION_CONTENTS_PRINTER_MULTIPLE : 245 IDS_LOCAL_DISOCVERY_NOTIFICATION_CONTENTS_PRINTER; 246 247 string16 title = l10n_util::GetStringUTF16(title_resource); 248 string16 body = l10n_util::GetStringFUTF16(body_resource, product_name); 249 250 Profile* profile_object = Profile::FromBrowserContext(profile_); 251 message_center::RichNotificationData rich_notification_data; 252 253 rich_notification_data.buttons.push_back( 254 message_center::ButtonInfo(l10n_util::GetStringUTF16( 255 IDS_LOCAL_DISOCVERY_NOTIFICATION_BUTTON_PRINTER))); 256 257 rich_notification_data.buttons.push_back( 258 message_center::ButtonInfo(l10n_util::GetStringUTF16( 259 IDS_LOCAL_DISCOVERY_NOTIFICATIONS_DISABLE_BUTTON_LABEL))); 260 261 Notification notification( 262 message_center::NOTIFICATION_TYPE_SIMPLE, 263 GURL(kPrivetNotificationOriginUrl), 264 title, 265 body, 266 ui::ResourceBundle::GetSharedInstance().GetImageNamed( 267 IDR_LOCAL_DISCOVERY_CLOUDPRINT_ICON), 268 WebKit::WebTextDirectionDefault, 269 message_center::NotifierId( 270 message_center::NotifierId::SYSTEM_COMPONENT), 271 product_name, 272 UTF8ToUTF16(kPrivetNotificationID), 273 rich_notification_data, 274 new PrivetNotificationDelegate(profile_)); 275 276 bool updated = g_browser_process->notification_ui_manager()->Update( 277 notification, profile_object); 278 if (!updated && added && !LocalDiscoveryUIHandler::GetHasVisible()) { 279 ReportPrivetUmaEvent(PRIVET_NOTIFICATION_SHOWN); 280 g_browser_process->notification_ui_manager()->Add(notification, 281 profile_object); 282 } 283} 284 285void PrivetNotificationService::PrivetRemoveNotification() { 286 ReportPrivetUmaEvent(PRIVET_NOTIFICATION_CANCELED); 287 g_browser_process->notification_ui_manager()->CancelById( 288 kPrivetNotificationID); 289} 290 291void PrivetNotificationService::Start() { 292 enable_privet_notification_member_.Init( 293 prefs::kLocalDiscoveryNotificationsEnabled, 294 Profile::FromBrowserContext(profile_)->GetPrefs(), 295 base::Bind(&PrivetNotificationService::OnNotificationsEnabledChanged, 296 base::Unretained(this))); 297 OnNotificationsEnabledChanged(); 298} 299 300void PrivetNotificationService::OnNotificationsEnabledChanged() { 301 if (IsForced()) { 302 StartLister(); 303 } else if (*enable_privet_notification_member_) { 304 ReportPrivetUmaEvent(PRIVET_SERVICE_STARTED); 305 traffic_detector_ = 306 new PrivetTrafficDetector( 307 net::ADDRESS_FAMILY_IPV4, 308 base::Bind(&PrivetNotificationService::StartLister, AsWeakPtr())); 309 traffic_detector_->Start(); 310 } else { 311 traffic_detector_ = NULL; 312 device_lister_.reset(); 313 service_discovery_client_ = NULL; 314 privet_notifications_listener_.reset(); 315 } 316} 317 318void PrivetNotificationService::StartLister() { 319 ReportPrivetUmaEvent(PRIVET_LISTER_STARTED); 320 traffic_detector_ = NULL; 321 DCHECK(!service_discovery_client_); 322 service_discovery_client_ = ServiceDiscoverySharedClient::GetInstance(); 323 device_lister_.reset(new PrivetDeviceListerImpl(service_discovery_client_, 324 this)); 325 device_lister_->Start(); 326 device_lister_->DiscoverNewDevices(false); 327 328 scoped_ptr<PrivetHTTPAsynchronousFactory> http_factory( 329 PrivetHTTPAsynchronousFactory::CreateInstance( 330 service_discovery_client_.get(), profile_->GetRequestContext())); 331 332 privet_notifications_listener_.reset(new PrivetNotificationsListener( 333 http_factory.Pass(), this)); 334} 335 336PrivetNotificationDelegate::PrivetNotificationDelegate( 337 content::BrowserContext* profile) 338 : profile_(profile) { 339} 340 341PrivetNotificationDelegate::~PrivetNotificationDelegate() { 342} 343 344std::string PrivetNotificationDelegate::id() const { 345 return kPrivetNotificationID; 346} 347 348content::RenderViewHost* PrivetNotificationDelegate::GetRenderViewHost() const { 349 return NULL; 350} 351 352void PrivetNotificationDelegate::Display() { 353} 354 355void PrivetNotificationDelegate::Error() { 356 LOG(ERROR) << "Error displaying privet notification"; 357} 358 359void PrivetNotificationDelegate::Close(bool by_user) { 360} 361 362void PrivetNotificationDelegate::Click() { 363} 364 365void PrivetNotificationDelegate::ButtonClick(int button_index) { 366 if (button_index == 0) { 367 ReportPrivetUmaEvent(PRIVET_NOTIFICATION_CLICKED); 368 OpenTab(GURL(kPrivetNotificationOriginUrl)); 369 } else if (button_index == 1) { 370 ReportPrivetUmaEvent(PRIVET_DISABLE_NOTIFICATIONS_CLICKED); 371 DisableNotifications(); 372 } 373} 374 375void PrivetNotificationDelegate::OpenTab(const GURL& url) { 376 Profile* profile_obj = Profile::FromBrowserContext(profile_); 377 378 Browser* browser = FindOrCreateTabbedBrowser(profile_obj, 379 chrome::GetActiveDesktop()); 380 content::WebContents::CreateParams create_params(profile_obj); 381 382 scoped_ptr<content::WebContents> contents( 383 content::WebContents::Create(create_params)); 384 contents->GetController().LoadURL(url, 385 content::Referrer(), 386 content::PAGE_TRANSITION_AUTO_TOPLEVEL, 387 ""); 388 389 browser->tab_strip_model()->AppendWebContents(contents.release(), true); 390} 391 392void PrivetNotificationDelegate::DisableNotifications() { 393 Profile* profile_obj = Profile::FromBrowserContext(profile_); 394 395 profile_obj->GetPrefs()->SetBoolean( 396 prefs::kLocalDiscoveryNotificationsEnabled, 397 false); 398} 399 400} // namespace local_discovery 401