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/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 "chrome/grit/generated_resources.h"
     31 #include "components/signin/core/browser/signin_manager_base.h"
     32 #include "content/public/browser/browser_context.h"
     33 #include "content/public/browser/navigation_controller.h"
     34 #include "content/public/browser/web_contents.h"
     35 #include "grit/theme_resources.h"
     36 #include "ui/base/l10n/l10n_util.h"
     37 #include "ui/base/page_transition_types.h"
     38 #include "ui/base/resource/resource_bundle.h"
     39 #include "ui/message_center/notifier_settings.h"
     40 
     41 #if defined(ENABLE_MDNS)
     42 #include "chrome/browser/local_discovery/privet_traffic_detector.h"
     43 #endif
     44 
     45 namespace local_discovery {
     46 
     47 namespace {
     48 
     49 const int kTenMinutesInSeconds = 600;
     50 const char kPrivetInfoKeyUptime[] = "uptime";
     51 const char kPrivetNotificationID[] = "privet_notification";
     52 const char kPrivetNotificationOriginUrl[] = "chrome://devices";
     53 const int kStartDelaySeconds = 5;
     54 
     55 enum PrivetNotificationsEvent {
     56   PRIVET_SERVICE_STARTED,
     57   PRIVET_LISTER_STARTED,
     58   PRIVET_DEVICE_CHANGED,
     59   PRIVET_INFO_DONE,
     60   PRIVET_NOTIFICATION_SHOWN,
     61   PRIVET_NOTIFICATION_CANCELED,
     62   PRIVET_NOTIFICATION_CLICKED,
     63   PRIVET_DISABLE_NOTIFICATIONS_CLICKED,
     64   PRIVET_EVENT_MAX,
     65 };
     66 
     67 void ReportPrivetUmaEvent(PrivetNotificationsEvent privet_event) {
     68   UMA_HISTOGRAM_ENUMERATION("LocalDiscovery.PrivetNotificationsEvent",
     69                             privet_event, PRIVET_EVENT_MAX);
     70 }
     71 
     72 }  // namespace
     73 
     74 PrivetNotificationsListener::PrivetNotificationsListener(
     75     scoped_ptr<PrivetHTTPAsynchronousFactory> privet_http_factory,
     76     Delegate* delegate)
     77     : delegate_(delegate), devices_active_(0) {
     78   privet_http_factory_.swap(privet_http_factory);
     79 }
     80 
     81 PrivetNotificationsListener::~PrivetNotificationsListener() {
     82 }
     83 
     84 void 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 
    118 void PrivetNotificationsListener::CreateInfoOperation(
    119     scoped_ptr<PrivetHTTPClient> http_client) {
    120   if (!http_client) {
    121     // Do nothing if resolution fails.
    122     return;
    123   }
    124 
    125   std::string name = http_client->GetName();
    126   DeviceContextMap::iterator device_iter = devices_seen_.find(name);
    127   DCHECK(device_iter != devices_seen_.end());
    128   DeviceContext* device = device_iter->second.get();
    129   device->privet_http.swap(http_client);
    130   device->info_operation = device->privet_http->CreateInfoOperation(
    131       base::Bind(&PrivetNotificationsListener::OnPrivetInfoDone,
    132                  base::Unretained(this),
    133                  device));
    134   device->info_operation->Start();
    135 }
    136 
    137 void PrivetNotificationsListener::OnPrivetInfoDone(
    138     DeviceContext* device,
    139     const base::DictionaryValue* json_value) {
    140   int uptime;
    141 
    142   if (!json_value ||
    143       !json_value->GetInteger(kPrivetInfoKeyUptime, &uptime) ||
    144       uptime > kTenMinutesInSeconds) {
    145     return;
    146   }
    147 
    148   DCHECK(!device->notification_may_be_active);
    149   device->notification_may_be_active = true;
    150   devices_active_++;
    151   delegate_->PrivetNotify(devices_active_ > 1, true);
    152 }
    153 
    154 void PrivetNotificationsListener::DeviceRemoved(const std::string& name) {
    155   DCHECK_EQ(1u, devices_seen_.count(name));
    156   DeviceContextMap::iterator device_iter = devices_seen_.find(name);
    157   DCHECK(device_iter != devices_seen_.end());
    158   DeviceContext* device = device_iter->second.get();
    159 
    160   device->info_operation.reset();
    161   device->privet_http_resolution.reset();
    162   device->notification_may_be_active = false;
    163   NotifyDeviceRemoved();
    164 }
    165 
    166 void PrivetNotificationsListener::DeviceCacheFlushed() {
    167   for (DeviceContextMap::iterator i = devices_seen_.begin();
    168        i != devices_seen_.end(); ++i) {
    169     DeviceContext* device = i->second.get();
    170 
    171     device->info_operation.reset();
    172     device->privet_http_resolution.reset();
    173     if (device->notification_may_be_active) {
    174       device->notification_may_be_active = false;
    175     }
    176   }
    177 
    178   devices_active_ = 0;
    179   delegate_->PrivetRemoveNotification();
    180 }
    181 
    182 void PrivetNotificationsListener::NotifyDeviceRemoved() {
    183   devices_active_--;
    184   if (devices_active_ == 0) {
    185     delegate_->PrivetRemoveNotification();
    186   } else {
    187     delegate_->PrivetNotify(devices_active_ > 1, false);
    188   }
    189 }
    190 
    191 PrivetNotificationsListener::DeviceContext::DeviceContext() {
    192 }
    193 
    194 PrivetNotificationsListener::DeviceContext::~DeviceContext() {
    195 }
    196 
    197 PrivetNotificationService::PrivetNotificationService(
    198     content::BrowserContext* profile)
    199     : profile_(profile) {
    200   base::MessageLoop::current()->PostDelayedTask(
    201       FROM_HERE,
    202       base::Bind(&PrivetNotificationService::Start, AsWeakPtr()),
    203       base::TimeDelta::FromSeconds(kStartDelaySeconds +
    204                                    base::RandInt(0, kStartDelaySeconds/4)));
    205 }
    206 
    207 PrivetNotificationService::~PrivetNotificationService() {
    208 }
    209 
    210 void PrivetNotificationService::DeviceChanged(
    211     bool added,
    212     const std::string& name,
    213     const DeviceDescription& description) {
    214   privet_notifications_listener_->DeviceChanged(added, name, description);
    215 }
    216 
    217 void PrivetNotificationService::DeviceRemoved(const std::string& name) {
    218   privet_notifications_listener_->DeviceRemoved(name);
    219 }
    220 
    221 void PrivetNotificationService::DeviceCacheFlushed() {
    222   privet_notifications_listener_->DeviceCacheFlushed();
    223 }
    224 
    225 // static
    226 bool PrivetNotificationService::IsEnabled() {
    227   CommandLine* command_line = CommandLine::ForCurrentProcess();
    228   return !command_line->HasSwitch(
    229       switches::kDisableDeviceDiscoveryNotifications);
    230 }
    231 
    232 // static
    233 bool PrivetNotificationService::IsForced() {
    234   CommandLine* command_line = CommandLine::ForCurrentProcess();
    235   return command_line->HasSwitch(switches::kEnableDeviceDiscoveryNotifications);
    236 }
    237 
    238 void PrivetNotificationService::PrivetNotify(bool has_multiple,
    239                                              bool added) {
    240   base::string16 product_name = l10n_util::GetStringUTF16(
    241       IDS_LOCAL_DISOCVERY_SERVICE_NAME_PRINTER);
    242 
    243   int title_resource = has_multiple ?
    244       IDS_LOCAL_DISOCVERY_NOTIFICATION_TITLE_PRINTER_MULTIPLE :
    245       IDS_LOCAL_DISOCVERY_NOTIFICATION_TITLE_PRINTER;
    246 
    247   int body_resource = has_multiple ?
    248       IDS_LOCAL_DISOCVERY_NOTIFICATION_CONTENTS_PRINTER_MULTIPLE :
    249       IDS_LOCAL_DISOCVERY_NOTIFICATION_CONTENTS_PRINTER;
    250 
    251   base::string16 title = l10n_util::GetStringUTF16(title_resource);
    252   base::string16 body = l10n_util::GetStringUTF16(body_resource);
    253 
    254   Profile* profile_object = Profile::FromBrowserContext(profile_);
    255   message_center::RichNotificationData rich_notification_data;
    256 
    257   rich_notification_data.buttons.push_back(
    258       message_center::ButtonInfo(l10n_util::GetStringUTF16(
    259           IDS_LOCAL_DISOCVERY_NOTIFICATION_BUTTON_PRINTER)));
    260 
    261   rich_notification_data.buttons.push_back(
    262       message_center::ButtonInfo(l10n_util::GetStringUTF16(
    263           IDS_LOCAL_DISCOVERY_NOTIFICATIONS_DISABLE_BUTTON_LABEL)));
    264 
    265   Notification notification(
    266       message_center::NOTIFICATION_TYPE_SIMPLE,
    267       GURL(kPrivetNotificationOriginUrl),
    268       title,
    269       body,
    270       ui::ResourceBundle::GetSharedInstance().GetImageNamed(
    271           IDR_LOCAL_DISCOVERY_CLOUDPRINT_ICON),
    272       blink::WebTextDirectionDefault,
    273       message_center::NotifierId(GURL(kPrivetNotificationOriginUrl)),
    274       product_name,
    275       base::UTF8ToUTF16(kPrivetNotificationID),
    276       rich_notification_data,
    277       new PrivetNotificationDelegate(profile_));
    278 
    279   bool updated = g_browser_process->notification_ui_manager()->Update(
    280       notification, profile_object);
    281   if (!updated && added && !LocalDiscoveryUIHandler::GetHasVisible()) {
    282     ReportPrivetUmaEvent(PRIVET_NOTIFICATION_SHOWN);
    283     g_browser_process->notification_ui_manager()->Add(notification,
    284                                                       profile_object);
    285   }
    286 }
    287 
    288 void PrivetNotificationService::PrivetRemoveNotification() {
    289   ReportPrivetUmaEvent(PRIVET_NOTIFICATION_CANCELED);
    290   g_browser_process->notification_ui_manager()->CancelById(
    291       kPrivetNotificationID);
    292 }
    293 
    294 void PrivetNotificationService::Start() {
    295 #if defined(CHROMEOS)
    296   SigninManagerBase* signin_manager =
    297       SigninManagerFactory::GetForProfileIfExists(
    298           Profile::FromBrowserContext(profile_));
    299 
    300   if (!signin_manager || !signin_manager->IsAuthenticated())
    301     return;
    302 #endif
    303 
    304   enable_privet_notification_member_.Init(
    305       prefs::kLocalDiscoveryNotificationsEnabled,
    306       Profile::FromBrowserContext(profile_)->GetPrefs(),
    307       base::Bind(&PrivetNotificationService::OnNotificationsEnabledChanged,
    308                  base::Unretained(this)));
    309   OnNotificationsEnabledChanged();
    310 }
    311 
    312 void PrivetNotificationService::OnNotificationsEnabledChanged() {
    313 #if defined(ENABLE_MDNS)
    314   if (IsForced()) {
    315     StartLister();
    316   } else if (*enable_privet_notification_member_) {
    317     ReportPrivetUmaEvent(PRIVET_SERVICE_STARTED);
    318     traffic_detector_ =
    319         new PrivetTrafficDetector(
    320             net::ADDRESS_FAMILY_IPV4,
    321             base::Bind(&PrivetNotificationService::StartLister, AsWeakPtr()));
    322     traffic_detector_->Start();
    323   } else {
    324     traffic_detector_ = NULL;
    325     device_lister_.reset();
    326     service_discovery_client_ = NULL;
    327     privet_notifications_listener_.reset();
    328   }
    329 #else
    330   if (IsForced() || *enable_privet_notification_member_) {
    331     StartLister();
    332   } else {
    333     device_lister_.reset();
    334     service_discovery_client_ = NULL;
    335     privet_notifications_listener_.reset();
    336   }
    337 #endif
    338 }
    339 
    340 void PrivetNotificationService::StartLister() {
    341   ReportPrivetUmaEvent(PRIVET_LISTER_STARTED);
    342 #if defined(ENABLE_MDNS)
    343   traffic_detector_ = NULL;
    344 #endif  // ENABLE_MDNS
    345   service_discovery_client_ = ServiceDiscoverySharedClient::GetInstance();
    346   device_lister_.reset(
    347       new PrivetDeviceListerImpl(service_discovery_client_.get(), this));
    348   device_lister_->Start();
    349   device_lister_->DiscoverNewDevices(false);
    350 
    351   scoped_ptr<PrivetHTTPAsynchronousFactory> http_factory(
    352       PrivetHTTPAsynchronousFactory::CreateInstance(
    353           service_discovery_client_.get(), profile_->GetRequestContext()));
    354 
    355   privet_notifications_listener_.reset(new PrivetNotificationsListener(
    356       http_factory.Pass(), this));
    357 }
    358 
    359 PrivetNotificationDelegate::PrivetNotificationDelegate(
    360     content::BrowserContext* profile)
    361     :  profile_(profile) {
    362 }
    363 
    364 PrivetNotificationDelegate::~PrivetNotificationDelegate() {
    365 }
    366 
    367 std::string PrivetNotificationDelegate::id() const {
    368   return kPrivetNotificationID;
    369 }
    370 
    371 content::WebContents* PrivetNotificationDelegate::GetWebContents() const {
    372   return NULL;
    373 }
    374 
    375 void PrivetNotificationDelegate::Display() {
    376 }
    377 
    378 void PrivetNotificationDelegate::Error() {
    379   LOG(ERROR) << "Error displaying privet notification";
    380 }
    381 
    382 void PrivetNotificationDelegate::Close(bool by_user) {
    383 }
    384 
    385 void PrivetNotificationDelegate::Click() {
    386 }
    387 
    388 void PrivetNotificationDelegate::ButtonClick(int button_index) {
    389   if (button_index == 0) {
    390     ReportPrivetUmaEvent(PRIVET_NOTIFICATION_CLICKED);
    391     OpenTab(GURL(kPrivetNotificationOriginUrl));
    392   } else if (button_index == 1) {
    393     ReportPrivetUmaEvent(PRIVET_DISABLE_NOTIFICATIONS_CLICKED);
    394     DisableNotifications();
    395   }
    396 }
    397 
    398 void PrivetNotificationDelegate::OpenTab(const GURL& url) {
    399   Profile* profile_obj = Profile::FromBrowserContext(profile_);
    400 
    401   chrome::NavigateParams params(profile_obj,
    402                               url,
    403                               ui::PAGE_TRANSITION_AUTO_TOPLEVEL);
    404   params.disposition = NEW_FOREGROUND_TAB;
    405   chrome::Navigate(&params);
    406 }
    407 
    408 void PrivetNotificationDelegate::DisableNotifications() {
    409   Profile* profile_obj = Profile::FromBrowserContext(profile_);
    410 
    411   profile_obj->GetPrefs()->SetBoolean(
    412       prefs::kLocalDiscoveryNotificationsEnabled,
    413       false);
    414 }
    415 
    416 }  // namespace local_discovery
    417