privet_notifications.cc revision effb81e5f8246d0db0270817048dc992db66e9fb
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(
225          switches::kDisableDeviceDiscoveryNotifications) &&
226      message_center::IsRichNotificationEnabled();
227}
228
229// static
230bool PrivetNotificationService::IsForced() {
231  CommandLine* command_line = CommandLine::ForCurrentProcess();
232  return command_line->HasSwitch(switches::kEnableDeviceDiscoveryNotifications);
233}
234
235void PrivetNotificationService::PrivetNotify(bool has_multiple,
236                                             bool added) {
237  base::string16 product_name = l10n_util::GetStringUTF16(
238      IDS_LOCAL_DISOCVERY_SERVICE_NAME_PRINTER);
239
240  int title_resource = has_multiple ?
241      IDS_LOCAL_DISOCVERY_NOTIFICATION_TITLE_PRINTER_MULTIPLE :
242      IDS_LOCAL_DISOCVERY_NOTIFICATION_TITLE_PRINTER;
243
244  int body_resource = has_multiple ?
245      IDS_LOCAL_DISOCVERY_NOTIFICATION_CONTENTS_PRINTER_MULTIPLE :
246      IDS_LOCAL_DISOCVERY_NOTIFICATION_CONTENTS_PRINTER;
247
248  base::string16 title = l10n_util::GetStringUTF16(title_resource);
249  base::string16 body = l10n_util::GetStringUTF16(body_resource);
250
251  Profile* profile_object = Profile::FromBrowserContext(profile_);
252  message_center::RichNotificationData rich_notification_data;
253
254  rich_notification_data.buttons.push_back(
255      message_center::ButtonInfo(l10n_util::GetStringUTF16(
256          IDS_LOCAL_DISOCVERY_NOTIFICATION_BUTTON_PRINTER)));
257
258  rich_notification_data.buttons.push_back(
259      message_center::ButtonInfo(l10n_util::GetStringUTF16(
260          IDS_LOCAL_DISCOVERY_NOTIFICATIONS_DISABLE_BUTTON_LABEL)));
261
262  Notification notification(
263      message_center::NOTIFICATION_TYPE_SIMPLE,
264      GURL(kPrivetNotificationOriginUrl),
265      title,
266      body,
267      ui::ResourceBundle::GetSharedInstance().GetImageNamed(
268          IDR_LOCAL_DISCOVERY_CLOUDPRINT_ICON),
269      blink::WebTextDirectionDefault,
270      message_center::NotifierId(GURL(kPrivetNotificationOriginUrl)),
271      product_name,
272      base::UTF8ToUTF16(kPrivetNotificationID),
273      rich_notification_data,
274      new PrivetNotificationDelegate(profile_));
275
276  bool updated = g_browser_process->notification_ui_manager()->Update(
277      notification, profile_object);
278  if (!updated && added && !LocalDiscoveryUIHandler::GetHasVisible()) {
279    ReportPrivetUmaEvent(PRIVET_NOTIFICATION_SHOWN);
280    g_browser_process->notification_ui_manager()->Add(notification,
281                                                      profile_object);
282  }
283}
284
285void PrivetNotificationService::PrivetRemoveNotification() {
286  ReportPrivetUmaEvent(PRIVET_NOTIFICATION_CANCELED);
287  g_browser_process->notification_ui_manager()->CancelById(
288      kPrivetNotificationID);
289}
290
291void PrivetNotificationService::Start() {
292#if defined(CHROMEOS)
293  SigninManagerBase* signin_manager =
294      SigninManagerFactory::GetForProfileIfExists(
295          Profile::FromBrowserContext(profile_));
296
297  if (!signin_manager || signin_manager->GetAuthenticatedUsername().empty())
298    return;
299#endif
300
301  enable_privet_notification_member_.Init(
302      prefs::kLocalDiscoveryNotificationsEnabled,
303      Profile::FromBrowserContext(profile_)->GetPrefs(),
304      base::Bind(&PrivetNotificationService::OnNotificationsEnabledChanged,
305                 base::Unretained(this)));
306  OnNotificationsEnabledChanged();
307}
308
309void PrivetNotificationService::OnNotificationsEnabledChanged() {
310#if defined(ENABLE_MDNS)
311  if (IsForced()) {
312    StartLister();
313  } else if (*enable_privet_notification_member_) {
314    ReportPrivetUmaEvent(PRIVET_SERVICE_STARTED);
315    traffic_detector_ =
316        new PrivetTrafficDetector(
317            net::ADDRESS_FAMILY_IPV4,
318            base::Bind(&PrivetNotificationService::StartLister, AsWeakPtr()));
319    traffic_detector_->Start();
320  } else {
321    traffic_detector_ = NULL;
322    device_lister_.reset();
323    service_discovery_client_ = NULL;
324    privet_notifications_listener_.reset();
325  }
326#else
327  if (IsForced() || *enable_privet_notification_member_) {
328    StartLister();
329  } else {
330    device_lister_.reset();
331    service_discovery_client_ = NULL;
332    privet_notifications_listener_.reset();
333  }
334#endif
335}
336
337void PrivetNotificationService::StartLister() {
338  ReportPrivetUmaEvent(PRIVET_LISTER_STARTED);
339#if defined(ENABLE_MDNS)
340  traffic_detector_ = NULL;
341#endif  // ENABLE_MDNS
342  service_discovery_client_ = ServiceDiscoverySharedClient::GetInstance();
343  device_lister_.reset(new PrivetDeviceListerImpl(service_discovery_client_,
344                                                  this));
345  device_lister_->Start();
346  device_lister_->DiscoverNewDevices(false);
347
348  scoped_ptr<PrivetHTTPAsynchronousFactory> http_factory(
349      PrivetHTTPAsynchronousFactory::CreateInstance(
350          service_discovery_client_.get(), profile_->GetRequestContext()));
351
352  privet_notifications_listener_.reset(new PrivetNotificationsListener(
353      http_factory.Pass(), this));
354}
355
356PrivetNotificationDelegate::PrivetNotificationDelegate(
357    content::BrowserContext* profile)
358    :  profile_(profile) {
359}
360
361PrivetNotificationDelegate::~PrivetNotificationDelegate() {
362}
363
364std::string PrivetNotificationDelegate::id() const {
365  return kPrivetNotificationID;
366}
367
368content::RenderViewHost* PrivetNotificationDelegate::GetRenderViewHost() const {
369  return NULL;
370}
371
372void PrivetNotificationDelegate::Display() {
373}
374
375void PrivetNotificationDelegate::Error() {
376  LOG(ERROR) << "Error displaying privet notification";
377}
378
379void PrivetNotificationDelegate::Close(bool by_user) {
380}
381
382void PrivetNotificationDelegate::Click() {
383}
384
385void PrivetNotificationDelegate::ButtonClick(int button_index) {
386  if (button_index == 0) {
387    ReportPrivetUmaEvent(PRIVET_NOTIFICATION_CLICKED);
388    OpenTab(GURL(kPrivetNotificationOriginUrl));
389  } else if (button_index == 1) {
390    ReportPrivetUmaEvent(PRIVET_DISABLE_NOTIFICATIONS_CLICKED);
391    DisableNotifications();
392  }
393}
394
395void PrivetNotificationDelegate::OpenTab(const GURL& url) {
396  Profile* profile_obj = Profile::FromBrowserContext(profile_);
397
398  chrome::NavigateParams params(profile_obj,
399                              url,
400                              content::PAGE_TRANSITION_AUTO_TOPLEVEL);
401  params.disposition = NEW_FOREGROUND_TAB;
402  chrome::Navigate(&params);
403}
404
405void PrivetNotificationDelegate::DisableNotifications() {
406  Profile* profile_obj = Profile::FromBrowserContext(profile_);
407
408  profile_obj->GetPrefs()->SetBoolean(
409      prefs::kLocalDiscoveryNotificationsEnabled,
410      false);
411}
412
413}  // namespace local_discovery
414