Home | History | Annotate | Download | only in local_discovery
      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 
     63 void ReportPrivetUmaEvent(PrivetNotificationsEvent privet_event) {
     64   UMA_HISTOGRAM_ENUMERATION("LocalDiscovery.PrivetNotificationsEvent",
     65                             privet_event, PRIVET_EVENT_MAX);
     66 }
     67 
     68 }  // namespace
     69 
     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 
     76 PrivetNotificationsListener::~PrivetNotificationsListener() {
     77 }
     78 
     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 
    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 
    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 
    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 
    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 
    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 
    186 PrivetNotificationsListener::DeviceContext::DeviceContext() {
    187 }
    188 
    189 PrivetNotificationsListener::DeviceContext::~DeviceContext() {
    190 }
    191 
    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 
    202 PrivetNotificationService::~PrivetNotificationService() {
    203 }
    204 
    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 
    212 void PrivetNotificationService::DeviceRemoved(const std::string& name) {
    213   privet_notifications_listener_->DeviceRemoved(name);
    214 }
    215 
    216 void PrivetNotificationService::DeviceCacheFlushed() {
    217   privet_notifications_listener_->DeviceCacheFlushed();
    218 }
    219 
    220 // static
    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
    230 bool PrivetNotificationService::IsForced() {
    231   CommandLine* command_line = CommandLine::ForCurrentProcess();
    232   return command_line->HasSwitch(switches::kEnableDeviceDiscoveryNotifications);
    233 }
    234 
    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 
    285 void PrivetNotificationService::PrivetRemoveNotification() {
    286   ReportPrivetUmaEvent(PRIVET_NOTIFICATION_CANCELED);
    287   g_browser_process->notification_ui_manager()->CancelById(
    288       kPrivetNotificationID);
    289 }
    290 
    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 
    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 
    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 
    336 PrivetNotificationDelegate::PrivetNotificationDelegate(
    337     content::BrowserContext* profile)
    338     :  profile_(profile) {
    339 }
    340 
    341 PrivetNotificationDelegate::~PrivetNotificationDelegate() {
    342 }
    343 
    344 std::string PrivetNotificationDelegate::id() const {
    345   return kPrivetNotificationID;
    346 }
    347 
    348 content::RenderViewHost* PrivetNotificationDelegate::GetRenderViewHost() const {
    349   return NULL;
    350 }
    351 
    352 void PrivetNotificationDelegate::Display() {
    353 }
    354 
    355 void PrivetNotificationDelegate::Error() {
    356   LOG(ERROR) << "Error displaying privet notification";
    357 }
    358 
    359 void PrivetNotificationDelegate::Close(bool by_user) {
    360 }
    361 
    362 void PrivetNotificationDelegate::Click() {
    363 }
    364 
    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 
    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 
    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