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
ReportPrivetUmaEvent(PrivetNotificationsEvent privet_event)67 void ReportPrivetUmaEvent(PrivetNotificationsEvent privet_event) {
68 UMA_HISTOGRAM_ENUMERATION("LocalDiscovery.PrivetNotificationsEvent",
69 privet_event, PRIVET_EVENT_MAX);
70 }
71
72 } // namespace
73
PrivetNotificationsListener(scoped_ptr<PrivetHTTPAsynchronousFactory> privet_http_factory,Delegate * delegate)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
~PrivetNotificationsListener()81 PrivetNotificationsListener::~PrivetNotificationsListener() {
82 }
83
DeviceChanged(bool added,const std::string & name,const DeviceDescription & description)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
CreateInfoOperation(scoped_ptr<PrivetHTTPClient> http_client)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
OnPrivetInfoDone(DeviceContext * device,const base::DictionaryValue * json_value)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
DeviceRemoved(const std::string & name)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
DeviceCacheFlushed()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
NotifyDeviceRemoved()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
DeviceContext()191 PrivetNotificationsListener::DeviceContext::DeviceContext() {
192 }
193
~DeviceContext()194 PrivetNotificationsListener::DeviceContext::~DeviceContext() {
195 }
196
PrivetNotificationService(content::BrowserContext * profile)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
~PrivetNotificationService()207 PrivetNotificationService::~PrivetNotificationService() {
208 }
209
DeviceChanged(bool added,const std::string & name,const DeviceDescription & description)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
DeviceRemoved(const std::string & name)217 void PrivetNotificationService::DeviceRemoved(const std::string& name) {
218 privet_notifications_listener_->DeviceRemoved(name);
219 }
220
DeviceCacheFlushed()221 void PrivetNotificationService::DeviceCacheFlushed() {
222 privet_notifications_listener_->DeviceCacheFlushed();
223 }
224
225 // static
IsEnabled()226 bool PrivetNotificationService::IsEnabled() {
227 CommandLine* command_line = CommandLine::ForCurrentProcess();
228 return !command_line->HasSwitch(
229 switches::kDisableDeviceDiscoveryNotifications);
230 }
231
232 // static
IsForced()233 bool PrivetNotificationService::IsForced() {
234 CommandLine* command_line = CommandLine::ForCurrentProcess();
235 return command_line->HasSwitch(switches::kEnableDeviceDiscoveryNotifications);
236 }
237
PrivetNotify(bool has_multiple,bool added)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
PrivetRemoveNotification()288 void PrivetNotificationService::PrivetRemoveNotification() {
289 ReportPrivetUmaEvent(PRIVET_NOTIFICATION_CANCELED);
290 g_browser_process->notification_ui_manager()->CancelById(
291 kPrivetNotificationID);
292 }
293
Start()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
OnNotificationsEnabledChanged()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
StartLister()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
PrivetNotificationDelegate(content::BrowserContext * profile)359 PrivetNotificationDelegate::PrivetNotificationDelegate(
360 content::BrowserContext* profile)
361 : profile_(profile) {
362 }
363
~PrivetNotificationDelegate()364 PrivetNotificationDelegate::~PrivetNotificationDelegate() {
365 }
366
id() const367 std::string PrivetNotificationDelegate::id() const {
368 return kPrivetNotificationID;
369 }
370
GetWebContents() const371 content::WebContents* PrivetNotificationDelegate::GetWebContents() const {
372 return NULL;
373 }
374
Display()375 void PrivetNotificationDelegate::Display() {
376 }
377
Error()378 void PrivetNotificationDelegate::Error() {
379 LOG(ERROR) << "Error displaying privet notification";
380 }
381
Close(bool by_user)382 void PrivetNotificationDelegate::Close(bool by_user) {
383 }
384
Click()385 void PrivetNotificationDelegate::Click() {
386 }
387
ButtonClick(int button_index)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
OpenTab(const GURL & url)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
DisableNotifications()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