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