// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ash/system/chromeos/power/tray_power.h" #include "ash/ash_switches.h" #include "ash/shell.h" #include "ash/system/chromeos/power/power_status_view.h" #include "ash/system/date/date_view.h" #include "ash/system/system_notifier.h" #include "ash/system/tray/system_tray_delegate.h" #include "ash/system/tray/tray_constants.h" #include "ash/system/tray/tray_notification_view.h" #include "ash/system/tray/tray_utils.h" #include "base/command_line.h" #include "base/metrics/histogram.h" #include "grit/ash_resources.h" #include "grit/ash_strings.h" #include "third_party/icu/source/i18n/unicode/fieldpos.h" #include "third_party/icu/source/i18n/unicode/fmtable.h" #include "ui/base/accessibility/accessible_view_state.h" #include "ui/base/resource/resource_bundle.h" #include "ui/message_center/message_center.h" #include "ui/message_center/notification.h" #include "ui/views/controls/button/button.h" #include "ui/views/controls/image_view.h" #include "ui/views/controls/label.h" #include "ui/views/layout/box_layout.h" #include "ui/views/layout/fill_layout.h" #include "ui/views/layout/grid_layout.h" #include "ui/views/view.h" #include "ui/views/widget/widget.h" using message_center::MessageCenter; using message_center::Notification; namespace ash { namespace internal { namespace tray { // This view is used only for the tray. class PowerTrayView : public views::ImageView { public: PowerTrayView() { UpdateImage(); } virtual ~PowerTrayView() { } // Overriden from views::View. virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE { state->name = accessible_name_; state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON; } void UpdateStatus(bool battery_alert) { UpdateImage(); SetVisible(PowerStatus::Get()->IsBatteryPresent()); if (battery_alert) { accessible_name_ = PowerStatus::Get()->GetAccessibleNameString(); NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_ALERT, true); } } private: void UpdateImage() { SetImage(PowerStatus::Get()->GetBatteryImage(PowerStatus::ICON_LIGHT)); } base::string16 accessible_name_; DISALLOW_COPY_AND_ASSIGN(PowerTrayView); }; class PowerNotificationView : public TrayNotificationView { public: explicit PowerNotificationView(TrayPower* owner) : TrayNotificationView(owner, 0) { power_status_view_ = new PowerStatusView(PowerStatusView::VIEW_NOTIFICATION, true); InitView(power_status_view_); } void UpdateStatus() { SetIconImage(PowerStatus::Get()->GetBatteryImage(PowerStatus::ICON_DARK)); } private: PowerStatusView* power_status_view_; DISALLOW_COPY_AND_ASSIGN(PowerNotificationView); }; } // namespace tray using tray::PowerNotificationView; const int TrayPower::kCriticalMinutes = 5; const int TrayPower::kLowPowerMinutes = 15; const int TrayPower::kNoWarningMinutes = 30; const int TrayPower::kCriticalPercentage = 5; const int TrayPower::kLowPowerPercentage = 10; const int TrayPower::kNoWarningPercentage = 15; TrayPower::TrayPower(SystemTray* system_tray, MessageCenter* message_center) : SystemTrayItem(system_tray), message_center_(message_center), power_tray_(NULL), notification_view_(NULL), notification_state_(NOTIFICATION_NONE), usb_charger_was_connected_(false), line_power_was_connected_(false) { PowerStatus::Get()->AddObserver(this); } TrayPower::~TrayPower() { PowerStatus::Get()->RemoveObserver(this); } views::View* TrayPower::CreateTrayView(user::LoginStatus status) { // There may not be enough information when this is created about whether // there is a battery or not. So always create this, and adjust visibility as // necessary. CHECK(power_tray_ == NULL); power_tray_ = new tray::PowerTrayView(); power_tray_->UpdateStatus(false); return power_tray_; } views::View* TrayPower::CreateDefaultView(user::LoginStatus status) { // Make sure icon status is up-to-date. (Also triggers stub activation). PowerStatus::Get()->RequestStatusUpdate(); return NULL; } views::View* TrayPower::CreateNotificationView(user::LoginStatus status) { CHECK(notification_view_ == NULL); if (!PowerStatus::Get()->IsBatteryPresent()) return NULL; notification_view_ = new PowerNotificationView(this); notification_view_->UpdateStatus(); return notification_view_; } void TrayPower::DestroyTrayView() { power_tray_ = NULL; } void TrayPower::DestroyDefaultView() { } void TrayPower::DestroyNotificationView() { notification_view_ = NULL; } void TrayPower::UpdateAfterLoginStatusChange(user::LoginStatus status) { } void TrayPower::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) { SetTrayImageItemBorder(power_tray_, alignment); } void TrayPower::OnPowerStatusChanged() { RecordChargerType(); if (PowerStatus::Get()->IsOriginalSpringChargerConnected()) { ash::Shell::GetInstance()->system_tray_delegate()-> ShowSpringChargerReplacementDialog(); } bool battery_alert = UpdateNotificationState(); if (power_tray_) power_tray_->UpdateStatus(battery_alert); if (notification_view_) notification_view_->UpdateStatus(); // Factory testing may place the battery into unusual states. if (CommandLine::ForCurrentProcess()->HasSwitch( ash::switches::kAshHideNotificationsForFactory)) return; if (ash::switches::UseUsbChargerNotification()) MaybeShowUsbChargerNotification(); if (battery_alert) ShowNotificationView(); else if (notification_state_ == NOTIFICATION_NONE) HideNotificationView(); usb_charger_was_connected_ = PowerStatus::Get()->IsUsbChargerConnected(); line_power_was_connected_ = PowerStatus::Get()->IsLinePowerConnected(); } bool TrayPower::MaybeShowUsbChargerNotification() { ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); const char kNotificationId[] = "usb-charger"; bool usb_charger_is_connected = PowerStatus::Get()->IsUsbChargerConnected(); // Check for a USB charger being connected. if (usb_charger_is_connected && !usb_charger_was_connected_) { scoped_ptr notification(new Notification( message_center::NOTIFICATION_TYPE_SIMPLE, kNotificationId, rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_LOW_POWER_CHARGER_TITLE), rb.GetLocalizedString( IDS_ASH_STATUS_TRAY_LOW_POWER_CHARGER_MESSAGE_SHORT), rb.GetImageNamed(IDR_AURA_NOTIFICATION_LOW_POWER_CHARGER), base::string16(), message_center::NotifierId( message_center::NotifierId::SYSTEM_COMPONENT, system_notifier::kNotifierPower), message_center::RichNotificationData(), NULL)); message_center_->AddNotification(notification.Pass()); return true; } // Check for unplug of a USB charger while the USB charger notification is // showing. if (!usb_charger_is_connected && usb_charger_was_connected_) { message_center_->RemoveNotification(kNotificationId, false); return true; } return false; } bool TrayPower::UpdateNotificationState() { const PowerStatus& status = *PowerStatus::Get(); if (!status.IsBatteryPresent() || status.IsBatteryTimeBeingCalculated() || status.IsMainsChargerConnected() || status.IsOriginalSpringChargerConnected()) { notification_state_ = NOTIFICATION_NONE; return false; } return status.IsUsbChargerConnected() ? UpdateNotificationStateForRemainingPercentage() : UpdateNotificationStateForRemainingTime(); } bool TrayPower::UpdateNotificationStateForRemainingTime() { // The notification includes a rounded minutes value, so round the estimate // received from the power manager to match. const int remaining_minutes = static_cast( PowerStatus::Get()->GetBatteryTimeToEmpty().InSecondsF() / 60.0 + 0.5); if (remaining_minutes >= kNoWarningMinutes || PowerStatus::Get()->IsBatteryFull()) { notification_state_ = NOTIFICATION_NONE; return false; } switch (notification_state_) { case NOTIFICATION_NONE: if (remaining_minutes <= kCriticalMinutes) { notification_state_ = NOTIFICATION_CRITICAL; return true; } if (remaining_minutes <= kLowPowerMinutes) { notification_state_ = NOTIFICATION_LOW_POWER; return true; } return false; case NOTIFICATION_LOW_POWER: if (remaining_minutes <= kCriticalMinutes) { notification_state_ = NOTIFICATION_CRITICAL; return true; } return false; case NOTIFICATION_CRITICAL: return false; } NOTREACHED(); return false; } bool TrayPower::UpdateNotificationStateForRemainingPercentage() { // The notification includes a rounded percentage, so round the value received // from the power manager to match. const int remaining_percentage = PowerStatus::Get()->GetRoundedBatteryPercent(); if (remaining_percentage >= kNoWarningPercentage || PowerStatus::Get()->IsBatteryFull()) { notification_state_ = NOTIFICATION_NONE; return false; } switch (notification_state_) { case NOTIFICATION_NONE: if (remaining_percentage <= kCriticalPercentage) { notification_state_ = NOTIFICATION_CRITICAL; return true; } if (remaining_percentage <= kLowPowerPercentage) { notification_state_ = NOTIFICATION_LOW_POWER; return true; } return false; case NOTIFICATION_LOW_POWER: if (remaining_percentage <= kCriticalPercentage) { notification_state_ = NOTIFICATION_CRITICAL; return true; } return false; case NOTIFICATION_CRITICAL: return false; } NOTREACHED(); return false; } void TrayPower::RecordChargerType() { if (!PowerStatus::Get()->IsLinePowerConnected() || line_power_was_connected_) return; ChargerType current_charger = UNKNOWN_CHARGER; if (PowerStatus::Get()->IsMainsChargerConnected()) { current_charger = MAINS_CHARGER; } else if (PowerStatus::Get()->IsUsbChargerConnected()) { current_charger = USB_CHARGER; } else if (PowerStatus::Get()->IsOriginalSpringChargerConnected()) { current_charger = ash::Shell::GetInstance()->system_tray_delegate()-> HasUserConfirmedSafeSpringCharger() ? SAFE_SPRING_CHARGER : UNCONFIRMED_SPRING_CHARGER; } if (current_charger != UNKNOWN_CHARGER) { UMA_HISTOGRAM_ENUMERATION("Power.ChargerType", current_charger, CHARGER_TYPE_COUNT); } } } // namespace internal } // namespace ash