Allow the minimum time between banner trigger visits to be varied via field trials.
[chromium-blink-merge.git] / chrome / browser / local_discovery / privet_notifications.cc
blob3e1e5c874d83e4f402021784fab54d312b5dd1e4
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.
5 #include "chrome/browser/local_discovery/privet_notifications.h"
7 #include "base/bind.h"
8 #include "base/command_line.h"
9 #include "base/location.h"
10 #include "base/metrics/histogram.h"
11 #include "base/prefs/pref_service.h"
12 #include "base/rand_util.h"
13 #include "base/single_thread_task_runner.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/thread_task_runner_handle.h"
16 #include "chrome/browser/browser_process.h"
17 #include "chrome/browser/local_discovery/privet_device_lister_impl.h"
18 #include "chrome/browser/local_discovery/privet_http_asynchronous_factory.h"
19 #include "chrome/browser/local_discovery/service_discovery_shared_client.h"
20 #include "chrome/browser/notifications/notification.h"
21 #include "chrome/browser/notifications/notification_ui_manager.h"
22 #include "chrome/browser/profiles/profile.h"
23 #include "chrome/browser/signin/signin_manager_factory.h"
24 #include "chrome/browser/ui/browser.h"
25 #include "chrome/browser/ui/browser_finder.h"
26 #include "chrome/browser/ui/browser_window.h"
27 #include "chrome/browser/ui/host_desktop.h"
28 #include "chrome/browser/ui/tabs/tab_strip_model.h"
29 #include "chrome/browser/ui/webui/local_discovery/local_discovery_ui_handler.h"
30 #include "chrome/common/chrome_switches.h"
31 #include "chrome/common/pref_names.h"
32 #include "chrome/grit/generated_resources.h"
33 #include "components/signin/core/browser/signin_manager_base.h"
34 #include "content/public/browser/browser_context.h"
35 #include "content/public/browser/navigation_controller.h"
36 #include "content/public/browser/web_contents.h"
37 #include "grit/theme_resources.h"
38 #include "ui/base/l10n/l10n_util.h"
39 #include "ui/base/page_transition_types.h"
40 #include "ui/base/resource/resource_bundle.h"
41 #include "ui/message_center/notifier_settings.h"
43 #if defined(ENABLE_MDNS)
44 #include "chrome/browser/local_discovery/privet_traffic_detector.h"
45 #endif
47 namespace local_discovery {
49 namespace {
51 const int kTenMinutesInSeconds = 600;
52 const char kPrivetInfoKeyUptime[] = "uptime";
53 const char kPrivetNotificationID[] = "privet_notification";
54 const char kPrivetNotificationOriginUrl[] = "chrome://devices";
55 const int kStartDelaySeconds = 5;
57 enum PrivetNotificationsEvent {
58 PRIVET_SERVICE_STARTED,
59 PRIVET_LISTER_STARTED,
60 PRIVET_DEVICE_CHANGED,
61 PRIVET_INFO_DONE,
62 PRIVET_NOTIFICATION_SHOWN,
63 PRIVET_NOTIFICATION_CANCELED,
64 PRIVET_NOTIFICATION_CLICKED,
65 PRIVET_DISABLE_NOTIFICATIONS_CLICKED,
66 PRIVET_EVENT_MAX,
69 void ReportPrivetUmaEvent(PrivetNotificationsEvent privet_event) {
70 UMA_HISTOGRAM_ENUMERATION("LocalDiscovery.PrivetNotificationsEvent",
71 privet_event, PRIVET_EVENT_MAX);
74 } // namespace
76 PrivetNotificationsListener::PrivetNotificationsListener(
77 scoped_ptr<PrivetHTTPAsynchronousFactory> privet_http_factory,
78 Delegate* delegate)
79 : delegate_(delegate), devices_active_(0) {
80 privet_http_factory_.swap(privet_http_factory);
83 PrivetNotificationsListener::~PrivetNotificationsListener() {
86 void PrivetNotificationsListener::DeviceChanged(
87 bool added,
88 const std::string& name,
89 const DeviceDescription& description) {
90 ReportPrivetUmaEvent(PRIVET_DEVICE_CHANGED);
91 DeviceContextMap::iterator found = devices_seen_.find(name);
92 if (found != devices_seen_.end()) {
93 if (!description.id.empty() && // Device is registered
94 found->second->notification_may_be_active) {
95 found->second->notification_may_be_active = false;
96 NotifyDeviceRemoved();
98 return; // Already saw this device.
101 linked_ptr<DeviceContext> device_context(new DeviceContext);
103 device_context->notification_may_be_active = false;
104 device_context->registered = !description.id.empty();
106 devices_seen_.insert(make_pair(name, device_context));
108 if (!device_context->registered) {
109 device_context->privet_http_resolution =
110 privet_http_factory_->CreatePrivetHTTP(name);
111 device_context->privet_http_resolution->Start(
112 description.address,
113 base::Bind(&PrivetNotificationsListener::CreateInfoOperation,
114 base::Unretained(this)));
118 void PrivetNotificationsListener::CreateInfoOperation(
119 scoped_ptr<PrivetHTTPClient> http_client) {
120 if (!http_client) {
121 // Do nothing if resolution fails.
122 return;
125 std::string name = http_client->GetName();
126 DeviceContextMap::iterator device_iter = devices_seen_.find(name);
127 if (device_iter == devices_seen_.end())
128 return;
129 DeviceContext* device = device_iter->second.get();
130 device->privet_http.swap(http_client);
131 device->info_operation = device->privet_http->CreateInfoOperation(
132 base::Bind(&PrivetNotificationsListener::OnPrivetInfoDone,
133 base::Unretained(this),
134 device));
135 device->info_operation->Start();
138 void PrivetNotificationsListener::OnPrivetInfoDone(
139 DeviceContext* device,
140 const base::DictionaryValue* json_value) {
141 int uptime;
143 if (!json_value ||
144 !json_value->GetInteger(kPrivetInfoKeyUptime, &uptime) ||
145 uptime > kTenMinutesInSeconds) {
146 return;
149 DCHECK(!device->notification_may_be_active);
150 device->notification_may_be_active = true;
151 devices_active_++;
152 delegate_->PrivetNotify(devices_active_, true);
155 void PrivetNotificationsListener::DeviceRemoved(const std::string& name) {
156 DeviceContextMap::iterator device_iter = devices_seen_.find(name);
157 if (device_iter == devices_seen_.end())
158 return;
159 DeviceContext* device = device_iter->second.get();
161 device->info_operation.reset();
162 device->privet_http_resolution.reset();
163 device->notification_may_be_active = false;
164 NotifyDeviceRemoved();
167 void PrivetNotificationsListener::DeviceCacheFlushed() {
168 for (DeviceContextMap::iterator i = devices_seen_.begin();
169 i != devices_seen_.end(); ++i) {
170 DeviceContext* device = i->second.get();
172 device->info_operation.reset();
173 device->privet_http_resolution.reset();
174 if (device->notification_may_be_active) {
175 device->notification_may_be_active = false;
179 devices_active_ = 0;
180 delegate_->PrivetRemoveNotification();
183 void PrivetNotificationsListener::NotifyDeviceRemoved() {
184 devices_active_--;
185 if (devices_active_ == 0) {
186 delegate_->PrivetRemoveNotification();
187 } else {
188 delegate_->PrivetNotify(devices_active_, false);
192 PrivetNotificationsListener::DeviceContext::DeviceContext() {
195 PrivetNotificationsListener::DeviceContext::~DeviceContext() {
198 PrivetNotificationService::PrivetNotificationService(
199 content::BrowserContext* profile)
200 : profile_(profile) {
201 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
202 FROM_HERE, base::Bind(&PrivetNotificationService::Start, AsWeakPtr()),
203 base::TimeDelta::FromSeconds(kStartDelaySeconds +
204 base::RandInt(0, kStartDelaySeconds / 4)));
207 PrivetNotificationService::~PrivetNotificationService() {
210 void PrivetNotificationService::DeviceChanged(
211 bool added,
212 const std::string& name,
213 const DeviceDescription& description) {
214 privet_notifications_listener_->DeviceChanged(added, name, description);
217 void PrivetNotificationService::DeviceRemoved(const std::string& name) {
218 privet_notifications_listener_->DeviceRemoved(name);
221 void PrivetNotificationService::DeviceCacheFlushed() {
222 privet_notifications_listener_->DeviceCacheFlushed();
225 // static
226 bool PrivetNotificationService::IsEnabled() {
227 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
228 return !command_line->HasSwitch(
229 switches::kDisableDeviceDiscoveryNotifications);
232 // static
233 bool PrivetNotificationService::IsForced() {
234 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
235 return command_line->HasSwitch(switches::kEnableDeviceDiscoveryNotifications);
238 void PrivetNotificationService::PrivetNotify(int devices_active,
239 bool added) {
240 base::string16 product_name = l10n_util::GetStringUTF16(
241 IDS_LOCAL_DISCOVERY_SERVICE_NAME_PRINTER);
243 base::string16 title = l10n_util::GetPluralStringFUTF16(
244 IDS_LOCAL_DISCOVERY_NOTIFICATION_TITLE_PRINTER, devices_active);
245 base::string16 body = l10n_util::GetPluralStringFUTF16(
246 IDS_LOCAL_DISCOVERY_NOTIFICATION_CONTENTS_PRINTER, devices_active);
248 Profile* profile_object = Profile::FromBrowserContext(profile_);
249 message_center::RichNotificationData rich_notification_data;
251 rich_notification_data.buttons.push_back(
252 message_center::ButtonInfo(l10n_util::GetStringUTF16(
253 IDS_LOCAL_DISCOVERY_NOTIFICATION_BUTTON_PRINTER)));
255 rich_notification_data.buttons.push_back(
256 message_center::ButtonInfo(l10n_util::GetStringUTF16(
257 IDS_LOCAL_DISCOVERY_NOTIFICATIONS_DISABLE_BUTTON_LABEL)));
259 Notification notification(
260 message_center::NOTIFICATION_TYPE_SIMPLE, title, body,
261 ui::ResourceBundle::GetSharedInstance().GetImageNamed(
262 IDR_LOCAL_DISCOVERY_CLOUDPRINT_ICON),
263 message_center::NotifierId(GURL(kPrivetNotificationOriginUrl)),
264 product_name, GURL(kPrivetNotificationOriginUrl), kPrivetNotificationID,
265 rich_notification_data, new PrivetNotificationDelegate(profile_));
267 bool updated = g_browser_process->notification_ui_manager()->Update(
268 notification, profile_object);
269 if (!updated && added && !LocalDiscoveryUIHandler::GetHasVisible()) {
270 ReportPrivetUmaEvent(PRIVET_NOTIFICATION_SHOWN);
271 g_browser_process->notification_ui_manager()->Add(notification,
272 profile_object);
276 void PrivetNotificationService::PrivetRemoveNotification() {
277 ReportPrivetUmaEvent(PRIVET_NOTIFICATION_CANCELED);
278 Profile* profile_object = Profile::FromBrowserContext(profile_);
279 g_browser_process->notification_ui_manager()->CancelById(
280 kPrivetNotificationID,
281 NotificationUIManager::GetProfileID(profile_object));
284 void PrivetNotificationService::Start() {
285 #if defined(CHROMEOS)
286 SigninManagerBase* signin_manager =
287 SigninManagerFactory::GetForProfileIfExists(
288 Profile::FromBrowserContext(profile_));
290 if (!signin_manager || !signin_manager->IsAuthenticated())
291 return;
292 #endif
294 enable_privet_notification_member_.Init(
295 prefs::kLocalDiscoveryNotificationsEnabled,
296 Profile::FromBrowserContext(profile_)->GetPrefs(),
297 base::Bind(&PrivetNotificationService::OnNotificationsEnabledChanged,
298 base::Unretained(this)));
299 OnNotificationsEnabledChanged();
302 void PrivetNotificationService::OnNotificationsEnabledChanged() {
303 #if defined(ENABLE_MDNS)
304 if (IsForced()) {
305 StartLister();
306 } else if (*enable_privet_notification_member_) {
307 ReportPrivetUmaEvent(PRIVET_SERVICE_STARTED);
308 traffic_detector_ =
309 new PrivetTrafficDetector(
310 net::ADDRESS_FAMILY_IPV4,
311 base::Bind(&PrivetNotificationService::StartLister, AsWeakPtr()));
312 traffic_detector_->Start();
313 } else {
314 traffic_detector_ = NULL;
315 device_lister_.reset();
316 service_discovery_client_ = NULL;
317 privet_notifications_listener_.reset();
319 #else
320 if (IsForced() || *enable_privet_notification_member_) {
321 StartLister();
322 } else {
323 device_lister_.reset();
324 service_discovery_client_ = NULL;
325 privet_notifications_listener_.reset();
327 #endif
330 void PrivetNotificationService::StartLister() {
331 ReportPrivetUmaEvent(PRIVET_LISTER_STARTED);
332 #if defined(ENABLE_MDNS)
333 traffic_detector_ = NULL;
334 #endif // ENABLE_MDNS
335 service_discovery_client_ = ServiceDiscoverySharedClient::GetInstance();
336 device_lister_.reset(
337 new PrivetDeviceListerImpl(service_discovery_client_.get(), this));
338 device_lister_->Start();
339 device_lister_->DiscoverNewDevices(false);
341 scoped_ptr<PrivetHTTPAsynchronousFactory> http_factory(
342 PrivetHTTPAsynchronousFactory::CreateInstance(
343 profile_->GetRequestContext()));
345 privet_notifications_listener_.reset(new PrivetNotificationsListener(
346 http_factory.Pass(), this));
349 PrivetNotificationDelegate::PrivetNotificationDelegate(
350 content::BrowserContext* profile)
351 : profile_(profile) {
354 PrivetNotificationDelegate::~PrivetNotificationDelegate() {
357 std::string PrivetNotificationDelegate::id() const {
358 return kPrivetNotificationID;
361 void PrivetNotificationDelegate::ButtonClick(int button_index) {
362 if (button_index == 0) {
363 ReportPrivetUmaEvent(PRIVET_NOTIFICATION_CLICKED);
364 OpenTab(GURL(kPrivetNotificationOriginUrl));
365 } else if (button_index == 1) {
366 ReportPrivetUmaEvent(PRIVET_DISABLE_NOTIFICATIONS_CLICKED);
367 DisableNotifications();
371 void PrivetNotificationDelegate::OpenTab(const GURL& url) {
372 Profile* profile_obj = Profile::FromBrowserContext(profile_);
374 chrome::NavigateParams params(profile_obj,
375 url,
376 ui::PAGE_TRANSITION_AUTO_TOPLEVEL);
377 params.disposition = NEW_FOREGROUND_TAB;
378 chrome::Navigate(&params);
381 void PrivetNotificationDelegate::DisableNotifications() {
382 Profile* profile_obj = Profile::FromBrowserContext(profile_);
384 profile_obj->GetPrefs()->SetBoolean(
385 prefs::kLocalDiscoveryNotificationsEnabled,
386 false);
389 } // namespace local_discovery