privet_notifications.cc revision 68043e1e95eeb07d5cae7aca370b26518b0867d6
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/privet_traffic_detector.h"
18#include "chrome/browser/local_discovery/service_discovery_shared_client.h"
19#include "chrome/browser/notifications/notification.h"
20#include "chrome/browser/notifications/notification_ui_manager.h"
21#include "chrome/browser/profiles/profile.h"
22#include "chrome/browser/ui/browser.h"
23#include "chrome/browser/ui/browser_finder.h"
24#include "chrome/browser/ui/host_desktop.h"
25#include "chrome/browser/ui/tabs/tab_strip_model.h"
26#include "chrome/browser/ui/webui/local_discovery/local_discovery_ui_handler.h"
27#include "chrome/common/chrome_switches.h"
28#include "chrome/common/pref_names.h"
29#include "content/public/browser/browser_context.h"
30#include "content/public/browser/navigation_controller.h"
31#include "content/public/browser/web_contents.h"
32#include "content/public/common/page_transition_types.h"
33#include "grit/generated_resources.h"
34#include "grit/theme_resources.h"
35#include "ui/base/l10n/l10n_util.h"
36#include "ui/base/resource/resource_bundle.h"
37#include "ui/message_center/message_center_util.h"
38#include "ui/message_center/notifier_settings.h"
39
40namespace local_discovery {
41
42namespace {
43
44const int kTenMinutesInSeconds = 600;
45const char kPrivetInfoKeyUptime[] = "uptime";
46const char kPrivetNotificationID[] = "privet_notification";
47const char kPrivetNotificationOriginUrl[] = "chrome://devices";
48const int kStartDelaySeconds = 5;
49
50enum PrivetNotificationsEvent {
51  PRIVET_SERVICE_STARTED,
52  PRIVET_LISTER_STARTED,
53  PRIVET_DEVICE_CHANGED,
54  PRIVET_INFO_DONE,
55  PRIVET_NOTIFICATION_SHOWN,
56  PRIVET_NOTIFICATION_CANCELED,
57  PRIVET_NOTIFICATION_CLICKED,
58  PRIVET_DISABLE_NOTIFICATIONS_CLICKED,
59  PRIVET_EVENT_MAX,
60};
61
62void ReportPrivetUmaEvent(PrivetNotificationsEvent privet_event) {
63  UMA_HISTOGRAM_ENUMERATION("LocalDiscovery.PrivetNotificationsEvent",
64                            privet_event, PRIVET_EVENT_MAX);
65}
66
67}  // namespace
68
69PrivetNotificationsListener::PrivetNotificationsListener(
70    scoped_ptr<PrivetHTTPAsynchronousFactory> privet_http_factory,
71    Delegate* delegate) : delegate_(delegate), devices_active_(0) {
72  privet_http_factory_.swap(privet_http_factory);
73}
74
75PrivetNotificationsListener::~PrivetNotificationsListener() {
76}
77
78void PrivetNotificationsListener::DeviceChanged(
79    bool added,
80    const std::string& name,
81    const DeviceDescription& description) {
82  ReportPrivetUmaEvent(PRIVET_DEVICE_CHANGED);
83  DeviceContextMap::iterator found = devices_seen_.find(name);
84  if (found != devices_seen_.end()) {
85    if (!description.id.empty() &&  // Device is registered
86        found->second->notification_may_be_active) {
87      found->second->notification_may_be_active = false;
88      NotifyDeviceRemoved();
89    }
90    return;  // Already saw this device.
91  }
92
93  linked_ptr<DeviceContext> device_context(new DeviceContext);
94
95  device_context->notification_may_be_active = false;
96  device_context->registered = !description.id.empty();
97
98  devices_seen_.insert(make_pair(name, device_context));
99
100  if (!device_context->registered) {
101    device_context->privet_http_resolution =
102        privet_http_factory_->CreatePrivetHTTP(
103            name,
104            description.address,
105            base::Bind(&PrivetNotificationsListener::CreateInfoOperation,
106                       base::Unretained(this)));
107
108    device_context->privet_http_resolution->Start();
109  }
110}
111
112void PrivetNotificationsListener::CreateInfoOperation(
113    scoped_ptr<PrivetHTTPClient> http_client) {
114  std::string name = http_client->GetName();
115  DeviceContextMap::iterator device_iter = devices_seen_.find(name);
116  DCHECK(device_iter != devices_seen_.end());
117  DeviceContext* device = device_iter->second.get();
118  device->privet_http.swap(http_client);
119  device->info_operation =
120       device->privet_http->CreateInfoOperation(this);
121  device->info_operation->Start();
122}
123
124void PrivetNotificationsListener::OnPrivetInfoDone(
125      PrivetInfoOperation* operation,
126      int http_code,
127      const base::DictionaryValue* json_value) {
128  ReportPrivetUmaEvent(PRIVET_INFO_DONE);
129  std::string name = operation->GetHTTPClient()->GetName();
130  DeviceContextMap::iterator device_iter = devices_seen_.find(name);
131  DCHECK(device_iter != devices_seen_.end());
132  DeviceContext* device = device_iter->second.get();
133
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, true);
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(
224          switches::kDisableDeviceDiscoveryNotifications) &&
225      message_center::IsRichNotificationEnabled();
226}
227
228// static
229bool PrivetNotificationService::IsForced() {
230  CommandLine* command_line = CommandLine::ForCurrentProcess();
231  return command_line->HasSwitch(switches::kEnableDeviceDiscoveryNotifications);
232}
233
234void PrivetNotificationService::PrivetNotify(bool has_multiple,
235                                             bool added) {
236    string16 product_name = l10n_util::GetStringUTF16(
237        IDS_LOCAL_DISOCVERY_PRODUCT_NAME_PRINTER);
238
239    int title_resource = has_multiple ?
240        IDS_LOCAL_DISOCVERY_NOTIFICATION_TITLE_PRINTER_MULTIPLE :
241        IDS_LOCAL_DISOCVERY_NOTIFICATION_TITLE_PRINTER;
242
243    int body_resource = has_multiple ?
244        IDS_LOCAL_DISOCVERY_NOTIFICATION_CONTENTS_PRINTER_MULTIPLE :
245        IDS_LOCAL_DISOCVERY_NOTIFICATION_CONTENTS_PRINTER;
246
247    string16 title = l10n_util::GetStringUTF16(title_resource);
248    string16 body = l10n_util::GetStringFUTF16(body_resource, product_name);
249
250    Profile* profile_object = Profile::FromBrowserContext(profile_);
251    message_center::RichNotificationData rich_notification_data;
252
253    rich_notification_data.buttons.push_back(
254        message_center::ButtonInfo(l10n_util::GetStringUTF16(
255            IDS_LOCAL_DISOCVERY_NOTIFICATION_BUTTON_PRINTER)));
256
257    rich_notification_data.buttons.push_back(
258        message_center::ButtonInfo(l10n_util::GetStringUTF16(
259            IDS_LOCAL_DISCOVERY_NOTIFICATIONS_DISABLE_BUTTON_LABEL)));
260
261    Notification notification(
262        message_center::NOTIFICATION_TYPE_SIMPLE,
263        GURL(kPrivetNotificationOriginUrl),
264        title,
265        body,
266        ui::ResourceBundle::GetSharedInstance().GetImageNamed(
267            IDR_LOCAL_DISCOVERY_CLOUDPRINT_ICON),
268        WebKit::WebTextDirectionDefault,
269        message_center::NotifierId(
270            message_center::NotifierId::SYSTEM_COMPONENT),
271        product_name,
272        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  enable_privet_notification_member_.Init(
293      prefs::kLocalDiscoveryNotificationsEnabled,
294      Profile::FromBrowserContext(profile_)->GetPrefs(),
295      base::Bind(&PrivetNotificationService::OnNotificationsEnabledChanged,
296                 base::Unretained(this)));
297  OnNotificationsEnabledChanged();
298}
299
300void PrivetNotificationService::OnNotificationsEnabledChanged() {
301  if (IsForced()) {
302    StartLister();
303  } else if (*enable_privet_notification_member_) {
304    ReportPrivetUmaEvent(PRIVET_SERVICE_STARTED);
305    traffic_detector_ =
306        new PrivetTrafficDetector(
307            net::ADDRESS_FAMILY_IPV4,
308            base::Bind(&PrivetNotificationService::StartLister, AsWeakPtr()));
309    traffic_detector_->Start();
310  } else {
311    traffic_detector_ = NULL;
312    device_lister_.reset();
313    service_discovery_client_ = NULL;
314    privet_notifications_listener_.reset();
315  }
316}
317
318void PrivetNotificationService::StartLister() {
319  ReportPrivetUmaEvent(PRIVET_LISTER_STARTED);
320  traffic_detector_ = NULL;
321  DCHECK(!service_discovery_client_);
322  service_discovery_client_ = ServiceDiscoverySharedClient::GetInstance();
323  device_lister_.reset(new PrivetDeviceListerImpl(service_discovery_client_,
324                                                  this));
325  device_lister_->Start();
326  device_lister_->DiscoverNewDevices(false);
327
328  scoped_ptr<PrivetHTTPAsynchronousFactory> http_factory(
329      PrivetHTTPAsynchronousFactory::CreateInstance(
330          service_discovery_client_.get(), profile_->GetRequestContext()));
331
332  privet_notifications_listener_.reset(new PrivetNotificationsListener(
333      http_factory.Pass(), this));
334}
335
336PrivetNotificationDelegate::PrivetNotificationDelegate(
337    content::BrowserContext* profile)
338    :  profile_(profile) {
339}
340
341PrivetNotificationDelegate::~PrivetNotificationDelegate() {
342}
343
344std::string PrivetNotificationDelegate::id() const {
345  return kPrivetNotificationID;
346}
347
348content::RenderViewHost* PrivetNotificationDelegate::GetRenderViewHost() const {
349  return NULL;
350}
351
352void PrivetNotificationDelegate::Display() {
353}
354
355void PrivetNotificationDelegate::Error() {
356  LOG(ERROR) << "Error displaying privet notification";
357}
358
359void PrivetNotificationDelegate::Close(bool by_user) {
360}
361
362void PrivetNotificationDelegate::Click() {
363}
364
365void PrivetNotificationDelegate::ButtonClick(int button_index) {
366  if (button_index == 0) {
367    ReportPrivetUmaEvent(PRIVET_NOTIFICATION_CLICKED);
368    OpenTab(GURL(kPrivetNotificationOriginUrl));
369  } else if (button_index == 1) {
370    ReportPrivetUmaEvent(PRIVET_DISABLE_NOTIFICATIONS_CLICKED);
371    DisableNotifications();
372  }
373}
374
375void PrivetNotificationDelegate::OpenTab(const GURL& url) {
376  Profile* profile_obj = Profile::FromBrowserContext(profile_);
377
378  Browser* browser = FindOrCreateTabbedBrowser(profile_obj,
379                                               chrome::GetActiveDesktop());
380  content::WebContents::CreateParams create_params(profile_obj);
381
382  scoped_ptr<content::WebContents> contents(
383      content::WebContents::Create(create_params));
384  contents->GetController().LoadURL(url,
385                                    content::Referrer(),
386                                    content::PAGE_TRANSITION_AUTO_TOPLEVEL,
387                                    "");
388
389  browser->tab_strip_model()->AppendWebContents(contents.release(), true);
390}
391
392void PrivetNotificationDelegate::DisableNotifications() {
393  Profile* profile_obj = Profile::FromBrowserContext(profile_);
394
395  profile_obj->GetPrefs()->SetBoolean(
396      prefs::kLocalDiscoveryNotificationsEnabled,
397      false);
398}
399
400}  // namespace local_discovery
401