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/browser_window.h" 25 #include "chrome/browser/ui/host_desktop.h" 26 #include "chrome/browser/ui/tabs/tab_strip_model.h" 27 #include "chrome/browser/ui/webui/local_discovery/local_discovery_ui_handler.h" 28 #include "chrome/common/chrome_switches.h" 29 #include "chrome/common/pref_names.h" 30 #include "content/public/browser/browser_context.h" 31 #include "content/public/browser/navigation_controller.h" 32 #include "content/public/browser/web_contents.h" 33 #include "content/public/common/page_transition_types.h" 34 #include "grit/generated_resources.h" 35 #include "grit/theme_resources.h" 36 #include "ui/base/l10n/l10n_util.h" 37 #include "ui/base/resource/resource_bundle.h" 38 #include "ui/message_center/message_center_util.h" 39 #include "ui/message_center/notifier_settings.h" 40 41 namespace local_discovery { 42 43 namespace { 44 45 const int kTenMinutesInSeconds = 600; 46 const char kPrivetInfoKeyUptime[] = "uptime"; 47 const char kPrivetNotificationID[] = "privet_notification"; 48 const char kPrivetNotificationOriginUrl[] = "chrome://devices"; 49 const int kStartDelaySeconds = 5; 50 51 enum PrivetNotificationsEvent { 52 PRIVET_SERVICE_STARTED, 53 PRIVET_LISTER_STARTED, 54 PRIVET_DEVICE_CHANGED, 55 PRIVET_INFO_DONE, 56 PRIVET_NOTIFICATION_SHOWN, 57 PRIVET_NOTIFICATION_CANCELED, 58 PRIVET_NOTIFICATION_CLICKED, 59 PRIVET_DISABLE_NOTIFICATIONS_CLICKED, 60 PRIVET_EVENT_MAX, 61 }; 62 63 void ReportPrivetUmaEvent(PrivetNotificationsEvent privet_event) { 64 UMA_HISTOGRAM_ENUMERATION("LocalDiscovery.PrivetNotificationsEvent", 65 privet_event, PRIVET_EVENT_MAX); 66 } 67 68 } // namespace 69 70 PrivetNotificationsListener::PrivetNotificationsListener( 71 scoped_ptr<PrivetHTTPAsynchronousFactory> privet_http_factory, 72 Delegate* delegate) : delegate_(delegate), devices_active_(0) { 73 privet_http_factory_.swap(privet_http_factory); 74 } 75 76 PrivetNotificationsListener::~PrivetNotificationsListener() { 77 } 78 79 void PrivetNotificationsListener::DeviceChanged( 80 bool added, 81 const std::string& name, 82 const DeviceDescription& description) { 83 ReportPrivetUmaEvent(PRIVET_DEVICE_CHANGED); 84 DeviceContextMap::iterator found = devices_seen_.find(name); 85 if (found != devices_seen_.end()) { 86 if (!description.id.empty() && // Device is registered 87 found->second->notification_may_be_active) { 88 found->second->notification_may_be_active = false; 89 NotifyDeviceRemoved(); 90 } 91 return; // Already saw this device. 92 } 93 94 linked_ptr<DeviceContext> device_context(new DeviceContext); 95 96 device_context->notification_may_be_active = false; 97 device_context->registered = !description.id.empty(); 98 99 devices_seen_.insert(make_pair(name, device_context)); 100 101 if (!device_context->registered) { 102 device_context->privet_http_resolution = 103 privet_http_factory_->CreatePrivetHTTP( 104 name, 105 description.address, 106 base::Bind(&PrivetNotificationsListener::CreateInfoOperation, 107 base::Unretained(this))); 108 109 device_context->privet_http_resolution->Start(); 110 } 111 } 112 113 void PrivetNotificationsListener::CreateInfoOperation( 114 scoped_ptr<PrivetHTTPClient> http_client) { 115 std::string name = http_client->GetName(); 116 DeviceContextMap::iterator device_iter = devices_seen_.find(name); 117 DCHECK(device_iter != devices_seen_.end()); 118 DeviceContext* device = device_iter->second.get(); 119 device->privet_http.swap(http_client); 120 device->info_operation = 121 device->privet_http->CreateInfoOperation(this); 122 device->info_operation->Start(); 123 } 124 125 void PrivetNotificationsListener::OnPrivetInfoDone( 126 PrivetInfoOperation* operation, 127 int http_code, 128 const base::DictionaryValue* json_value) { 129 ReportPrivetUmaEvent(PRIVET_INFO_DONE); 130 std::string name = operation->GetHTTPClient()->GetName(); 131 DeviceContextMap::iterator device_iter = devices_seen_.find(name); 132 DCHECK(device_iter != devices_seen_.end()); 133 DeviceContext* device = device_iter->second.get(); 134 135 int uptime; 136 137 if (!json_value || 138 !json_value->GetInteger(kPrivetInfoKeyUptime, &uptime) || 139 uptime > kTenMinutesInSeconds) { 140 return; 141 } 142 143 DCHECK(!device->notification_may_be_active); 144 device->notification_may_be_active = true; 145 devices_active_++; 146 delegate_->PrivetNotify(devices_active_ > 1, true); 147 } 148 149 void PrivetNotificationsListener::DeviceRemoved(const std::string& name) { 150 DCHECK_EQ(1u, devices_seen_.count(name)); 151 DeviceContextMap::iterator device_iter = devices_seen_.find(name); 152 DCHECK(device_iter != devices_seen_.end()); 153 DeviceContext* device = device_iter->second.get(); 154 155 device->info_operation.reset(); 156 device->privet_http_resolution.reset(); 157 device->notification_may_be_active = false; 158 NotifyDeviceRemoved(); 159 } 160 161 void PrivetNotificationsListener::DeviceCacheFlushed() { 162 for (DeviceContextMap::iterator i = devices_seen_.begin(); 163 i != devices_seen_.end(); ++i) { 164 DeviceContext* device = i->second.get(); 165 166 device->info_operation.reset(); 167 device->privet_http_resolution.reset(); 168 if (device->notification_may_be_active) { 169 device->notification_may_be_active = false; 170 } 171 } 172 173 devices_active_ = 0; 174 delegate_->PrivetRemoveNotification(); 175 } 176 177 void PrivetNotificationsListener::NotifyDeviceRemoved() { 178 devices_active_--; 179 if (devices_active_ == 0) { 180 delegate_->PrivetRemoveNotification(); 181 } else { 182 delegate_->PrivetNotify(devices_active_ > 1, true); 183 } 184 } 185 186 PrivetNotificationsListener::DeviceContext::DeviceContext() { 187 } 188 189 PrivetNotificationsListener::DeviceContext::~DeviceContext() { 190 } 191 192 PrivetNotificationService::PrivetNotificationService( 193 content::BrowserContext* profile) 194 : profile_(profile) { 195 base::MessageLoop::current()->PostDelayedTask( 196 FROM_HERE, 197 base::Bind(&PrivetNotificationService::Start, AsWeakPtr()), 198 base::TimeDelta::FromSeconds(kStartDelaySeconds + 199 base::RandInt(0, kStartDelaySeconds/4))); 200 } 201 202 PrivetNotificationService::~PrivetNotificationService() { 203 } 204 205 void PrivetNotificationService::DeviceChanged( 206 bool added, 207 const std::string& name, 208 const DeviceDescription& description) { 209 privet_notifications_listener_->DeviceChanged(added, name, description); 210 } 211 212 void PrivetNotificationService::DeviceRemoved(const std::string& name) { 213 privet_notifications_listener_->DeviceRemoved(name); 214 } 215 216 void PrivetNotificationService::DeviceCacheFlushed() { 217 privet_notifications_listener_->DeviceCacheFlushed(); 218 } 219 220 // static 221 bool PrivetNotificationService::IsEnabled() { 222 CommandLine* command_line = CommandLine::ForCurrentProcess(); 223 return !command_line->HasSwitch(switches::kDisableDeviceDiscovery) && 224 !command_line->HasSwitch( 225 switches::kDisableDeviceDiscoveryNotifications) && 226 message_center::IsRichNotificationEnabled(); 227 } 228 229 // static 230 bool PrivetNotificationService::IsForced() { 231 CommandLine* command_line = CommandLine::ForCurrentProcess(); 232 return command_line->HasSwitch(switches::kEnableDeviceDiscoveryNotifications); 233 } 234 235 void PrivetNotificationService::PrivetNotify(bool has_multiple, 236 bool added) { 237 base::string16 product_name = l10n_util::GetStringUTF16( 238 IDS_LOCAL_DISOCVERY_SERVICE_NAME_PRINTER); 239 240 int title_resource = has_multiple ? 241 IDS_LOCAL_DISOCVERY_NOTIFICATION_TITLE_PRINTER_MULTIPLE : 242 IDS_LOCAL_DISOCVERY_NOTIFICATION_TITLE_PRINTER; 243 244 int body_resource = has_multiple ? 245 IDS_LOCAL_DISOCVERY_NOTIFICATION_CONTENTS_PRINTER_MULTIPLE : 246 IDS_LOCAL_DISOCVERY_NOTIFICATION_CONTENTS_PRINTER; 247 248 base::string16 title = l10n_util::GetStringUTF16(title_resource); 249 base::string16 body = l10n_util::GetStringUTF16(body_resource); 250 251 Profile* profile_object = Profile::FromBrowserContext(profile_); 252 message_center::RichNotificationData rich_notification_data; 253 254 rich_notification_data.buttons.push_back( 255 message_center::ButtonInfo(l10n_util::GetStringUTF16( 256 IDS_LOCAL_DISOCVERY_NOTIFICATION_BUTTON_PRINTER))); 257 258 rich_notification_data.buttons.push_back( 259 message_center::ButtonInfo(l10n_util::GetStringUTF16( 260 IDS_LOCAL_DISCOVERY_NOTIFICATIONS_DISABLE_BUTTON_LABEL))); 261 262 Notification notification( 263 message_center::NOTIFICATION_TYPE_SIMPLE, 264 GURL(kPrivetNotificationOriginUrl), 265 title, 266 body, 267 ui::ResourceBundle::GetSharedInstance().GetImageNamed( 268 IDR_LOCAL_DISCOVERY_CLOUDPRINT_ICON), 269 blink::WebTextDirectionDefault, 270 message_center::NotifierId(GURL(kPrivetNotificationOriginUrl)), 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 285 void PrivetNotificationService::PrivetRemoveNotification() { 286 ReportPrivetUmaEvent(PRIVET_NOTIFICATION_CANCELED); 287 g_browser_process->notification_ui_manager()->CancelById( 288 kPrivetNotificationID); 289 } 290 291 void 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 300 void 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 318 void 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 336 PrivetNotificationDelegate::PrivetNotificationDelegate( 337 content::BrowserContext* profile) 338 : profile_(profile) { 339 } 340 341 PrivetNotificationDelegate::~PrivetNotificationDelegate() { 342 } 343 344 std::string PrivetNotificationDelegate::id() const { 345 return kPrivetNotificationID; 346 } 347 348 content::RenderViewHost* PrivetNotificationDelegate::GetRenderViewHost() const { 349 return NULL; 350 } 351 352 void PrivetNotificationDelegate::Display() { 353 } 354 355 void PrivetNotificationDelegate::Error() { 356 LOG(ERROR) << "Error displaying privet notification"; 357 } 358 359 void PrivetNotificationDelegate::Close(bool by_user) { 360 } 361 362 void PrivetNotificationDelegate::Click() { 363 } 364 365 void 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 375 void PrivetNotificationDelegate::OpenTab(const GURL& url) { 376 Profile* profile_obj = Profile::FromBrowserContext(profile_); 377 378 chrome::NavigateParams params(profile_obj, 379 url, 380 content::PAGE_TRANSITION_AUTO_TOPLEVEL); 381 params.disposition = NEW_FOREGROUND_TAB; 382 chrome::Navigate(¶ms); 383 } 384 385 void PrivetNotificationDelegate::DisableNotifications() { 386 Profile* profile_obj = Profile::FromBrowserContext(profile_); 387 388 profile_obj->GetPrefs()->SetBoolean( 389 prefs::kLocalDiscoveryNotificationsEnabled, 390 false); 391 } 392 393 } // namespace local_discovery 394