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