| // Copyright 2015 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/system/power/battery_notification.h" |
| |
| #include "ash/constants/ash_features.h" |
| #include "ash/constants/notifier_catalogs.h" |
| #include "ash/public/cpp/notification_utils.h" |
| #include "ash/public/cpp/power_utils.h" |
| #include "ash/resources/vector_icons/vector_icons.h" |
| #include "ash/shell.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/system/power/battery_saver_controller.h" |
| #include "ash/system/power/power_notification_controller.h" |
| #include "ash/system/power/power_status.h" |
| #include "base/i18n/message_formatter.h" |
| #include "base/i18n/time_formatting.h" |
| #include "base/logging.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/time/time.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/l10n/time_format.h" |
| #include "ui/gfx/image/image.h" |
| #include "ui/message_center/message_center.h" |
| #include "ui/message_center/public/cpp/notification.h" |
| |
| using message_center::HandleNotificationClickDelegate; |
| using message_center::MessageCenter; |
| using message_center::Notification; |
| |
| namespace ash { |
| |
| namespace { |
| |
| const char kNotifierBattery[] = "ash.battery"; |
| |
| bool IsNotificationLowPower( |
| PowerNotificationController::NotificationState notification_state) { |
| return notification_state == |
| PowerNotificationController::NOTIFICATION_BSM_THRESHOLD_OPT_IN || |
| notification_state == PowerNotificationController:: |
| NOTIFICATION_BSM_ENABLING_AT_THRESHOLD || |
| notification_state == |
| PowerNotificationController::NOTIFICATION_GENERIC_LOW_POWER; |
| } |
| |
| const gfx::VectorIcon& GetBatteryImageMD( |
| PowerNotificationController::NotificationState notification_state) { |
| if (PowerStatus::Get()->IsUsbChargerConnected()) { |
| return kNotificationBatteryFluctuatingIcon; |
| } else if (IsNotificationLowPower(notification_state)) { |
| return kNotificationBatteryLowIcon; |
| } else if (notification_state == |
| PowerNotificationController::NOTIFICATION_CRITICAL) { |
| return kNotificationBatteryCriticalIcon; |
| } else { |
| NOTREACHED_IN_MIGRATION(); |
| return gfx::kNoneIcon; |
| } |
| } |
| |
| message_center::SystemNotificationWarningLevel GetWarningLevelMD( |
| PowerNotificationController::NotificationState notification_state) { |
| if (PowerStatus::Get()->IsUsbChargerConnected()) { |
| return message_center::SystemNotificationWarningLevel::NORMAL; |
| } else if (IsNotificationLowPower(notification_state)) { |
| return message_center::SystemNotificationWarningLevel::WARNING; |
| } else if (notification_state == |
| PowerNotificationController::NOTIFICATION_CRITICAL) { |
| return message_center::SystemNotificationWarningLevel::CRITICAL_WARNING; |
| } else { |
| NOTREACHED_IN_MIGRATION(); |
| return message_center::SystemNotificationWarningLevel::NORMAL; |
| } |
| } |
| |
| std::u16string GetLowBatteryTitle( |
| PowerNotificationController::NotificationState notification_state) { |
| const bool critical_battery = |
| notification_state == PowerNotificationController::NOTIFICATION_CRITICAL; |
| |
| const bool enabling_at_threshold_notification = |
| IsBatterySaverAllowed() && |
| notification_state == |
| PowerNotificationController::NOTIFICATION_BSM_ENABLING_AT_THRESHOLD; |
| |
| if (critical_battery) { |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_CRITICAL_BATTERY_TITLE); |
| } else if (enabling_at_threshold_notification) { |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_LOW_BATTERY_BSM_AUTOENABLED_TITLE); |
| } |
| |
| return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_LOW_BATTERY_TITLE); |
| } |
| |
| std::u16string GetLowBatteryMessage( |
| PowerNotificationController::NotificationState notification_state, |
| const std::u16string& duration, |
| const double battery_percentage, |
| const bool should_display_time) { |
| const bool critical_notification = |
| notification_state == PowerNotificationController::NOTIFICATION_CRITICAL; |
| |
| const bool enabling_at_threshold_notification = |
| notification_state == |
| PowerNotificationController::NOTIFICATION_BSM_ENABLING_AT_THRESHOLD; |
| |
| const bool opt_in_at_threshold_notification = |
| notification_state == |
| PowerNotificationController::NOTIFICATION_BSM_THRESHOLD_OPT_IN; |
| |
| const bool generic_low_power_notification = |
| notification_state == |
| PowerNotificationController::NOTIFICATION_GENERIC_LOW_POWER; |
| |
| // Send notification immediately with only battery percentage, but update |
| // string to battery percentage + time remaining when available. |
| if (IsBatterySaverAllowed() && enabling_at_threshold_notification) { |
| return should_display_time |
| ? l10n_util::GetStringFUTF16( |
| IDS_ASH_STATUS_TRAY_LOW_BATTERY_BSM_AUTOENABLED_MESSAGE, |
| base::NumberToString16(battery_percentage), duration) |
| : l10n_util::GetStringFUTF16( |
| IDS_ASH_STATUS_TRAY_LOW_BATTERY_BSM_AUTOENABLED_MESSAGE_WITHOUT_TIME, |
| base::NumberToString16(battery_percentage)); |
| } |
| |
| if (IsBatterySaverAllowed() && |
| (opt_in_at_threshold_notification || generic_low_power_notification || |
| critical_notification)) { |
| return should_display_time |
| ? l10n_util::GetStringFUTF16( |
| IDS_ASH_STATUS_TRAY_LOW_BATTERY_BSM_GENERIC_MESSAGE, |
| base::NumberToString16(battery_percentage), duration) |
| : l10n_util::GetStringFUTF16( |
| IDS_ASH_STATUS_TRAY_LOW_BATTERY_BSM_GENERIC_MESSAGE_WITHOUT_TIME, |
| base::NumberToString16(battery_percentage)); |
| } |
| |
| return l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_LOW_BATTERY_MESSAGE, |
| duration, |
| base::NumberToString16(battery_percentage)); |
| } |
| |
| std::optional<int> CalculateNotificationButtonToken( |
| const PowerStatus& status, |
| PowerNotificationController::NotificationState notification_state) { |
| const bool no_notification = |
| notification_state == PowerNotificationController::NOTIFICATION_NONE; |
| const bool generic_low_power_notification = |
| notification_state == |
| PowerNotificationController::NOTIFICATION_GENERIC_LOW_POWER; |
| const bool critical_battery_notification = |
| notification_state == PowerNotificationController::NOTIFICATION_CRITICAL; |
| |
| // There are no buttons to add if either battery saver mode isn't available, |
| // or if it is available, but there are no notifications showing, or if our |
| // battery is a generic low power or critical notification. |
| if (!IsBatterySaverAllowed() || no_notification || |
| generic_low_power_notification || critical_battery_notification) { |
| return std::nullopt; |
| } |
| |
| // Note: At this point, the Notification State could be OPT_OUT, or OPT_IN. |
| const bool is_notification_opt_in = |
| notification_state == |
| PowerNotificationController::NOTIFICATION_BSM_THRESHOLD_OPT_IN; |
| |
| return is_notification_opt_in |
| ? IDS_ASH_STATUS_TRAY_LOW_BATTERY_BSM_BUTTON_OPT_IN |
| : IDS_ASH_STATUS_TRAY_LOW_BATTERY_BSM_BUTTON_OPT_OUT; |
| } |
| |
| void CalculateNotificationButtons( |
| const PowerStatus& status, |
| PowerNotificationController::NotificationState notification_state, |
| message_center::RichNotificationData& rich_notification_data) { |
| std::optional<int> enable_disable_bsm_token_optional = |
| CalculateNotificationButtonToken(status, notification_state); |
| if (enable_disable_bsm_token_optional == std::nullopt) { |
| return; |
| } |
| |
| message_center::ButtonInfo bsm_button{ |
| l10n_util::GetStringUTF16(enable_disable_bsm_token_optional.value())}; |
| rich_notification_data.buttons = |
| std::vector<message_center::ButtonInfo>{bsm_button}; |
| rich_notification_data.settings_button_handler = |
| message_center::SettingsButtonHandler::DELEGATE; |
| } |
| |
| void HandlePowerNotificationButtonClick( |
| std::optional<int> token, |
| PowerNotificationController* power_notification_controller, |
| const std::optional<int> button_index) { |
| if (token == std::nullopt || button_index == std::nullopt) { |
| return; |
| } |
| |
| const bool active = |
| token.value() == IDS_ASH_STATUS_TRAY_LOW_BATTERY_BSM_BUTTON_OPT_IN; |
| |
| // Handle Button functionality based on button pressed, and button text. |
| switch (button_index.value()) { |
| case 0: { |
| Shell::Get()->battery_saver_controller()->SetState( |
| active, BatterySaverController::UpdateReason::kThreshold); |
| |
| if (power_notification_controller) { |
| power_notification_controller->SetUserOptStatus(true); |
| } |
| |
| // Send an 'Enabled' toast if enabling via button. |
| if (active) { |
| Shell::Get() |
| ->battery_saver_controller() |
| ->ShowBatterySaverModeEnabledToast(); |
| } |
| |
| // Dismiss notification from Message Center. |
| message_center::MessageCenter::Get()->RemoveNotification( |
| BatteryNotification::kNotificationId, false); |
| break; |
| } |
| default: |
| NOTREACHED_NORETURN(); |
| } |
| } |
| |
| std::unique_ptr<Notification> CreateNotification( |
| PowerNotificationController* power_notification_controller) { |
| const PowerStatus& status = *PowerStatus::Get(); |
| |
| const double battery_percentage = status.GetRoundedBatteryPercent(); |
| |
| const auto notification_state = |
| power_notification_controller->GetNotificationState(); |
| |
| std::u16string title = |
| l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_BATTERY_PERCENT_TITLE); |
| std::u16string message = base::i18n::MessageFormatter::FormatWithNumberedArgs( |
| l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_BATTERY_PERCENT), |
| battery_percentage / 100.0); |
| |
| const std::optional<base::TimeDelta> time = |
| status.IsBatteryCharging() ? status.GetBatteryTimeToFull() |
| : status.GetBatteryTimeToEmpty(); |
| |
| message_center::RichNotificationData rich_notification_data; |
| |
| if (status.IsUsbChargerConnected()) { |
| title = |
| l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_LOW_POWER_CHARGER_TITLE); |
| message = l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_BATTERY_CHARGING_UNRELIABLE); |
| } else if (time && |
| (power_utils::ShouldDisplayBatteryTime(*time) || |
| IsBatterySaverAllowed()) && |
| !status.IsBatteryDischargingOnLinePower()) { |
| std::u16string duration = ui::TimeFormat::Simple( |
| ui::TimeFormat::FORMAT_DURATION, ui::TimeFormat::LENGTH_LONG, *time); |
| if (status.IsBatteryCharging()) { |
| title = |
| l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_BATTERY_CHARGING_TITLE); |
| message = l10n_util::GetStringFUTF16( |
| IDS_ASH_STATUS_TRAY_BATTERY_TIME_UNTIL_FULL, duration); |
| } else { |
| // Low battery notifications should display on fullscreen windows. |
| rich_notification_data.fullscreen_visibility = |
| message_center::FullscreenVisibility::OVER_USER; |
| |
| // Calculate the title, message, and buttons based on the power state. |
| title = GetLowBatteryTitle(notification_state); |
| message = |
| GetLowBatteryMessage(notification_state, duration, battery_percentage, |
| power_utils::ShouldDisplayBatteryTime(*time)); |
| CalculateNotificationButtons(status, notification_state, |
| rich_notification_data); |
| } |
| } |
| |
| std::unique_ptr<Notification> notification = ash::CreateSystemNotificationPtr( |
| message_center::NOTIFICATION_TYPE_SIMPLE, |
| BatteryNotification::kNotificationId, title, message, std::u16string(), |
| GURL(), |
| message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT, |
| kNotifierBattery, |
| NotificationCatalogName::kBatteryNotifier), |
| rich_notification_data, |
| base::MakeRefCounted<message_center::HandleNotificationClickDelegate>( |
| base::BindRepeating( |
| &HandlePowerNotificationButtonClick, |
| CalculateNotificationButtonToken(status, notification_state), |
| power_notification_controller)), |
| GetBatteryImageMD(notification_state), |
| GetWarningLevelMD(notification_state)); |
| if (notification_state == |
| PowerNotificationController::NOTIFICATION_CRITICAL) { |
| notification->SetSystemPriority(); |
| notification->set_pinned(true); |
| } |
| return notification; |
| } |
| |
| } // namespace |
| |
| // static |
| const char BatteryNotification::kNotificationId[] = "battery"; |
| |
| BatteryNotification::BatteryNotification( |
| MessageCenter* message_center, |
| PowerNotificationController* power_notification_controller) |
| : message_center_(message_center), |
| power_notification_controller_(power_notification_controller) { |
| message_center_->AddNotification( |
| CreateNotification(power_notification_controller_)); |
| } |
| |
| BatteryNotification::~BatteryNotification() { |
| if (message_center_->FindVisibleNotificationById(kNotificationId)) { |
| message_center_->RemoveNotification(kNotificationId, false); |
| } |
| } |
| |
| void BatteryNotification::Update() { |
| if (message_center_->FindVisibleNotificationById(kNotificationId)) { |
| message_center_->UpdateNotification( |
| kNotificationId, CreateNotification(power_notification_controller_)); |
| } |
| } |
| |
| } // namespace ash |