privet_notifications.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
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/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) : delegate_(delegate), devices_active_(0) {
77  privet_http_factory_.swap(privet_http_factory);
78}
79
80PrivetNotificationsListener::~PrivetNotificationsListener() {
81}
82
83void PrivetNotificationsListener::DeviceChanged(
84    bool added,
85    const std::string& name,
86    const DeviceDescription& description) {
87  ReportPrivetUmaEvent(PRIVET_DEVICE_CHANGED);
88  DeviceContextMap::iterator found = devices_seen_.find(name);
89  if (found != devices_seen_.end()) {
90    if (!description.id.empty() &&  // Device is registered
91        found->second->notification_may_be_active) {
92      found->second->notification_may_be_active = false;
93      NotifyDeviceRemoved();
94    }
95    return;  // Already saw this device.
96  }
97
98  linked_ptr<DeviceContext> device_context(new DeviceContext);
99
100  device_context->notification_may_be_active = false;
101  device_context->registered = !description.id.empty();
102
103  devices_seen_.insert(make_pair(name, device_context));
104
105  if (!device_context->registered) {
106    device_context->privet_http_resolution =
107        privet_http_factory_->CreatePrivetHTTP(
108            name,
109            description.address,
110            base::Bind(&PrivetNotificationsListener::CreateInfoOperation,
111                       base::Unretained(this)));
112
113    device_context->privet_http_resolution->Start();
114  }
115}
116
117void PrivetNotificationsListener::CreateInfoOperation(
118    scoped_ptr<PrivetHTTPClient> http_client) {
119  std::string name = http_client->GetName();
120  DeviceContextMap::iterator device_iter = devices_seen_.find(name);
121  DCHECK(device_iter != devices_seen_.end());
122  DeviceContext* device = device_iter->second.get();
123  device->privet_http.swap(http_client);
124  device->info_operation = device->privet_http->CreateInfoOperation(
125      base::Bind(&PrivetNotificationsListener::OnPrivetInfoDone,
126                 base::Unretained(this),
127                 device));
128  device->info_operation->Start();
129}
130
131void PrivetNotificationsListener::OnPrivetInfoDone(
132    DeviceContext* device,
133    const base::DictionaryValue* json_value) {
134  int uptime;
135
136  if (!json_value ||
137      !json_value->GetInteger(kPrivetInfoKeyUptime, &uptime) ||
138      uptime > kTenMinutesInSeconds) {
139    return;
140  }
141
142  DCHECK(!device->notification_may_be_active);
143  device->notification_may_be_active = true;
144  devices_active_++;
145  delegate_->PrivetNotify(devices_active_ > 1, true);
146}
147
148void PrivetNotificationsListener::DeviceRemoved(const std::string& name) {
149  DCHECK_EQ(1u, devices_seen_.count(name));
150  DeviceContextMap::iterator device_iter = devices_seen_.find(name);
151  DCHECK(device_iter != devices_seen_.end());
152  DeviceContext* device = device_iter->second.get();
153
154  device->info_operation.reset();
155  device->privet_http_resolution.reset();
156  device->notification_may_be_active = false;
157  NotifyDeviceRemoved();
158}
159
160void PrivetNotificationsListener::DeviceCacheFlushed() {
161  for (DeviceContextMap::iterator i = devices_seen_.begin();
162       i != devices_seen_.end(); ++i) {
163    DeviceContext* device = i->second.get();
164
165    device->info_operation.reset();
166    device->privet_http_resolution.reset();
167    if (device->notification_may_be_active) {
168      device->notification_may_be_active = false;
169    }
170  }
171
172  devices_active_ = 0;
173  delegate_->PrivetRemoveNotification();
174}
175
176void PrivetNotificationsListener::NotifyDeviceRemoved() {
177  devices_active_--;
178  if (devices_active_ == 0) {
179    delegate_->PrivetRemoveNotification();
180  } else {
181    delegate_->PrivetNotify(devices_active_ > 1, false);
182  }
183}
184
185PrivetNotificationsListener::DeviceContext::DeviceContext() {
186}
187
188PrivetNotificationsListener::DeviceContext::~DeviceContext() {
189}
190
191PrivetNotificationService::PrivetNotificationService(
192    content::BrowserContext* profile)
193    : profile_(profile) {
194  base::MessageLoop::current()->PostDelayedTask(
195      FROM_HERE,
196      base::Bind(&PrivetNotificationService::Start, AsWeakPtr()),
197      base::TimeDelta::FromSeconds(kStartDelaySeconds +
198                                   base::RandInt(0, kStartDelaySeconds/4)));
199}
200
201PrivetNotificationService::~PrivetNotificationService() {
202}
203
204void PrivetNotificationService::DeviceChanged(
205    bool added,
206    const std::string& name,
207    const DeviceDescription& description) {
208  privet_notifications_listener_->DeviceChanged(added, name, description);
209}
210
211void PrivetNotificationService::DeviceRemoved(const std::string& name) {
212  privet_notifications_listener_->DeviceRemoved(name);
213}
214
215void PrivetNotificationService::DeviceCacheFlushed() {
216  privet_notifications_listener_->DeviceCacheFlushed();
217}
218
219// static
220bool PrivetNotificationService::IsEnabled() {
221  CommandLine* command_line = CommandLine::ForCurrentProcess();
222  return !command_line->HasSwitch(switches::kDisableDeviceDiscovery) &&
223      !command_line->HasSwitch(switches::kDisableDeviceDiscoveryNotifications);
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 defined(ENABLE_MDNS)
308  if (IsForced()) {
309    StartLister();
310  } else if (*enable_privet_notification_member_) {
311    ReportPrivetUmaEvent(PRIVET_SERVICE_STARTED);
312    traffic_detector_ =
313        new PrivetTrafficDetector(
314            net::ADDRESS_FAMILY_IPV4,
315            base::Bind(&PrivetNotificationService::StartLister, AsWeakPtr()));
316    traffic_detector_->Start();
317  } else {
318    traffic_detector_ = NULL;
319    device_lister_.reset();
320    service_discovery_client_ = NULL;
321    privet_notifications_listener_.reset();
322  }
323#else
324  if (IsForced() || *enable_privet_notification_member_) {
325    StartLister();
326  } else {
327    device_lister_.reset();
328    service_discovery_client_ = NULL;
329    privet_notifications_listener_.reset();
330  }
331#endif
332}
333
334void PrivetNotificationService::StartLister() {
335  ReportPrivetUmaEvent(PRIVET_LISTER_STARTED);
336#if defined(ENABLE_MDNS)
337  traffic_detector_ = NULL;
338#endif  // ENABLE_MDNS
339  service_discovery_client_ = ServiceDiscoverySharedClient::GetInstance();
340  device_lister_.reset(new PrivetDeviceListerImpl(service_discovery_client_,
341                                                  this));
342  device_lister_->Start();
343  device_lister_->DiscoverNewDevices(false);
344
345  scoped_ptr<PrivetHTTPAsynchronousFactory> http_factory(
346      PrivetHTTPAsynchronousFactory::CreateInstance(
347          service_discovery_client_.get(), profile_->GetRequestContext()));
348
349  privet_notifications_listener_.reset(new PrivetNotificationsListener(
350      http_factory.Pass(), this));
351}
352
353PrivetNotificationDelegate::PrivetNotificationDelegate(
354    content::BrowserContext* profile)
355    :  profile_(profile) {
356}
357
358PrivetNotificationDelegate::~PrivetNotificationDelegate() {
359}
360
361std::string PrivetNotificationDelegate::id() const {
362  return kPrivetNotificationID;
363}
364
365content::WebContents* PrivetNotificationDelegate::GetWebContents() const {
366  return NULL;
367}
368
369void PrivetNotificationDelegate::Display() {
370}
371
372void PrivetNotificationDelegate::Error() {
373  LOG(ERROR) << "Error displaying privet notification";
374}
375
376void PrivetNotificationDelegate::Close(bool by_user) {
377}
378
379void PrivetNotificationDelegate::Click() {
380}
381
382void PrivetNotificationDelegate::ButtonClick(int button_index) {
383  if (button_index == 0) {
384    ReportPrivetUmaEvent(PRIVET_NOTIFICATION_CLICKED);
385    OpenTab(GURL(kPrivetNotificationOriginUrl));
386  } else if (button_index == 1) {
387    ReportPrivetUmaEvent(PRIVET_DISABLE_NOTIFICATIONS_CLICKED);
388    DisableNotifications();
389  }
390}
391
392void PrivetNotificationDelegate::OpenTab(const GURL& url) {
393  Profile* profile_obj = Profile::FromBrowserContext(profile_);
394
395  chrome::NavigateParams params(profile_obj,
396                              url,
397                              content::PAGE_TRANSITION_AUTO_TOPLEVEL);
398  params.disposition = NEW_FOREGROUND_TAB;
399  chrome::Navigate(&params);
400}
401
402void PrivetNotificationDelegate::DisableNotifications() {
403  Profile* profile_obj = Profile::FromBrowserContext(profile_);
404
405  profile_obj->GetPrefs()->SetBoolean(
406      prefs::kLocalDiscoveryNotificationsEnabled,
407      false);
408}
409
410}  // namespace local_discovery
411