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