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(&params);
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