privet_notifications.cc revision 0529e5d033099cbfc42635f6f6183833b09dff6e
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 "components/signin/core/browser/signin_manager_base.h" 31#include "content/public/browser/browser_context.h" 32#include "content/public/browser/navigation_controller.h" 33#include "content/public/browser/web_contents.h" 34#include "content/public/common/page_transition_types.h" 35#include "grit/generated_resources.h" 36#include "grit/theme_resources.h" 37#include "ui/base/l10n/l10n_util.h" 38#include "ui/base/resource/resource_bundle.h" 39#include "ui/message_center/message_center_util.h" 40#include "ui/message_center/notifier_settings.h" 41 42#if defined(ENABLE_MDNS) 43#include "chrome/browser/local_discovery/privet_traffic_detector.h" 44#endif 45 46namespace local_discovery { 47 48namespace { 49 50const int kTenMinutesInSeconds = 600; 51const char kPrivetInfoKeyUptime[] = "uptime"; 52const char kPrivetNotificationID[] = "privet_notification"; 53const char kPrivetNotificationOriginUrl[] = "chrome://devices"; 54const int kStartDelaySeconds = 5; 55 56enum PrivetNotificationsEvent { 57 PRIVET_SERVICE_STARTED, 58 PRIVET_LISTER_STARTED, 59 PRIVET_DEVICE_CHANGED, 60 PRIVET_INFO_DONE, 61 PRIVET_NOTIFICATION_SHOWN, 62 PRIVET_NOTIFICATION_CANCELED, 63 PRIVET_NOTIFICATION_CLICKED, 64 PRIVET_DISABLE_NOTIFICATIONS_CLICKED, 65 PRIVET_EVENT_MAX, 66}; 67 68void ReportPrivetUmaEvent(PrivetNotificationsEvent privet_event) { 69 UMA_HISTOGRAM_ENUMERATION("LocalDiscovery.PrivetNotificationsEvent", 70 privet_event, PRIVET_EVENT_MAX); 71} 72 73} // namespace 74 75PrivetNotificationsListener::PrivetNotificationsListener( 76 scoped_ptr<PrivetHTTPAsynchronousFactory> privet_http_factory, 77 Delegate* delegate) : delegate_(delegate), devices_active_(0) { 78 privet_http_factory_.swap(privet_http_factory); 79} 80 81PrivetNotificationsListener::~PrivetNotificationsListener() { 82} 83 84void 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 118void PrivetNotificationsListener::CreateInfoOperation( 119 scoped_ptr<PrivetHTTPClient> http_client) { 120 std::string name = http_client->GetName(); 121 DeviceContextMap::iterator device_iter = devices_seen_.find(name); 122 DCHECK(device_iter != devices_seen_.end()); 123 DeviceContext* device = device_iter->second.get(); 124 device->privet_http.swap(http_client); 125 device->info_operation = device->privet_http->CreateInfoOperation( 126 base::Bind(&PrivetNotificationsListener::OnPrivetInfoDone, 127 base::Unretained(this), 128 device)); 129 device->info_operation->Start(); 130} 131 132void PrivetNotificationsListener::OnPrivetInfoDone( 133 DeviceContext* device, 134 const base::DictionaryValue* json_value) { 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 149void 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 161void 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 177void PrivetNotificationsListener::NotifyDeviceRemoved() { 178 devices_active_--; 179 if (devices_active_ == 0) { 180 delegate_->PrivetRemoveNotification(); 181 } else { 182 delegate_->PrivetNotify(devices_active_ > 1, false); 183 } 184} 185 186PrivetNotificationsListener::DeviceContext::DeviceContext() { 187} 188 189PrivetNotificationsListener::DeviceContext::~DeviceContext() { 190} 191 192PrivetNotificationService::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 202PrivetNotificationService::~PrivetNotificationService() { 203} 204 205void PrivetNotificationService::DeviceChanged( 206 bool added, 207 const std::string& name, 208 const DeviceDescription& description) { 209 privet_notifications_listener_->DeviceChanged(added, name, description); 210} 211 212void PrivetNotificationService::DeviceRemoved(const std::string& name) { 213 privet_notifications_listener_->DeviceRemoved(name); 214} 215 216void PrivetNotificationService::DeviceCacheFlushed() { 217 privet_notifications_listener_->DeviceCacheFlushed(); 218} 219 220// static 221bool PrivetNotificationService::IsEnabled() { 222 CommandLine* command_line = CommandLine::ForCurrentProcess(); 223 return !command_line->HasSwitch(switches::kDisableDeviceDiscovery) && 224 !command_line->HasSwitch(switches::kDisableDeviceDiscoveryNotifications); 225} 226 227// static 228bool PrivetNotificationService::IsForced() { 229 CommandLine* command_line = CommandLine::ForCurrentProcess(); 230 return command_line->HasSwitch(switches::kEnableDeviceDiscoveryNotifications); 231} 232 233void PrivetNotificationService::PrivetNotify(bool has_multiple, 234 bool added) { 235 base::string16 product_name = l10n_util::GetStringUTF16( 236 IDS_LOCAL_DISOCVERY_SERVICE_NAME_PRINTER); 237 238 int title_resource = has_multiple ? 239 IDS_LOCAL_DISOCVERY_NOTIFICATION_TITLE_PRINTER_MULTIPLE : 240 IDS_LOCAL_DISOCVERY_NOTIFICATION_TITLE_PRINTER; 241 242 int body_resource = has_multiple ? 243 IDS_LOCAL_DISOCVERY_NOTIFICATION_CONTENTS_PRINTER_MULTIPLE : 244 IDS_LOCAL_DISOCVERY_NOTIFICATION_CONTENTS_PRINTER; 245 246 base::string16 title = l10n_util::GetStringUTF16(title_resource); 247 base::string16 body = l10n_util::GetStringUTF16(body_resource); 248 249 Profile* profile_object = Profile::FromBrowserContext(profile_); 250 message_center::RichNotificationData rich_notification_data; 251 252 rich_notification_data.buttons.push_back( 253 message_center::ButtonInfo(l10n_util::GetStringUTF16( 254 IDS_LOCAL_DISOCVERY_NOTIFICATION_BUTTON_PRINTER))); 255 256 rich_notification_data.buttons.push_back( 257 message_center::ButtonInfo(l10n_util::GetStringUTF16( 258 IDS_LOCAL_DISCOVERY_NOTIFICATIONS_DISABLE_BUTTON_LABEL))); 259 260 Notification notification( 261 message_center::NOTIFICATION_TYPE_SIMPLE, 262 GURL(kPrivetNotificationOriginUrl), 263 title, 264 body, 265 ui::ResourceBundle::GetSharedInstance().GetImageNamed( 266 IDR_LOCAL_DISCOVERY_CLOUDPRINT_ICON), 267 blink::WebTextDirectionDefault, 268 message_center::NotifierId(GURL(kPrivetNotificationOriginUrl)), 269 product_name, 270 base::UTF8ToUTF16(kPrivetNotificationID), 271 rich_notification_data, 272 new PrivetNotificationDelegate(profile_)); 273 274 bool updated = g_browser_process->notification_ui_manager()->Update( 275 notification, profile_object); 276 if (!updated && added && !LocalDiscoveryUIHandler::GetHasVisible()) { 277 ReportPrivetUmaEvent(PRIVET_NOTIFICATION_SHOWN); 278 g_browser_process->notification_ui_manager()->Add(notification, 279 profile_object); 280 } 281} 282 283void PrivetNotificationService::PrivetRemoveNotification() { 284 ReportPrivetUmaEvent(PRIVET_NOTIFICATION_CANCELED); 285 g_browser_process->notification_ui_manager()->CancelById( 286 kPrivetNotificationID); 287} 288 289void PrivetNotificationService::Start() { 290#if defined(CHROMEOS) 291 SigninManagerBase* signin_manager = 292 SigninManagerFactory::GetForProfileIfExists( 293 Profile::FromBrowserContext(profile_)); 294 295 if (!signin_manager || signin_manager->GetAuthenticatedUsername().empty()) 296 return; 297#endif 298 299 enable_privet_notification_member_.Init( 300 prefs::kLocalDiscoveryNotificationsEnabled, 301 Profile::FromBrowserContext(profile_)->GetPrefs(), 302 base::Bind(&PrivetNotificationService::OnNotificationsEnabledChanged, 303 base::Unretained(this))); 304 OnNotificationsEnabledChanged(); 305} 306 307void PrivetNotificationService::OnNotificationsEnabledChanged() { 308#if defined(ENABLE_MDNS) 309 if (IsForced()) { 310 StartLister(); 311 } else if (*enable_privet_notification_member_) { 312 ReportPrivetUmaEvent(PRIVET_SERVICE_STARTED); 313 traffic_detector_ = 314 new PrivetTrafficDetector( 315 net::ADDRESS_FAMILY_IPV4, 316 base::Bind(&PrivetNotificationService::StartLister, AsWeakPtr())); 317 traffic_detector_->Start(); 318 } else { 319 traffic_detector_ = NULL; 320 device_lister_.reset(); 321 service_discovery_client_ = NULL; 322 privet_notifications_listener_.reset(); 323 } 324#else 325 if (IsForced() || *enable_privet_notification_member_) { 326 StartLister(); 327 } else { 328 device_lister_.reset(); 329 service_discovery_client_ = NULL; 330 privet_notifications_listener_.reset(); 331 } 332#endif 333} 334 335void PrivetNotificationService::StartLister() { 336 ReportPrivetUmaEvent(PRIVET_LISTER_STARTED); 337#if defined(ENABLE_MDNS) 338 traffic_detector_ = NULL; 339#endif // ENABLE_MDNS 340 service_discovery_client_ = ServiceDiscoverySharedClient::GetInstance(); 341 device_lister_.reset(new PrivetDeviceListerImpl(service_discovery_client_, 342 this)); 343 device_lister_->Start(); 344 device_lister_->DiscoverNewDevices(false); 345 346 scoped_ptr<PrivetHTTPAsynchronousFactory> http_factory( 347 PrivetHTTPAsynchronousFactory::CreateInstance( 348 service_discovery_client_.get(), profile_->GetRequestContext())); 349 350 privet_notifications_listener_.reset(new PrivetNotificationsListener( 351 http_factory.Pass(), this)); 352} 353 354PrivetNotificationDelegate::PrivetNotificationDelegate( 355 content::BrowserContext* profile) 356 : profile_(profile) { 357} 358 359PrivetNotificationDelegate::~PrivetNotificationDelegate() { 360} 361 362std::string PrivetNotificationDelegate::id() const { 363 return kPrivetNotificationID; 364} 365 366content::WebContents* PrivetNotificationDelegate::GetWebContents() const { 367 return NULL; 368} 369 370void PrivetNotificationDelegate::Display() { 371} 372 373void PrivetNotificationDelegate::Error() { 374 LOG(ERROR) << "Error displaying privet notification"; 375} 376 377void PrivetNotificationDelegate::Close(bool by_user) { 378} 379 380void PrivetNotificationDelegate::Click() { 381} 382 383void PrivetNotificationDelegate::ButtonClick(int button_index) { 384 if (button_index == 0) { 385 ReportPrivetUmaEvent(PRIVET_NOTIFICATION_CLICKED); 386 OpenTab(GURL(kPrivetNotificationOriginUrl)); 387 } else if (button_index == 1) { 388 ReportPrivetUmaEvent(PRIVET_DISABLE_NOTIFICATIONS_CLICKED); 389 DisableNotifications(); 390 } 391} 392 393void PrivetNotificationDelegate::OpenTab(const GURL& url) { 394 Profile* profile_obj = Profile::FromBrowserContext(profile_); 395 396 chrome::NavigateParams params(profile_obj, 397 url, 398 content::PAGE_TRANSITION_AUTO_TOPLEVEL); 399 params.disposition = NEW_FOREGROUND_TAB; 400 chrome::Navigate(¶ms); 401} 402 403void PrivetNotificationDelegate::DisableNotifications() { 404 Profile* profile_obj = Profile::FromBrowserContext(profile_); 405 406 profile_obj->GetPrefs()->SetBoolean( 407 prefs::kLocalDiscoveryNotificationsEnabled, 408 false); 409} 410 411} // namespace local_discovery 412