• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/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 "content/public/browser/browser_context.h"
31 #include "content/public/browser/navigation_controller.h"
32 #include "content/public/browser/web_contents.h"
33 #include "content/public/common/page_transition_types.h"
34 #include "grit/generated_resources.h"
35 #include "grit/theme_resources.h"
36 #include "ui/base/l10n/l10n_util.h"
37 #include "ui/base/resource/resource_bundle.h"
38 #include "ui/message_center/message_center_util.h"
39 #include "ui/message_center/notifier_settings.h"
40 
41 namespace local_discovery {
42 
43 namespace {
44 
45 const int kTenMinutesInSeconds = 600;
46 const char kPrivetInfoKeyUptime[] = "uptime";
47 const char kPrivetNotificationID[] = "privet_notification";
48 const char kPrivetNotificationOriginUrl[] = "chrome://devices";
49 const int kStartDelaySeconds = 5;
50 
51 enum PrivetNotificationsEvent {
52   PRIVET_SERVICE_STARTED,
53   PRIVET_LISTER_STARTED,
54   PRIVET_DEVICE_CHANGED,
55   PRIVET_INFO_DONE,
56   PRIVET_NOTIFICATION_SHOWN,
57   PRIVET_NOTIFICATION_CANCELED,
58   PRIVET_NOTIFICATION_CLICKED,
59   PRIVET_DISABLE_NOTIFICATIONS_CLICKED,
60   PRIVET_EVENT_MAX,
61 };
62 
ReportPrivetUmaEvent(PrivetNotificationsEvent privet_event)63 void ReportPrivetUmaEvent(PrivetNotificationsEvent privet_event) {
64   UMA_HISTOGRAM_ENUMERATION("LocalDiscovery.PrivetNotificationsEvent",
65                             privet_event, PRIVET_EVENT_MAX);
66 }
67 
68 }  // namespace
69 
PrivetNotificationsListener(scoped_ptr<PrivetHTTPAsynchronousFactory> privet_http_factory,Delegate * delegate)70 PrivetNotificationsListener::PrivetNotificationsListener(
71     scoped_ptr<PrivetHTTPAsynchronousFactory> privet_http_factory,
72     Delegate* delegate) : delegate_(delegate), devices_active_(0) {
73   privet_http_factory_.swap(privet_http_factory);
74 }
75 
~PrivetNotificationsListener()76 PrivetNotificationsListener::~PrivetNotificationsListener() {
77 }
78 
DeviceChanged(bool added,const std::string & name,const DeviceDescription & description)79 void PrivetNotificationsListener::DeviceChanged(
80     bool added,
81     const std::string& name,
82     const DeviceDescription& description) {
83   ReportPrivetUmaEvent(PRIVET_DEVICE_CHANGED);
84   DeviceContextMap::iterator found = devices_seen_.find(name);
85   if (found != devices_seen_.end()) {
86     if (!description.id.empty() &&  // Device is registered
87         found->second->notification_may_be_active) {
88       found->second->notification_may_be_active = false;
89       NotifyDeviceRemoved();
90     }
91     return;  // Already saw this device.
92   }
93 
94   linked_ptr<DeviceContext> device_context(new DeviceContext);
95 
96   device_context->notification_may_be_active = false;
97   device_context->registered = !description.id.empty();
98 
99   devices_seen_.insert(make_pair(name, device_context));
100 
101   if (!device_context->registered) {
102     device_context->privet_http_resolution =
103         privet_http_factory_->CreatePrivetHTTP(
104             name,
105             description.address,
106             base::Bind(&PrivetNotificationsListener::CreateInfoOperation,
107                        base::Unretained(this)));
108 
109     device_context->privet_http_resolution->Start();
110   }
111 }
112 
CreateInfoOperation(scoped_ptr<PrivetHTTPClient> http_client)113 void PrivetNotificationsListener::CreateInfoOperation(
114     scoped_ptr<PrivetHTTPClient> http_client) {
115   std::string name = http_client->GetName();
116   DeviceContextMap::iterator device_iter = devices_seen_.find(name);
117   DCHECK(device_iter != devices_seen_.end());
118   DeviceContext* device = device_iter->second.get();
119   device->privet_http.swap(http_client);
120   device->info_operation =
121        device->privet_http->CreateInfoOperation(this);
122   device->info_operation->Start();
123 }
124 
OnPrivetInfoDone(PrivetInfoOperation * operation,int http_code,const base::DictionaryValue * json_value)125 void PrivetNotificationsListener::OnPrivetInfoDone(
126       PrivetInfoOperation* operation,
127       int http_code,
128       const base::DictionaryValue* json_value) {
129   ReportPrivetUmaEvent(PRIVET_INFO_DONE);
130   std::string name = operation->GetHTTPClient()->GetName();
131   DeviceContextMap::iterator device_iter = devices_seen_.find(name);
132   DCHECK(device_iter != devices_seen_.end());
133   DeviceContext* device = device_iter->second.get();
134 
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 
DeviceRemoved(const std::string & name)149 void 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 
DeviceCacheFlushed()161 void 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 
NotifyDeviceRemoved()177 void PrivetNotificationsListener::NotifyDeviceRemoved() {
178   devices_active_--;
179   if (devices_active_ == 0) {
180     delegate_->PrivetRemoveNotification();
181   } else {
182     delegate_->PrivetNotify(devices_active_ > 1, true);
183   }
184 }
185 
DeviceContext()186 PrivetNotificationsListener::DeviceContext::DeviceContext() {
187 }
188 
~DeviceContext()189 PrivetNotificationsListener::DeviceContext::~DeviceContext() {
190 }
191 
PrivetNotificationService(content::BrowserContext * profile)192 PrivetNotificationService::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 
~PrivetNotificationService()202 PrivetNotificationService::~PrivetNotificationService() {
203 }
204 
DeviceChanged(bool added,const std::string & name,const DeviceDescription & description)205 void PrivetNotificationService::DeviceChanged(
206     bool added,
207     const std::string& name,
208     const DeviceDescription& description) {
209   privet_notifications_listener_->DeviceChanged(added, name, description);
210 }
211 
DeviceRemoved(const std::string & name)212 void PrivetNotificationService::DeviceRemoved(const std::string& name) {
213   privet_notifications_listener_->DeviceRemoved(name);
214 }
215 
DeviceCacheFlushed()216 void PrivetNotificationService::DeviceCacheFlushed() {
217   privet_notifications_listener_->DeviceCacheFlushed();
218 }
219 
220 // static
IsEnabled()221 bool 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
IsForced()230 bool PrivetNotificationService::IsForced() {
231   CommandLine* command_line = CommandLine::ForCurrentProcess();
232   return command_line->HasSwitch(switches::kEnableDeviceDiscoveryNotifications);
233 }
234 
PrivetNotify(bool has_multiple,bool added)235 void 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       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 
PrivetRemoveNotification()285 void PrivetNotificationService::PrivetRemoveNotification() {
286   ReportPrivetUmaEvent(PRIVET_NOTIFICATION_CANCELED);
287   g_browser_process->notification_ui_manager()->CancelById(
288       kPrivetNotificationID);
289 }
290 
Start()291 void 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 
OnNotificationsEnabledChanged()300 void 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 
StartLister()318 void 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 
PrivetNotificationDelegate(content::BrowserContext * profile)336 PrivetNotificationDelegate::PrivetNotificationDelegate(
337     content::BrowserContext* profile)
338     :  profile_(profile) {
339 }
340 
~PrivetNotificationDelegate()341 PrivetNotificationDelegate::~PrivetNotificationDelegate() {
342 }
343 
id() const344 std::string PrivetNotificationDelegate::id() const {
345   return kPrivetNotificationID;
346 }
347 
GetRenderViewHost() const348 content::RenderViewHost* PrivetNotificationDelegate::GetRenderViewHost() const {
349   return NULL;
350 }
351 
Display()352 void PrivetNotificationDelegate::Display() {
353 }
354 
Error()355 void PrivetNotificationDelegate::Error() {
356   LOG(ERROR) << "Error displaying privet notification";
357 }
358 
Close(bool by_user)359 void PrivetNotificationDelegate::Close(bool by_user) {
360 }
361 
Click()362 void PrivetNotificationDelegate::Click() {
363 }
364 
ButtonClick(int button_index)365 void 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 
OpenTab(const GURL & url)375 void PrivetNotificationDelegate::OpenTab(const GURL& url) {
376   Profile* profile_obj = Profile::FromBrowserContext(profile_);
377 
378   chrome::NavigateParams params(profile_obj,
379                               url,
380                               content::PAGE_TRANSITION_AUTO_TOPLEVEL);
381   params.disposition = NEW_FOREGROUND_TAB;
382   chrome::Navigate(&params);
383 }
384 
DisableNotifications()385 void PrivetNotificationDelegate::DisableNotifications() {
386   Profile* profile_obj = Profile::FromBrowserContext(profile_);
387 
388   profile_obj->GetPrefs()->SetBoolean(
389       prefs::kLocalDiscoveryNotificationsEnabled,
390       false);
391 }
392 
393 }  // namespace local_discovery
394