blob: 596ab05c2af98aa052cd969d1fe67626294ff46d [file] [log] [blame]
// Copyright 2018 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/power/power_notification_controller.h"
#include "ash/public/cpp/ash_switches.h"
#include "ash/public/cpp/notification_utils.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/power/battery_notification.h"
#include "ash/system/power/dual_role_notification.h"
#include "base/command_line.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/chromeos/devicetype_utils.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/public/cpp/notification_delegate.h"
using message_center::MessageCenter;
using message_center::Notification;
namespace ash {
namespace {
const char kNotifierPower[] = "ash.power";
// Informs the PowerNotificationController when a USB notification is closed.
class UsbNotificationDelegate : public message_center::NotificationDelegate {
public:
explicit UsbNotificationDelegate(PowerNotificationController* controller)
: controller_(controller) {}
// Overridden from message_center::NotificationDelegate.
void Close(bool by_user) override {
if (by_user)
controller_->NotifyUsbNotificationClosedByUser();
}
private:
~UsbNotificationDelegate() override = default;
PowerNotificationController* const controller_;
DISALLOW_COPY_AND_ASSIGN(UsbNotificationDelegate);
};
std::string GetNotificationStateString(
PowerNotificationController::NotificationState notification_state) {
switch (notification_state) {
case PowerNotificationController::NOTIFICATION_NONE:
return "none";
case PowerNotificationController::NOTIFICATION_LOW_POWER:
return "low power";
case PowerNotificationController::NOTIFICATION_CRITICAL:
return "critical power";
}
NOTREACHED() << "Unknown state " << notification_state;
return "Unknown state";
}
void LogBatteryForUsbCharger(
PowerNotificationController::NotificationState state,
int battery_percent) {
VLOG(1) << "Showing " << GetNotificationStateString(state)
<< " notification. USB charger is connected. "
<< "Battery percentage: " << battery_percent << "%.";
}
void LogBatteryForNoCharger(
PowerNotificationController::NotificationState state,
int remaining_minutes) {
VLOG(1) << "Showing " << GetNotificationStateString(state)
<< " notification. No charger connected."
<< " Remaining time: " << remaining_minutes << " minutes.";
}
} // namespace
const char PowerNotificationController::kUsbNotificationId[] = "usb-charger";
PowerNotificationController::PowerNotificationController(
message_center::MessageCenter* message_center)
: message_center_(message_center) {
PowerStatus::Get()->AddObserver(this);
}
PowerNotificationController::~PowerNotificationController() {
PowerStatus::Get()->RemoveObserver(this);
message_center_->RemoveNotification(kUsbNotificationId, false);
}
void PowerNotificationController::OnPowerStatusChanged() {
bool battery_alert = UpdateNotificationState();
// Factory testing may place the battery into unusual states.
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kAshHideNotificationsForFactory)) {
return;
}
MaybeShowUsbChargerNotification();
MaybeShowDualRoleNotification();
if (battery_alert) {
// Remove any existing notification so it's dismissed before adding a new
// one. Otherwise we might update a "low battery" notification to "critical"
// without it being shown again.
battery_notification_.reset();
battery_notification_.reset(
new BatteryNotification(message_center_, notification_state_));
} else if (notification_state_ == NOTIFICATION_NONE) {
battery_notification_.reset();
} else if (battery_notification_.get()) {
battery_notification_->Update(notification_state_);
}
battery_was_full_ = PowerStatus::Get()->IsBatteryFull();
usb_charger_was_connected_ = PowerStatus::Get()->IsUsbChargerConnected();
line_power_was_connected_ = PowerStatus::Get()->IsLinePowerConnected();
}
bool PowerNotificationController::MaybeShowUsbChargerNotification() {
const PowerStatus& status = *PowerStatus::Get();
// We show the notification if a USB charger is connected but the battery
// isn't full (since some ECs may choose to use a lower power rail when the
// battery is full even when a high-power charger is connected).
const bool show = status.IsUsbChargerConnected() && !status.IsBatteryFull();
// Check if the notification needs to be created.
if (show && !usb_charger_was_connected_ && !usb_notification_dismissed_) {
std::unique_ptr<Notification> notification = ash::CreateSystemNotification(
message_center::NOTIFICATION_TYPE_SIMPLE, kUsbNotificationId,
l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_LOW_POWER_CHARGER_TITLE),
ui::SubstituteChromeOSDeviceType(
IDS_ASH_STATUS_TRAY_LOW_POWER_CHARGER_MESSAGE_SHORT),
base::string16(), GURL(),
message_center::NotifierId(
message_center::NotifierType::SYSTEM_COMPONENT, kNotifierPower),
message_center::RichNotificationData(),
new UsbNotificationDelegate(this), kNotificationLowPowerChargerIcon,
message_center::SystemNotificationWarningLevel::WARNING);
notification->set_priority(message_center::SYSTEM_PRIORITY);
message_center_->AddNotification(std::move(notification));
return true;
}
if (!show && usb_charger_was_connected_ && !battery_was_full_) {
// USB charger was unplugged or identified as a different type or battery
// reached the full state while the notification was showing.
message_center_->RemoveNotification(kUsbNotificationId, false);
if (!status.IsLinePowerConnected())
usb_notification_dismissed_ = false;
return true;
}
return false;
}
void PowerNotificationController::MaybeShowDualRoleNotification() {
const PowerStatus& status = *PowerStatus::Get();
if (!status.HasDualRoleDevices()) {
dual_role_notification_.reset();
return;
}
if (!dual_role_notification_)
dual_role_notification_.reset(new DualRoleNotification(message_center_));
dual_role_notification_->Update();
}
bool PowerNotificationController::UpdateNotificationState() {
const PowerStatus& status = *PowerStatus::Get();
if (!status.IsBatteryPresent() || status.IsBatteryTimeBeingCalculated() ||
status.IsMainsChargerConnected()) {
notification_state_ = NOTIFICATION_NONE;
return false;
}
return status.IsUsbChargerConnected()
? UpdateNotificationStateForRemainingPercentage()
: UpdateNotificationStateForRemainingTime();
}
bool PowerNotificationController::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<int>(
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;
LogBatteryForNoCharger(notification_state_, remaining_minutes);
return true;
}
if (remaining_minutes <= kLowPowerMinutes) {
notification_state_ = NOTIFICATION_LOW_POWER;
LogBatteryForNoCharger(notification_state_, remaining_minutes);
return true;
}
return false;
case NOTIFICATION_LOW_POWER:
if (remaining_minutes <= kCriticalMinutes) {
notification_state_ = NOTIFICATION_CRITICAL;
LogBatteryForNoCharger(notification_state_, remaining_minutes);
return true;
}
return false;
case NOTIFICATION_CRITICAL:
return false;
}
NOTREACHED();
return false;
}
bool PowerNotificationController::
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;
LogBatteryForUsbCharger(notification_state_, remaining_percentage);
return true;
}
if (remaining_percentage <= kLowPowerPercentage) {
notification_state_ = NOTIFICATION_LOW_POWER;
LogBatteryForUsbCharger(notification_state_, remaining_percentage);
return true;
}
return false;
case NOTIFICATION_LOW_POWER:
if (remaining_percentage <= kCriticalPercentage) {
notification_state_ = NOTIFICATION_CRITICAL;
LogBatteryForUsbCharger(notification_state_, remaining_percentage);
return true;
}
return false;
case NOTIFICATION_CRITICAL:
return false;
}
NOTREACHED();
return false;
}
void PowerNotificationController::NotifyUsbNotificationClosedByUser() {
usb_notification_dismissed_ = true;
}
} // namespace ash