privet_notifications.cc revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
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 "content/public/common/page_transition_types.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/notifier_settings.h" 40 41#if defined(ENABLE_MDNS) 42#include "chrome/browser/local_discovery/privet_traffic_detector.h" 43#endif 44 45namespace local_discovery { 46 47namespace { 48 49const int kTenMinutesInSeconds = 600; 50const char kPrivetInfoKeyUptime[] = "uptime"; 51const char kPrivetNotificationID[] = "privet_notification"; 52const char kPrivetNotificationOriginUrl[] = "chrome://devices"; 53const int kStartDelaySeconds = 5; 54 55enum 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 67void ReportPrivetUmaEvent(PrivetNotificationsEvent privet_event) { 68 UMA_HISTOGRAM_ENUMERATION("LocalDiscovery.PrivetNotificationsEvent", 69 privet_event, PRIVET_EVENT_MAX); 70} 71 72} // namespace 73 74PrivetNotificationsListener::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 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 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 137void 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 154void 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 166void 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 182void 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 191PrivetNotificationsListener::DeviceContext::DeviceContext() { 192} 193 194PrivetNotificationsListener::DeviceContext::~DeviceContext() { 195} 196 197PrivetNotificationService::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 207PrivetNotificationService::~PrivetNotificationService() { 208} 209 210void PrivetNotificationService::DeviceChanged( 211 bool added, 212 const std::string& name, 213 const DeviceDescription& description) { 214 privet_notifications_listener_->DeviceChanged(added, name, description); 215} 216 217void PrivetNotificationService::DeviceRemoved(const std::string& name) { 218 privet_notifications_listener_->DeviceRemoved(name); 219} 220 221void PrivetNotificationService::DeviceCacheFlushed() { 222 privet_notifications_listener_->DeviceCacheFlushed(); 223} 224 225// static 226bool PrivetNotificationService::IsEnabled() { 227 CommandLine* command_line = CommandLine::ForCurrentProcess(); 228 return !command_line->HasSwitch( 229 switches::kDisableDeviceDiscoveryNotifications); 230} 231 232// static 233bool PrivetNotificationService::IsForced() { 234 CommandLine* command_line = CommandLine::ForCurrentProcess(); 235 return command_line->HasSwitch(switches::kEnableDeviceDiscoveryNotifications); 236} 237 238void 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 288void PrivetNotificationService::PrivetRemoveNotification() { 289 ReportPrivetUmaEvent(PRIVET_NOTIFICATION_CANCELED); 290 g_browser_process->notification_ui_manager()->CancelById( 291 kPrivetNotificationID); 292} 293 294void PrivetNotificationService::Start() { 295#if defined(CHROMEOS) 296 SigninManagerBase* signin_manager = 297 SigninManagerFactory::GetForProfileIfExists( 298 Profile::FromBrowserContext(profile_)); 299 300 if (!signin_manager || signin_manager->GetAuthenticatedUsername().empty()) 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 312void 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 340void 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(new PrivetDeviceListerImpl(service_discovery_client_, 347 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 359PrivetNotificationDelegate::PrivetNotificationDelegate( 360 content::BrowserContext* profile) 361 : profile_(profile) { 362} 363 364PrivetNotificationDelegate::~PrivetNotificationDelegate() { 365} 366 367std::string PrivetNotificationDelegate::id() const { 368 return kPrivetNotificationID; 369} 370 371content::WebContents* PrivetNotificationDelegate::GetWebContents() const { 372 return NULL; 373} 374 375void PrivetNotificationDelegate::Display() { 376} 377 378void PrivetNotificationDelegate::Error() { 379 LOG(ERROR) << "Error displaying privet notification"; 380} 381 382void PrivetNotificationDelegate::Close(bool by_user) { 383} 384 385void PrivetNotificationDelegate::Click() { 386} 387 388void 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 398void PrivetNotificationDelegate::OpenTab(const GURL& url) { 399 Profile* profile_obj = Profile::FromBrowserContext(profile_); 400 401 chrome::NavigateParams params(profile_obj, 402 url, 403 content::PAGE_TRANSITION_AUTO_TOPLEVEL); 404 params.disposition = NEW_FOREGROUND_TAB; 405 chrome::Navigate(¶ms); 406} 407 408void 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