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.
5 #include "chrome/browser/local_discovery/privet_notifications.h"
8 #include "base/command_line.h"
9 #include "base/location.h"
10 #include "base/metrics/histogram.h"
11 #include "base/prefs/pref_service.h"
12 #include "base/rand_util.h"
13 #include "base/single_thread_task_runner.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/thread_task_runner_handle.h"
16 #include "chrome/browser/browser_process.h"
17 #include "chrome/browser/local_discovery/privet_device_lister_impl.h"
18 #include "chrome/browser/local_discovery/privet_http_asynchronous_factory.h"
19 #include "chrome/browser/local_discovery/service_discovery_shared_client.h"
20 #include "chrome/browser/notifications/notification.h"
21 #include "chrome/browser/notifications/notification_ui_manager.h"
22 #include "chrome/browser/profiles/profile.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 "chrome/grit/generated_resources.h"
33 #include "components/signin/core/browser/signin_manager_base.h"
34 #include "content/public/browser/browser_context.h"
35 #include "content/public/browser/navigation_controller.h"
36 #include "content/public/browser/web_contents.h"
37 #include "grit/theme_resources.h"
38 #include "ui/base/l10n/l10n_util.h"
39 #include "ui/base/page_transition_types.h"
40 #include "ui/base/resource/resource_bundle.h"
41 #include "ui/message_center/notifier_settings.h"
43 #if defined(ENABLE_MDNS)
44 #include "chrome/browser/local_discovery/privet_traffic_detector.h"
47 namespace local_discovery
{
51 const int kTenMinutesInSeconds
= 600;
52 const char kPrivetInfoKeyUptime
[] = "uptime";
53 const char kPrivetNotificationID
[] = "privet_notification";
54 const char kPrivetNotificationOriginUrl
[] = "chrome://devices";
55 const int kStartDelaySeconds
= 5;
57 enum PrivetNotificationsEvent
{
58 PRIVET_SERVICE_STARTED
,
59 PRIVET_LISTER_STARTED
,
60 PRIVET_DEVICE_CHANGED
,
62 PRIVET_NOTIFICATION_SHOWN
,
63 PRIVET_NOTIFICATION_CANCELED
,
64 PRIVET_NOTIFICATION_CLICKED
,
65 PRIVET_DISABLE_NOTIFICATIONS_CLICKED
,
69 void ReportPrivetUmaEvent(PrivetNotificationsEvent privet_event
) {
70 UMA_HISTOGRAM_ENUMERATION("LocalDiscovery.PrivetNotificationsEvent",
71 privet_event
, PRIVET_EVENT_MAX
);
76 PrivetNotificationsListener::PrivetNotificationsListener(
77 scoped_ptr
<PrivetHTTPAsynchronousFactory
> privet_http_factory
,
79 : delegate_(delegate
), devices_active_(0) {
80 privet_http_factory_
.swap(privet_http_factory
);
83 PrivetNotificationsListener::~PrivetNotificationsListener() {
86 void PrivetNotificationsListener::DeviceChanged(
88 const std::string
& name
,
89 const DeviceDescription
& description
) {
90 ReportPrivetUmaEvent(PRIVET_DEVICE_CHANGED
);
91 DeviceContextMap::iterator found
= devices_seen_
.find(name
);
92 if (found
!= devices_seen_
.end()) {
93 if (!description
.id
.empty() && // Device is registered
94 found
->second
->notification_may_be_active
) {
95 found
->second
->notification_may_be_active
= false;
96 NotifyDeviceRemoved();
98 return; // Already saw this device.
101 linked_ptr
<DeviceContext
> device_context(new DeviceContext
);
103 device_context
->notification_may_be_active
= false;
104 device_context
->registered
= !description
.id
.empty();
106 devices_seen_
.insert(make_pair(name
, device_context
));
108 if (!device_context
->registered
) {
109 device_context
->privet_http_resolution
=
110 privet_http_factory_
->CreatePrivetHTTP(name
);
111 device_context
->privet_http_resolution
->Start(
113 base::Bind(&PrivetNotificationsListener::CreateInfoOperation
,
114 base::Unretained(this)));
118 void PrivetNotificationsListener::CreateInfoOperation(
119 scoped_ptr
<PrivetHTTPClient
> http_client
) {
121 // Do nothing if resolution fails.
125 std::string name
= http_client
->GetName();
126 DeviceContextMap::iterator device_iter
= devices_seen_
.find(name
);
127 if (device_iter
== devices_seen_
.end())
129 DeviceContext
* device
= device_iter
->second
.get();
130 device
->privet_http
.swap(http_client
);
131 device
->info_operation
= device
->privet_http
->CreateInfoOperation(
132 base::Bind(&PrivetNotificationsListener::OnPrivetInfoDone
,
133 base::Unretained(this),
135 device
->info_operation
->Start();
138 void PrivetNotificationsListener::OnPrivetInfoDone(
139 DeviceContext
* device
,
140 const base::DictionaryValue
* json_value
) {
144 !json_value
->GetInteger(kPrivetInfoKeyUptime
, &uptime
) ||
145 uptime
> kTenMinutesInSeconds
) {
149 DCHECK(!device
->notification_may_be_active
);
150 device
->notification_may_be_active
= true;
152 delegate_
->PrivetNotify(devices_active_
, true);
155 void PrivetNotificationsListener::DeviceRemoved(const std::string
& name
) {
156 DeviceContextMap::iterator device_iter
= devices_seen_
.find(name
);
157 if (device_iter
== devices_seen_
.end())
159 DeviceContext
* device
= device_iter
->second
.get();
161 device
->info_operation
.reset();
162 device
->privet_http_resolution
.reset();
163 device
->notification_may_be_active
= false;
164 NotifyDeviceRemoved();
167 void PrivetNotificationsListener::DeviceCacheFlushed() {
168 for (DeviceContextMap::iterator i
= devices_seen_
.begin();
169 i
!= devices_seen_
.end(); ++i
) {
170 DeviceContext
* device
= i
->second
.get();
172 device
->info_operation
.reset();
173 device
->privet_http_resolution
.reset();
174 if (device
->notification_may_be_active
) {
175 device
->notification_may_be_active
= false;
180 delegate_
->PrivetRemoveNotification();
183 void PrivetNotificationsListener::NotifyDeviceRemoved() {
185 if (devices_active_
== 0) {
186 delegate_
->PrivetRemoveNotification();
188 delegate_
->PrivetNotify(devices_active_
, false);
192 PrivetNotificationsListener::DeviceContext::DeviceContext() {
195 PrivetNotificationsListener::DeviceContext::~DeviceContext() {
198 PrivetNotificationService::PrivetNotificationService(
199 content::BrowserContext
* profile
)
200 : profile_(profile
) {
201 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
202 FROM_HERE
, base::Bind(&PrivetNotificationService::Start
, AsWeakPtr()),
203 base::TimeDelta::FromSeconds(kStartDelaySeconds
+
204 base::RandInt(0, kStartDelaySeconds
/ 4)));
207 PrivetNotificationService::~PrivetNotificationService() {
210 void PrivetNotificationService::DeviceChanged(
212 const std::string
& name
,
213 const DeviceDescription
& description
) {
214 privet_notifications_listener_
->DeviceChanged(added
, name
, description
);
217 void PrivetNotificationService::DeviceRemoved(const std::string
& name
) {
218 privet_notifications_listener_
->DeviceRemoved(name
);
221 void PrivetNotificationService::DeviceCacheFlushed() {
222 privet_notifications_listener_
->DeviceCacheFlushed();
226 bool PrivetNotificationService::IsEnabled() {
227 base::CommandLine
* command_line
= base::CommandLine::ForCurrentProcess();
228 return !command_line
->HasSwitch(
229 switches::kDisableDeviceDiscoveryNotifications
);
233 bool PrivetNotificationService::IsForced() {
234 base::CommandLine
* command_line
= base::CommandLine::ForCurrentProcess();
235 return command_line
->HasSwitch(switches::kEnableDeviceDiscoveryNotifications
);
238 void PrivetNotificationService::PrivetNotify(int devices_active
,
240 base::string16 product_name
= l10n_util::GetStringUTF16(
241 IDS_LOCAL_DISCOVERY_SERVICE_NAME_PRINTER
);
243 base::string16 title
= l10n_util::GetPluralStringFUTF16(
244 IDS_LOCAL_DISCOVERY_NOTIFICATION_TITLE_PRINTER
, devices_active
);
245 base::string16 body
= l10n_util::GetPluralStringFUTF16(
246 IDS_LOCAL_DISCOVERY_NOTIFICATION_CONTENTS_PRINTER
, devices_active
);
248 Profile
* profile_object
= Profile::FromBrowserContext(profile_
);
249 message_center::RichNotificationData rich_notification_data
;
251 rich_notification_data
.buttons
.push_back(
252 message_center::ButtonInfo(l10n_util::GetStringUTF16(
253 IDS_LOCAL_DISCOVERY_NOTIFICATION_BUTTON_PRINTER
)));
255 rich_notification_data
.buttons
.push_back(
256 message_center::ButtonInfo(l10n_util::GetStringUTF16(
257 IDS_LOCAL_DISCOVERY_NOTIFICATIONS_DISABLE_BUTTON_LABEL
)));
259 Notification
notification(
260 message_center::NOTIFICATION_TYPE_SIMPLE
,
261 GURL(kPrivetNotificationOriginUrl
),
264 ui::ResourceBundle::GetSharedInstance().GetImageNamed(
265 IDR_LOCAL_DISCOVERY_CLOUDPRINT_ICON
),
266 message_center::NotifierId(GURL(kPrivetNotificationOriginUrl
)),
268 kPrivetNotificationID
,
269 rich_notification_data
,
270 new PrivetNotificationDelegate(profile_
));
272 bool updated
= g_browser_process
->notification_ui_manager()->Update(
273 notification
, profile_object
);
274 if (!updated
&& added
&& !LocalDiscoveryUIHandler::GetHasVisible()) {
275 ReportPrivetUmaEvent(PRIVET_NOTIFICATION_SHOWN
);
276 g_browser_process
->notification_ui_manager()->Add(notification
,
281 void PrivetNotificationService::PrivetRemoveNotification() {
282 ReportPrivetUmaEvent(PRIVET_NOTIFICATION_CANCELED
);
283 Profile
* profile_object
= Profile::FromBrowserContext(profile_
);
284 g_browser_process
->notification_ui_manager()->CancelById(
285 kPrivetNotificationID
,
286 NotificationUIManager::GetProfileID(profile_object
));
289 void PrivetNotificationService::Start() {
290 #if defined(CHROMEOS)
291 SigninManagerBase
* signin_manager
=
292 SigninManagerFactory::GetForProfileIfExists(
293 Profile::FromBrowserContext(profile_
));
295 if (!signin_manager
|| !signin_manager
->IsAuthenticated())
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();
307 void PrivetNotificationService::OnNotificationsEnabledChanged() {
308 #if defined(ENABLE_MDNS)
311 } else if (*enable_privet_notification_member_
) {
312 ReportPrivetUmaEvent(PRIVET_SERVICE_STARTED
);
314 new PrivetTrafficDetector(
315 net::ADDRESS_FAMILY_IPV4
,
316 base::Bind(&PrivetNotificationService::StartLister
, AsWeakPtr()));
317 traffic_detector_
->Start();
319 traffic_detector_
= NULL
;
320 device_lister_
.reset();
321 service_discovery_client_
= NULL
;
322 privet_notifications_listener_
.reset();
325 if (IsForced() || *enable_privet_notification_member_
) {
328 device_lister_
.reset();
329 service_discovery_client_
= NULL
;
330 privet_notifications_listener_
.reset();
335 void 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(
342 new PrivetDeviceListerImpl(service_discovery_client_
.get(), this));
343 device_lister_
->Start();
344 device_lister_
->DiscoverNewDevices(false);
346 scoped_ptr
<PrivetHTTPAsynchronousFactory
> http_factory(
347 PrivetHTTPAsynchronousFactory::CreateInstance(
348 profile_
->GetRequestContext()));
350 privet_notifications_listener_
.reset(new PrivetNotificationsListener(
351 http_factory
.Pass(), this));
354 PrivetNotificationDelegate::PrivetNotificationDelegate(
355 content::BrowserContext
* profile
)
356 : profile_(profile
) {
359 PrivetNotificationDelegate::~PrivetNotificationDelegate() {
362 std::string
PrivetNotificationDelegate::id() const {
363 return kPrivetNotificationID
;
366 void PrivetNotificationDelegate::ButtonClick(int button_index
) {
367 if (button_index
== 0) {
368 ReportPrivetUmaEvent(PRIVET_NOTIFICATION_CLICKED
);
369 OpenTab(GURL(kPrivetNotificationOriginUrl
));
370 } else if (button_index
== 1) {
371 ReportPrivetUmaEvent(PRIVET_DISABLE_NOTIFICATIONS_CLICKED
);
372 DisableNotifications();
376 void PrivetNotificationDelegate::OpenTab(const GURL
& url
) {
377 Profile
* profile_obj
= Profile::FromBrowserContext(profile_
);
379 chrome::NavigateParams
params(profile_obj
,
381 ui::PAGE_TRANSITION_AUTO_TOPLEVEL
);
382 params
.disposition
= NEW_FOREGROUND_TAB
;
383 chrome::Navigate(¶ms
);
386 void PrivetNotificationDelegate::DisableNotifications() {
387 Profile
* profile_obj
= Profile::FromBrowserContext(profile_
);
389 profile_obj
->GetPrefs()->SetBoolean(
390 prefs::kLocalDiscoveryNotificationsEnabled
,
394 } // namespace local_discovery