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/system/chromeos/power/power_status_view.h" 10 #include "ash/system/date/date_view.h" 11 #include "ash/system/system_notifier.h" 12 #include "ash/system/tray/system_tray_delegate.h" 13 #include "ash/system/tray/tray_constants.h" 14 #include "ash/system/tray/tray_notification_view.h" 15 #include "ash/system/tray/tray_utils.h" 16 #include "base/command_line.h" 17 #include "base/metrics/histogram.h" 18 #include "grit/ash_resources.h" 19 #include "grit/ash_strings.h" 20 #include "third_party/icu/source/i18n/unicode/fieldpos.h" 21 #include "third_party/icu/source/i18n/unicode/fmtable.h" 22 #include "ui/base/accessibility/accessible_view_state.h" 23 #include "ui/base/resource/resource_bundle.h" 24 #include "ui/message_center/message_center.h" 25 #include "ui/message_center/notification.h" 26 #include "ui/views/controls/button/button.h" 27 #include "ui/views/controls/image_view.h" 28 #include "ui/views/controls/label.h" 29 #include "ui/views/layout/box_layout.h" 30 #include "ui/views/layout/fill_layout.h" 31 #include "ui/views/layout/grid_layout.h" 32 #include "ui/views/view.h" 33 #include "ui/views/widget/widget.h" 34 35 using message_center::MessageCenter; 36 using message_center::Notification; 37 38 namespace ash { 39 namespace internal { 40 namespace tray { 41 42 // This view is used only for the tray. 43 class PowerTrayView : public views::ImageView { 44 public: 45 PowerTrayView() { 46 UpdateImage(); 47 } 48 49 virtual ~PowerTrayView() { 50 } 51 52 // Overriden from views::View. 53 virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE { 54 state->name = accessible_name_; 55 state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON; 56 } 57 58 void UpdateStatus(bool battery_alert) { 59 UpdateImage(); 60 SetVisible(PowerStatus::Get()->IsBatteryPresent()); 61 62 if (battery_alert) { 63 accessible_name_ = PowerStatus::Get()->GetAccessibleNameString(); 64 NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_ALERT, true); 65 } 66 } 67 68 private: 69 void UpdateImage() { 70 SetImage(PowerStatus::Get()->GetBatteryImage(PowerStatus::ICON_LIGHT)); 71 } 72 73 base::string16 accessible_name_; 74 75 DISALLOW_COPY_AND_ASSIGN(PowerTrayView); 76 }; 77 78 class PowerNotificationView : public TrayNotificationView { 79 public: 80 explicit PowerNotificationView(TrayPower* owner) 81 : TrayNotificationView(owner, 0) { 82 power_status_view_ = 83 new PowerStatusView(PowerStatusView::VIEW_NOTIFICATION, true); 84 InitView(power_status_view_); 85 } 86 87 void UpdateStatus() { 88 SetIconImage(PowerStatus::Get()->GetBatteryImage(PowerStatus::ICON_DARK)); 89 } 90 91 private: 92 PowerStatusView* power_status_view_; 93 94 DISALLOW_COPY_AND_ASSIGN(PowerNotificationView); 95 }; 96 97 } // namespace tray 98 99 using tray::PowerNotificationView; 100 101 const int TrayPower::kCriticalMinutes = 5; 102 const int TrayPower::kLowPowerMinutes = 15; 103 const int TrayPower::kNoWarningMinutes = 30; 104 const int TrayPower::kCriticalPercentage = 5; 105 const int TrayPower::kLowPowerPercentage = 10; 106 const int TrayPower::kNoWarningPercentage = 15; 107 108 TrayPower::TrayPower(SystemTray* system_tray, MessageCenter* message_center) 109 : SystemTrayItem(system_tray), 110 message_center_(message_center), 111 power_tray_(NULL), 112 notification_view_(NULL), 113 notification_state_(NOTIFICATION_NONE), 114 usb_charger_was_connected_(false), 115 line_power_was_connected_(false) { 116 PowerStatus::Get()->AddObserver(this); 117 } 118 119 TrayPower::~TrayPower() { 120 PowerStatus::Get()->RemoveObserver(this); 121 } 122 123 views::View* TrayPower::CreateTrayView(user::LoginStatus status) { 124 // There may not be enough information when this is created about whether 125 // there is a battery or not. So always create this, and adjust visibility as 126 // necessary. 127 CHECK(power_tray_ == NULL); 128 power_tray_ = new tray::PowerTrayView(); 129 power_tray_->UpdateStatus(false); 130 return power_tray_; 131 } 132 133 views::View* TrayPower::CreateDefaultView(user::LoginStatus status) { 134 // Make sure icon status is up-to-date. (Also triggers stub activation). 135 PowerStatus::Get()->RequestStatusUpdate(); 136 return NULL; 137 } 138 139 views::View* TrayPower::CreateNotificationView(user::LoginStatus status) { 140 CHECK(notification_view_ == NULL); 141 if (!PowerStatus::Get()->IsBatteryPresent()) 142 return NULL; 143 144 notification_view_ = new PowerNotificationView(this); 145 notification_view_->UpdateStatus(); 146 147 return notification_view_; 148 } 149 150 void TrayPower::DestroyTrayView() { 151 power_tray_ = NULL; 152 } 153 154 void TrayPower::DestroyDefaultView() { 155 } 156 157 void TrayPower::DestroyNotificationView() { 158 notification_view_ = NULL; 159 } 160 161 void TrayPower::UpdateAfterLoginStatusChange(user::LoginStatus status) { 162 } 163 164 void TrayPower::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) { 165 SetTrayImageItemBorder(power_tray_, alignment); 166 } 167 168 void TrayPower::OnPowerStatusChanged() { 169 RecordChargerType(); 170 171 if (PowerStatus::Get()->IsOriginalSpringChargerConnected()) { 172 ash::Shell::GetInstance()->system_tray_delegate()-> 173 ShowSpringChargerReplacementDialog(); 174 } 175 176 bool battery_alert = UpdateNotificationState(); 177 if (power_tray_) 178 power_tray_->UpdateStatus(battery_alert); 179 if (notification_view_) 180 notification_view_->UpdateStatus(); 181 182 // Factory testing may place the battery into unusual states. 183 if (CommandLine::ForCurrentProcess()->HasSwitch( 184 ash::switches::kAshHideNotificationsForFactory)) 185 return; 186 187 if (ash::switches::UseUsbChargerNotification()) 188 MaybeShowUsbChargerNotification(); 189 190 if (battery_alert) 191 ShowNotificationView(); 192 else if (notification_state_ == NOTIFICATION_NONE) 193 HideNotificationView(); 194 195 usb_charger_was_connected_ = PowerStatus::Get()->IsUsbChargerConnected(); 196 line_power_was_connected_ = PowerStatus::Get()->IsLinePowerConnected(); 197 } 198 199 bool TrayPower::MaybeShowUsbChargerNotification() { 200 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 201 const char kNotificationId[] = "usb-charger"; 202 bool usb_charger_is_connected = PowerStatus::Get()->IsUsbChargerConnected(); 203 204 // Check for a USB charger being connected. 205 if (usb_charger_is_connected && !usb_charger_was_connected_) { 206 scoped_ptr<Notification> notification(new Notification( 207 message_center::NOTIFICATION_TYPE_SIMPLE, 208 kNotificationId, 209 rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_LOW_POWER_CHARGER_TITLE), 210 rb.GetLocalizedString( 211 IDS_ASH_STATUS_TRAY_LOW_POWER_CHARGER_MESSAGE_SHORT), 212 rb.GetImageNamed(IDR_AURA_NOTIFICATION_LOW_POWER_CHARGER), 213 base::string16(), 214 message_center::NotifierId( 215 message_center::NotifierId::SYSTEM_COMPONENT, 216 system_notifier::kNotifierPower), 217 message_center::RichNotificationData(), 218 NULL)); 219 message_center_->AddNotification(notification.Pass()); 220 return true; 221 } 222 223 // Check for unplug of a USB charger while the USB charger notification is 224 // showing. 225 if (!usb_charger_is_connected && usb_charger_was_connected_) { 226 message_center_->RemoveNotification(kNotificationId, false); 227 return true; 228 } 229 return false; 230 } 231 232 bool TrayPower::UpdateNotificationState() { 233 const PowerStatus& status = *PowerStatus::Get(); 234 if (!status.IsBatteryPresent() || 235 status.IsBatteryTimeBeingCalculated() || 236 status.IsMainsChargerConnected() || 237 status.IsOriginalSpringChargerConnected()) { 238 notification_state_ = NOTIFICATION_NONE; 239 return false; 240 } 241 242 return status.IsUsbChargerConnected() ? 243 UpdateNotificationStateForRemainingPercentage() : 244 UpdateNotificationStateForRemainingTime(); 245 } 246 247 bool TrayPower::UpdateNotificationStateForRemainingTime() { 248 // The notification includes a rounded minutes value, so round the estimate 249 // received from the power manager to match. 250 const int remaining_minutes = static_cast<int>( 251 PowerStatus::Get()->GetBatteryTimeToEmpty().InSecondsF() / 60.0 + 0.5); 252 253 if (remaining_minutes >= kNoWarningMinutes || 254 PowerStatus::Get()->IsBatteryFull()) { 255 notification_state_ = NOTIFICATION_NONE; 256 return false; 257 } 258 259 switch (notification_state_) { 260 case NOTIFICATION_NONE: 261 if (remaining_minutes <= kCriticalMinutes) { 262 notification_state_ = NOTIFICATION_CRITICAL; 263 return true; 264 } 265 if (remaining_minutes <= kLowPowerMinutes) { 266 notification_state_ = NOTIFICATION_LOW_POWER; 267 return true; 268 } 269 return false; 270 case NOTIFICATION_LOW_POWER: 271 if (remaining_minutes <= kCriticalMinutes) { 272 notification_state_ = NOTIFICATION_CRITICAL; 273 return true; 274 } 275 return false; 276 case NOTIFICATION_CRITICAL: 277 return false; 278 } 279 NOTREACHED(); 280 return false; 281 } 282 283 bool TrayPower::UpdateNotificationStateForRemainingPercentage() { 284 // The notification includes a rounded percentage, so round the value received 285 // from the power manager to match. 286 const int remaining_percentage = 287 PowerStatus::Get()->GetRoundedBatteryPercent(); 288 289 if (remaining_percentage >= kNoWarningPercentage || 290 PowerStatus::Get()->IsBatteryFull()) { 291 notification_state_ = NOTIFICATION_NONE; 292 return false; 293 } 294 295 switch (notification_state_) { 296 case NOTIFICATION_NONE: 297 if (remaining_percentage <= kCriticalPercentage) { 298 notification_state_ = NOTIFICATION_CRITICAL; 299 return true; 300 } 301 if (remaining_percentage <= kLowPowerPercentage) { 302 notification_state_ = NOTIFICATION_LOW_POWER; 303 return true; 304 } 305 return false; 306 case NOTIFICATION_LOW_POWER: 307 if (remaining_percentage <= kCriticalPercentage) { 308 notification_state_ = NOTIFICATION_CRITICAL; 309 return true; 310 } 311 return false; 312 case NOTIFICATION_CRITICAL: 313 return false; 314 } 315 NOTREACHED(); 316 return false; 317 } 318 319 void TrayPower::RecordChargerType() { 320 if (!PowerStatus::Get()->IsLinePowerConnected() || 321 line_power_was_connected_) 322 return; 323 324 ChargerType current_charger = UNKNOWN_CHARGER; 325 if (PowerStatus::Get()->IsMainsChargerConnected()) { 326 current_charger = MAINS_CHARGER; 327 } else if (PowerStatus::Get()->IsUsbChargerConnected()) { 328 current_charger = USB_CHARGER; 329 } else if (PowerStatus::Get()->IsOriginalSpringChargerConnected()) { 330 current_charger = 331 ash::Shell::GetInstance()->system_tray_delegate()-> 332 HasUserConfirmedSafeSpringCharger() ? 333 SAFE_SPRING_CHARGER : UNCONFIRMED_SPRING_CHARGER; 334 } 335 336 if (current_charger != UNKNOWN_CHARGER) { 337 UMA_HISTOGRAM_ENUMERATION("Power.ChargerType", 338 current_charger, 339 CHARGER_TYPE_COUNT); 340 } 341 } 342 343 } // namespace internal 344 } // namespace ash 345