tray_power.cc revision 868fa2fe829687343ffae624259930155e16dbd8
1// Copyright (c) 2012 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 "ash/system/chromeos/power/tray_power.h" 6 7#include "ash/ash_switches.h" 8#include "ash/shell.h" 9#include "ash/shell_delegate.h" 10#include "ash/system/chromeos/power/power_status_view.h" 11#include "ash/system/date/date_view.h" 12#include "ash/system/tray/tray_constants.h" 13#include "ash/system/tray/tray_notification_view.h" 14#include "ash/system/tray/tray_utils.h" 15#include "base/command_line.h" 16#include "base/strings/string_number_conversions.h" 17#include "base/strings/stringprintf.h" 18#include "base/strings/utf_string_conversions.h" 19#include "chromeos/dbus/power_supply_status.h" 20#include "grit/ash_resources.h" 21#include "grit/ash_strings.h" 22#include "third_party/icu/public/i18n/unicode/fieldpos.h" 23#include "third_party/icu/public/i18n/unicode/fmtable.h" 24#include "third_party/skia/include/core/SkRect.h" 25#include "ui/base/accessibility/accessible_view_state.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#include "ui/gfx/image/image_skia.h" 30#include "ui/gfx/image/image_skia_operations.h" 31#include "ui/gfx/size.h" 32#include "ui/views/controls/button/button.h" 33#include "ui/views/controls/image_view.h" 34#include "ui/views/controls/label.h" 35#include "ui/views/layout/box_layout.h" 36#include "ui/views/layout/fill_layout.h" 37#include "ui/views/layout/grid_layout.h" 38#include "ui/views/view.h" 39#include "ui/views/widget/widget.h" 40 41using chromeos::PowerManagerHandler; 42using chromeos::PowerSupplyStatus; 43 44namespace ash { 45namespace internal { 46 47namespace { 48// Width and height of battery images. 49const int kBatteryImageHeight = 25; 50const int kBatteryImageWidth = 25; 51// Number of different power states. 52const int kNumPowerImages = 15; 53// Top/bottom padding of the text items. 54const int kPaddingVertical = 10; 55// Specify min width of status label for layout. 56const int kLabelMinWidth = 120; 57// Notification times. 58const int kCriticalSeconds = 5 * 60; 59const int kLowPowerSeconds = 15 * 60; 60const int kNoWarningSeconds = 30 * 60; 61// Minimum battery percentage rendered in UI. 62const int kMinBatteryPercent = 1; 63// Notification in battery percentage. 64const double kCriticalPercentage = 5.0; 65const double kLowPowerPercentage = 10.0; 66const double kNoWarningPercentage = 15.0; 67 68base::string16 GetBatteryTimeAccessibilityString(int hour, int min) { 69 DCHECK(hour || min); 70 if (hour && !min) { 71 return Shell::GetInstance()->delegate()->GetTimeDurationLongString( 72 base::TimeDelta::FromHours(hour)); 73 } 74 if (min && !hour) { 75 return Shell::GetInstance()->delegate()->GetTimeDurationLongString( 76 base::TimeDelta::FromMinutes(min)); 77 } 78 return l10n_util::GetStringFUTF16( 79 IDS_ASH_STATUS_TRAY_BATTERY_TIME_ACCESSIBLE, 80 Shell::GetInstance()->delegate()->GetTimeDurationLongString( 81 base::TimeDelta::FromHours(hour)), 82 Shell::GetInstance()->delegate()->GetTimeDurationLongString( 83 base::TimeDelta::FromMinutes(min))); 84} 85 86} // namespace 87 88namespace tray { 89 90// This view is used only for the tray. 91class PowerTrayView : public views::ImageView { 92 public: 93 PowerTrayView() 94 : battery_icon_index_(-1), 95 battery_icon_offset_(0), 96 battery_charging_unreliable_(false) { 97 UpdateImage(); 98 } 99 100 virtual ~PowerTrayView() { 101 } 102 103 // Overriden from views::View. 104 virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE { 105 state->name = accessible_name_; 106 state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON; 107 } 108 109 void UpdatePowerStatus(const PowerSupplyStatus& status, 110 bool battery_alert) { 111 supply_status_ = status; 112 // Sanitize. 113 if (supply_status_.battery_is_full) 114 supply_status_.battery_percentage = 100.0; 115 116 UpdateImage(); 117 SetVisible(status.battery_is_present); 118 119 if (battery_alert) { 120 accessible_name_ = TrayPower::GetAccessibleNameString(status); 121 NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_ALERT, true); 122 } 123 } 124 125 private: 126 void UpdateImage() { 127 int index = TrayPower::GetBatteryImageIndex(supply_status_); 128 int offset = TrayPower::GetBatteryImageOffset(supply_status_); 129 bool charging_unreliable = 130 TrayPower::IsBatteryChargingUnreliable(supply_status_); 131 if (battery_icon_index_ != index || 132 battery_icon_offset_ != offset || 133 battery_charging_unreliable_ != charging_unreliable) { 134 battery_icon_index_ = index; 135 battery_icon_offset_ = offset; 136 battery_charging_unreliable_ = charging_unreliable; 137 if (battery_icon_index_ != -1) 138 SetImage(TrayPower::GetBatteryImage(battery_icon_index_, 139 battery_icon_offset_, 140 battery_charging_unreliable_, 141 ICON_LIGHT)); 142 } 143 } 144 145 PowerSupplyStatus supply_status_; 146 base::string16 accessible_name_; 147 148 // Index of the current icon in the icon array image, or -1 if unknown. 149 int battery_icon_index_; 150 int battery_icon_offset_; 151 bool battery_charging_unreliable_; 152 153 DISALLOW_COPY_AND_ASSIGN(PowerTrayView); 154}; 155 156class PowerNotificationView : public TrayNotificationView { 157 public: 158 explicit PowerNotificationView(TrayPower* owner) 159 : TrayNotificationView(owner, 0), 160 battery_icon_index_(-1), 161 battery_icon_offset_(0), 162 battery_charging_unreliable_(false) { 163 power_status_view_ = 164 new PowerStatusView(PowerStatusView::VIEW_NOTIFICATION, true); 165 InitView(power_status_view_); 166 } 167 168 void UpdatePowerStatus(const PowerSupplyStatus& status) { 169 int index = TrayPower::GetBatteryImageIndex(status); 170 int offset = TrayPower::GetBatteryImageOffset(status); 171 bool charging_unreliable = TrayPower::IsBatteryChargingUnreliable(status); 172 if (battery_icon_index_ != index || 173 battery_icon_offset_ != offset || 174 battery_charging_unreliable_ != charging_unreliable) { 175 battery_icon_index_ = index; 176 battery_icon_offset_ = offset; 177 battery_charging_unreliable_ = charging_unreliable; 178 if (battery_icon_index_ != -1) { 179 SetIconImage(TrayPower::GetBatteryImage( 180 battery_icon_index_, 181 battery_icon_offset_, 182 battery_charging_unreliable_, 183 ICON_DARK)); 184 } 185 } 186 power_status_view_->UpdatePowerStatus(status); 187 } 188 189 private: 190 PowerStatusView* power_status_view_; 191 192 // Index of the current icon in the icon array image, or -1 if unknown. 193 int battery_icon_index_; 194 int battery_icon_offset_; 195 bool battery_charging_unreliable_; 196 197 DISALLOW_COPY_AND_ASSIGN(PowerNotificationView); 198}; 199 200} // namespace tray 201 202using tray::PowerNotificationView; 203 204TrayPower::TrayPower(SystemTray* system_tray) 205 : SystemTrayItem(system_tray), 206 power_tray_(NULL), 207 notification_view_(NULL), 208 notification_state_(NOTIFICATION_NONE) { 209 PowerManagerHandler::Get()->AddObserver(this); 210} 211 212TrayPower::~TrayPower() { 213 if (PowerManagerHandler::IsInitialized()) 214 PowerManagerHandler::Get()->RemoveObserver(this); 215} 216 217// static 218bool TrayPower::IsBatteryChargingUnreliable( 219 const chromeos::PowerSupplyStatus& supply_status) { 220 // Sometimes devices can get into a state where the battery is almost fully 221 // charged and the power subsystem reports "neither charging nor discharging" 222 // despite the battery not at 100%. For now, only report unreliable charging 223 // on USB. 224 // TODO(derat): Update this when the power manager code is refactored for M29. 225 return supply_status.battery_state == PowerSupplyStatus::CONNECTED_TO_USB; 226} 227 228// static 229int TrayPower::GetBatteryImageIndex( 230 const chromeos::PowerSupplyStatus& supply_status) { 231 int image_index = 0; 232 if (supply_status.battery_percentage >= 100) { 233 image_index = kNumPowerImages - 1; 234 } else if (!supply_status.battery_is_present) { 235 image_index = kNumPowerImages; 236 } else { 237 image_index = static_cast<int>(supply_status.battery_percentage / 238 100.0 * (kNumPowerImages - 1)); 239 image_index = std::max(std::min(image_index, kNumPowerImages - 2), 0); 240 } 241 return image_index; 242} 243 244// static 245int TrayPower::GetBatteryImageOffset( 246 const chromeos::PowerSupplyStatus& supply_status) { 247 if (IsBatteryChargingUnreliable(supply_status) || 248 !supply_status.line_power_on) 249 return 0; 250 return 1; 251} 252 253// static 254gfx::ImageSkia TrayPower::GetBatteryImage(int image_index, 255 int image_offset, 256 bool charging_unreliable, 257 IconSet icon_set) { 258 gfx::Image all; 259 if (charging_unreliable) { 260 all = ui::ResourceBundle::GetSharedInstance().GetImageNamed( 261 icon_set == ICON_DARK ? 262 IDR_AURA_UBER_TRAY_POWER_SMALL_CHARGING_UNRELIABLE_DARK : 263 IDR_AURA_UBER_TRAY_POWER_SMALL_CHARGING_UNRELIABLE); 264 } else { 265 all = ui::ResourceBundle::GetSharedInstance().GetImageNamed( 266 icon_set == ICON_DARK ? 267 IDR_AURA_UBER_TRAY_POWER_SMALL_DARK : IDR_AURA_UBER_TRAY_POWER_SMALL); 268 } 269 gfx::Rect region( 270 image_offset * kBatteryImageWidth, 271 image_index * kBatteryImageHeight, 272 kBatteryImageWidth, kBatteryImageHeight); 273 return gfx::ImageSkiaOperations::ExtractSubset(*all.ToImageSkia(), region); 274} 275 276// static 277base::string16 TrayPower::GetAccessibleNameString( 278 const chromeos::PowerSupplyStatus& supply_status) { 279 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 280 if (supply_status.line_power_on && supply_status.battery_is_full) { 281 return rb.GetLocalizedString( 282 IDS_ASH_STATUS_TRAY_BATTERY_FULL_CHARGE_ACCESSIBLE); 283 } 284 bool charging_unreliable = 285 IsBatteryChargingUnreliable(supply_status); 286 if (supply_status.battery_percentage < 0.0f) { 287 if (charging_unreliable) { 288 return rb.GetLocalizedString( 289 IDS_ASH_STATUS_TRAY_BATTERY_CHARGING_UNRELIABLE_ACCESSIBLE); 290 } 291 return rb.GetLocalizedString( 292 IDS_ASH_STATUS_TRAY_BATTERY_CALCULATING_ACCESSIBLE); 293 } 294 base::string16 battery_percentage_accessbile = l10n_util::GetStringFUTF16( 295 supply_status.line_power_on ? 296 IDS_ASH_STATUS_TRAY_BATTERY_PERCENT_CHARGING_ACCESSIBLE: 297 IDS_ASH_STATUS_TRAY_BATTERY_PERCENT_ACCESSIBLE , 298 base::IntToString16(GetRoundedBatteryPercentage( 299 supply_status.battery_percentage))); 300 base::string16 battery_time_accessible = base::string16(); 301 if (charging_unreliable) { 302 battery_time_accessible = rb.GetLocalizedString( 303 IDS_ASH_STATUS_TRAY_BATTERY_CHARGING_UNRELIABLE_ACCESSIBLE); 304 } else { 305 if (supply_status.is_calculating_battery_time) { 306 battery_time_accessible = rb.GetLocalizedString( 307 IDS_ASH_STATUS_TRAY_BATTERY_CALCULATING_ACCESSIBLE); 308 } else { 309 base::TimeDelta time = base::TimeDelta::FromSeconds( 310 supply_status.line_power_on ? 311 supply_status.battery_seconds_to_full : 312 supply_status.battery_seconds_to_empty); 313 int hour = time.InHours(); 314 int min = (time - base::TimeDelta::FromHours(hour)).InMinutes(); 315 if (hour || min) { 316 base::string16 minute = min < 10 ? 317 ASCIIToUTF16("0") + base::IntToString16(min) : 318 base::IntToString16(min); 319 battery_time_accessible = 320 l10n_util::GetStringFUTF16( 321 supply_status.line_power_on ? 322 IDS_ASH_STATUS_TRAY_BATTERY_TIME_UNTIL_FULL_ACCESSIBLE : 323 IDS_ASH_STATUS_TRAY_BATTERY_TIME_LEFT_ACCESSIBLE, 324 GetBatteryTimeAccessibilityString(hour, min)); 325 } 326 } 327 } 328 return battery_time_accessible.empty() ? 329 battery_percentage_accessbile : 330 battery_percentage_accessbile + ASCIIToUTF16(". ") 331 + battery_time_accessible; 332} 333 334// static 335int TrayPower::GetRoundedBatteryPercentage(double battery_percentage) { 336 DCHECK(battery_percentage >= 0.0); 337 return std::max(kMinBatteryPercent, 338 static_cast<int>(battery_percentage + 0.5)); 339} 340 341views::View* TrayPower::CreateTrayView(user::LoginStatus status) { 342 // There may not be enough information when this is created about whether 343 // there is a battery or not. So always create this, and adjust visibility as 344 // necessary. 345 PowerSupplyStatus power_status = 346 PowerManagerHandler::Get()->GetPowerSupplyStatus(); 347 CHECK(power_tray_ == NULL); 348 power_tray_ = new tray::PowerTrayView(); 349 power_tray_->UpdatePowerStatus(power_status, false); 350 return power_tray_; 351} 352 353views::View* TrayPower::CreateDefaultView(user::LoginStatus status) { 354 // Make sure icon status is up-to-date. (Also triggers stub activation). 355 RequestStatusUpdate(); 356 return NULL; 357} 358 359views::View* TrayPower::CreateNotificationView(user::LoginStatus status) { 360 CHECK(notification_view_ == NULL); 361 PowerSupplyStatus power_status = 362 PowerManagerHandler::Get()->GetPowerSupplyStatus(); 363 if (!power_status.battery_is_present) 364 return NULL; 365 366 notification_view_ = new PowerNotificationView(this); 367 notification_view_->UpdatePowerStatus(power_status); 368 369 return notification_view_; 370} 371 372void TrayPower::DestroyTrayView() { 373 power_tray_ = NULL; 374} 375 376void TrayPower::DestroyDefaultView() { 377} 378 379void TrayPower::DestroyNotificationView() { 380 notification_view_ = NULL; 381} 382 383void TrayPower::UpdateAfterLoginStatusChange(user::LoginStatus status) { 384} 385 386void TrayPower::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) { 387 SetTrayImageItemBorder(power_tray_, alignment); 388} 389 390void TrayPower::OnPowerStatusChanged( 391 const chromeos::PowerSupplyStatus& status) { 392 bool battery_alert = UpdateNotificationState(status); 393 if (power_tray_) 394 power_tray_->UpdatePowerStatus(status, battery_alert); 395 if (notification_view_) 396 notification_view_->UpdatePowerStatus(status); 397 398 if (battery_alert) 399 ShowNotificationView(); 400 else if (notification_state_ == NOTIFICATION_NONE) 401 HideNotificationView(); 402} 403 404void TrayPower::RequestStatusUpdate() const { 405 PowerManagerHandler::Get()->RequestStatusUpdate(); 406} 407 408bool TrayPower::UpdateNotificationState( 409 const chromeos::PowerSupplyStatus& status) { 410 if (!status.battery_is_present || 411 status.is_calculating_battery_time || 412 status.battery_state == PowerSupplyStatus::CHARGING) { 413 notification_state_ = NOTIFICATION_NONE; 414 return false; 415 } 416 417 if (TrayPower::IsBatteryChargingUnreliable(status)) { 418 return UpdateNotificationStateForRemainingPercentage( 419 status.battery_percentage); 420 } else { 421 return UpdateNotificationStateForRemainingTime( 422 status.battery_seconds_to_empty); 423 } 424} 425 426bool TrayPower::UpdateNotificationStateForRemainingTime(int remaining_seconds) { 427 if (remaining_seconds >= kNoWarningSeconds) { 428 notification_state_ = NOTIFICATION_NONE; 429 return false; 430 } 431 432 switch (notification_state_) { 433 case NOTIFICATION_NONE: 434 if (remaining_seconds <= kCriticalSeconds) { 435 notification_state_ = NOTIFICATION_CRITICAL; 436 return true; 437 } else if (remaining_seconds <= kLowPowerSeconds) { 438 notification_state_ = NOTIFICATION_LOW_POWER; 439 return true; 440 } 441 return false; 442 case NOTIFICATION_LOW_POWER: 443 if (remaining_seconds <= kCriticalSeconds) { 444 notification_state_ = NOTIFICATION_CRITICAL; 445 return true; 446 } 447 return false; 448 case NOTIFICATION_CRITICAL: 449 return false; 450 } 451 NOTREACHED(); 452 return false; 453} 454 455bool TrayPower::UpdateNotificationStateForRemainingPercentage( 456 double remaining_percentage) { 457 if (remaining_percentage > kNoWarningPercentage) { 458 notification_state_ = NOTIFICATION_NONE; 459 return false; 460 } 461 462 switch (notification_state_) { 463 case NOTIFICATION_NONE: 464 if (remaining_percentage <= kCriticalPercentage) { 465 notification_state_ = NOTIFICATION_CRITICAL; 466 return true; 467 } 468 if (remaining_percentage <= kLowPowerPercentage) { 469 notification_state_ = NOTIFICATION_LOW_POWER; 470 return true; 471 } 472 return false; 473 case NOTIFICATION_LOW_POWER: 474 if (remaining_percentage <= kCriticalPercentage) { 475 notification_state_ = NOTIFICATION_CRITICAL; 476 return true; 477 } 478 return false; 479 case NOTIFICATION_CRITICAL: 480 return false; 481 } 482 NOTREACHED(); 483 return false; 484} 485 486} // namespace internal 487} // namespace ash 488