Home | History | Annotate | Download | only in power
      1 // Copyright (c) 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/chromeos/power/peripheral_battery_observer.h"
      6 
      7 #include <vector>
      8 
      9 #include "ash/shell.h"
     10 #include "ash/strings/grit/ash_strings.h"
     11 #include "base/bind.h"
     12 #include "base/strings/string16.h"
     13 #include "base/strings/string_split.h"
     14 #include "base/strings/string_util.h"
     15 #include "base/strings/stringprintf.h"
     16 #include "base/strings/utf_string_conversions.h"
     17 #include "chrome/browser/browser_process.h"
     18 #include "chrome/browser/notifications/notification.h"
     19 #include "chrome/browser/notifications/notification_ui_manager.h"
     20 #include "chrome/browser/profiles/profile_manager.h"
     21 #include "chrome/grit/theme_resources.h"
     22 #include "chromeos/dbus/dbus_thread_manager.h"
     23 #include "content/public/browser/browser_thread.h"
     24 #include "device/bluetooth/bluetooth_adapter_factory.h"
     25 #include "device/bluetooth/bluetooth_device.h"
     26 #include "ui/base/l10n/l10n_util.h"
     27 #include "ui/base/resource/resource_bundle.h"
     28 #include "ui/gfx/image/image.h"
     29 
     30 namespace chromeos {
     31 
     32 namespace {
     33 
     34 // When a peripheral device's battery level is <= kLowBatteryLevel, consider
     35 // it to be in low battery condition.
     36 const int kLowBatteryLevel = 15;
     37 
     38 // Don't show 2 low battery notification within |kNotificationIntervalSec|
     39 // seconds.
     40 const int kNotificationIntervalSec = 60;
     41 
     42 const char kNotificationOriginUrl[] = "chrome://peripheral-battery";
     43 
     44 // HID Bluetooth device's battery sysfs entry path looks like
     45 // "/sys/class/power_supply/hid-AA:BB:CC:DD:EE:FF-battery".
     46 // Here the bluetooth address is showed in reverse order and its true
     47 // address "FF:EE:DD:CC:BB:AA".
     48 const char kHIDBatteryPathPrefix[] = "/sys/class/power_supply/hid-";
     49 const char kHIDBatteryPathSuffix[] = "-battery";
     50 
     51 bool IsBluetoothHIDBattery(const std::string& path) {
     52   return StartsWithASCII(path, kHIDBatteryPathPrefix, false) &&
     53       EndsWith(path, kHIDBatteryPathSuffix, false);
     54 }
     55 
     56 std::string ExtractBluetoothAddress(const std::string& path) {
     57   int header_size = strlen(kHIDBatteryPathPrefix);
     58   int end_size = strlen(kHIDBatteryPathSuffix);
     59   int key_len = path.size() - header_size - end_size;
     60   if (key_len <= 0)
     61     return std::string();
     62   std::string reverse_address = path.substr(header_size, key_len);
     63   base::StringToLowerASCII(&reverse_address);
     64   std::vector<std::string> result;
     65   base::SplitString(reverse_address, ':', &result);
     66   std::reverse(result.begin(), result.end());
     67   std::string address = JoinString(result, ':');
     68   return address;
     69 }
     70 
     71 class PeripheralBatteryNotificationDelegate : public NotificationDelegate {
     72  public:
     73   explicit PeripheralBatteryNotificationDelegate(const std::string& id)
     74       : id_(id) {}
     75 
     76   // Overridden from NotificationDelegate:
     77   virtual void Display() OVERRIDE {}
     78   virtual void Error() OVERRIDE {}
     79   virtual void Close(bool by_user) OVERRIDE {}
     80   virtual void Click() OVERRIDE {}
     81   virtual std::string id() const OVERRIDE { return id_; }
     82   // A NULL return value prevents loading image from URL. It is OK since our
     83   // implementation loads image from system resource bundle.
     84   virtual content::WebContents* GetWebContents() const OVERRIDE {
     85     return NULL;
     86   }
     87 
     88  private:
     89   virtual ~PeripheralBatteryNotificationDelegate() {}
     90 
     91   const std::string id_;
     92 
     93   DISALLOW_COPY_AND_ASSIGN(PeripheralBatteryNotificationDelegate);
     94 };
     95 
     96 }  // namespace
     97 
     98 PeripheralBatteryObserver::PeripheralBatteryObserver()
     99     : testing_clock_(NULL),
    100       weakptr_factory_(
    101           new base::WeakPtrFactory<PeripheralBatteryObserver>(this)) {
    102   DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(this);
    103   device::BluetoothAdapterFactory::GetAdapter(
    104       base::Bind(&PeripheralBatteryObserver::InitializeOnBluetoothReady,
    105                  weakptr_factory_->GetWeakPtr()));
    106 }
    107 
    108 PeripheralBatteryObserver::~PeripheralBatteryObserver() {
    109   if (bluetooth_adapter_.get())
    110     bluetooth_adapter_->RemoveObserver(this);
    111   DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(this);
    112 }
    113 
    114 void PeripheralBatteryObserver::PeripheralBatteryStatusReceived(
    115     const std::string& path,
    116     const std::string& name,
    117     int level) {
    118   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    119   std::string address;
    120   if (IsBluetoothHIDBattery(path)) {
    121     // For HID bluetooth device, device address is used as key to index
    122     // BatteryInfo.
    123     address = ExtractBluetoothAddress(path);
    124   } else {
    125     LOG(ERROR) << "Unsupported battery path " << path;
    126     return;
    127   }
    128 
    129   if (address.empty()) {
    130     LOG(ERROR) << "No valid battery address at path " << path;
    131     return;
    132   }
    133 
    134   if (level < -1 || level > 100) {
    135     LOG(ERROR) << "Invalid battery level " << level
    136                << " for device " << name << " at path " << path;
    137     return;
    138   }
    139   // If unknown battery level received, cancel any existing notification.
    140   if (level == -1) {
    141     CancelNotification(address);
    142     return;
    143   }
    144 
    145   // Post the notification in 2 cases:
    146   // 1. It's the first time the battery level is received, and it is
    147   //    below kLowBatteryLevel.
    148   // 2. The battery level is in record and it drops below kLowBatteryLevel.
    149   if (batteries_.find(address) == batteries_.end()) {
    150     BatteryInfo battery(name, level, base::TimeTicks());
    151     if (level <= kLowBatteryLevel) {
    152       if (PostNotification(address, battery))
    153         battery.last_notification_timestamp = testing_clock_ ?
    154             testing_clock_->NowTicks() : base::TimeTicks::Now();
    155     }
    156     batteries_[address] = battery;
    157   } else {
    158     BatteryInfo* battery = &batteries_[address];
    159     battery->name = name;
    160     int old_level = battery->level;
    161     battery->level = level;
    162     if (old_level > kLowBatteryLevel && level <= kLowBatteryLevel) {
    163       if (PostNotification(address, *battery))
    164         battery->last_notification_timestamp = testing_clock_ ?
    165             testing_clock_->NowTicks() : base::TimeTicks::Now();
    166     }
    167   }
    168 }
    169 
    170 void PeripheralBatteryObserver::DeviceChanged(device::BluetoothAdapter* adapter,
    171                                               device::BluetoothDevice* device) {
    172   if (!device->IsPaired())
    173     RemoveBattery(device->GetAddress());
    174 }
    175 
    176 void PeripheralBatteryObserver::DeviceRemoved(device::BluetoothAdapter* adapter,
    177                                               device::BluetoothDevice* device) {
    178   RemoveBattery(device->GetAddress());
    179 }
    180 
    181 void PeripheralBatteryObserver::InitializeOnBluetoothReady(
    182     scoped_refptr<device::BluetoothAdapter> adapter) {
    183   bluetooth_adapter_ = adapter;
    184   CHECK(bluetooth_adapter_.get());
    185   bluetooth_adapter_->AddObserver(this);
    186 }
    187 
    188 void PeripheralBatteryObserver::RemoveBattery(const std::string& address) {
    189   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    190   std::string address_lowercase = address;
    191   base::StringToLowerASCII(&address_lowercase);
    192   std::map<std::string, BatteryInfo>::iterator it =
    193       batteries_.find(address_lowercase);
    194   if (it != batteries_.end()) {
    195     batteries_.erase(it);
    196     CancelNotification(address_lowercase);
    197   }
    198 }
    199 
    200 bool PeripheralBatteryObserver::PostNotification(const std::string& address,
    201                                                  const BatteryInfo& battery) {
    202   // Only post notification if kNotificationInterval seconds have passed since
    203   // last notification showed, avoiding the case where the battery level
    204   // oscillates around the threshold level.
    205   base::TimeTicks now = testing_clock_ ? testing_clock_->NowTicks() :
    206       base::TimeTicks::Now();
    207   if (now - battery.last_notification_timestamp <
    208       base::TimeDelta::FromSeconds(kNotificationIntervalSec))
    209     return false;
    210 
    211   NotificationUIManager* notification_manager =
    212       g_browser_process->notification_ui_manager();
    213 
    214   base::string16 string_text = l10n_util::GetStringFUTF16Int(
    215       IDS_ASH_LOW_PERIPHERAL_BATTERY_NOTIFICATION_TEXT,
    216       battery.level);
    217 
    218   Notification notification(
    219       message_center::NOTIFICATION_TYPE_SIMPLE,
    220       GURL(kNotificationOriginUrl),
    221       base::UTF8ToUTF16(battery.name),
    222       string_text,
    223       ui::ResourceBundle::GetSharedInstance().GetImageNamed(
    224           IDR_NOTIFICATION_PERIPHERAL_BATTERY_LOW),
    225       blink::WebTextDirectionDefault,
    226       message_center::NotifierId(GURL(kNotificationOriginUrl)),
    227       base::string16(),
    228       base::UTF8ToUTF16(address),
    229       message_center::RichNotificationData(),
    230       new PeripheralBatteryNotificationDelegate(address));
    231 
    232   notification.set_priority(message_center::SYSTEM_PRIORITY);
    233 
    234   notification_manager->Add(
    235       notification,
    236       ProfileManager::GetPrimaryUserProfile());
    237 
    238   return true;
    239 }
    240 
    241 void PeripheralBatteryObserver::CancelNotification(const std::string& address) {
    242   g_browser_process->notification_ui_manager()->CancelById(address);
    243 }
    244 
    245 }  // namespace chromeos
    246