1// Copyright 2013 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/test/ash_test_base.h"
9#include "base/memory/scoped_ptr.h"
10#include "chromeos/dbus/power_manager/power_supply_properties.pb.h"
11#include "ui/message_center/fake_message_center.h"
12
13using message_center::Notification;
14using power_manager::PowerSupplyProperties;
15
16namespace {
17
18class MockMessageCenter : public message_center::FakeMessageCenter {
19 public:
20  MockMessageCenter() : add_count_(0), remove_count_(0) {}
21  virtual ~MockMessageCenter() {}
22
23  int add_count() const { return add_count_; }
24  int remove_count() const { return remove_count_; }
25
26  // message_center::FakeMessageCenter overrides:
27  virtual void AddNotification(scoped_ptr<Notification> notification) OVERRIDE {
28    add_count_++;
29  }
30  virtual void RemoveNotification(const std::string& id, bool by_user)
31      OVERRIDE {
32    remove_count_++;
33  }
34
35 private:
36  int add_count_;
37  int remove_count_;
38
39  DISALLOW_COPY_AND_ASSIGN(MockMessageCenter);
40};
41
42}  // namespace
43
44namespace ash {
45
46class TrayPowerTest : public test::AshTestBase {
47 public:
48  TrayPowerTest() {}
49  virtual ~TrayPowerTest() {}
50
51  MockMessageCenter* message_center() { return message_center_.get(); }
52  TrayPower* tray_power() { return tray_power_.get(); }
53
54  // test::AshTestBase::SetUp() overrides:
55  virtual void SetUp() OVERRIDE {
56    test::AshTestBase::SetUp();
57    message_center_.reset(new MockMessageCenter());
58    tray_power_.reset(new TrayPower(NULL, message_center_.get()));
59  }
60
61  virtual void TearDown() OVERRIDE {
62    tray_power_.reset();
63    message_center_.reset();
64    test::AshTestBase::TearDown();
65  }
66
67  TrayPower::NotificationState notification_state() const {
68    return tray_power_->notification_state_;
69  }
70
71  bool MaybeShowUsbChargerNotification(const PowerSupplyProperties& proto) {
72    PowerStatus::Get()->SetProtoForTesting(proto);
73    return tray_power_->MaybeShowUsbChargerNotification();
74  }
75
76  bool UpdateNotificationState(const PowerSupplyProperties& proto) {
77    PowerStatus::Get()->SetProtoForTesting(proto);
78    return tray_power_->UpdateNotificationState();
79  }
80
81  void SetUsbChargerConnected(bool connected) {
82    tray_power_->usb_charger_was_connected_ = connected;
83   }
84
85  // Returns a discharging PowerSupplyProperties more appropriate for testing.
86  static PowerSupplyProperties DefaultPowerSupplyProperties() {
87    PowerSupplyProperties proto;
88    proto.set_external_power(
89        power_manager::PowerSupplyProperties_ExternalPower_DISCONNECTED);
90    proto.set_battery_state(
91        power_manager::PowerSupplyProperties_BatteryState_DISCHARGING);
92    proto.set_battery_percent(50.0);
93    proto.set_battery_time_to_empty_sec(3 * 60 * 60);
94    proto.set_battery_time_to_full_sec(2 * 60 * 60);
95    proto.set_is_calculating_battery_time(false);
96    return proto;
97  }
98
99 private:
100  scoped_ptr<MockMessageCenter> message_center_;
101  scoped_ptr<TrayPower> tray_power_;
102
103  DISALLOW_COPY_AND_ASSIGN(TrayPowerTest);
104};
105
106TEST_F(TrayPowerTest, MaybeShowUsbChargerNotification) {
107  PowerSupplyProperties discharging = DefaultPowerSupplyProperties();
108  EXPECT_FALSE(MaybeShowUsbChargerNotification(discharging));
109  EXPECT_EQ(0, message_center()->add_count());
110  EXPECT_EQ(0, message_center()->remove_count());
111
112  // Notification shows when connecting a USB charger.
113  PowerSupplyProperties usb_connected = DefaultPowerSupplyProperties();
114  usb_connected.set_external_power(
115      power_manager::PowerSupplyProperties_ExternalPower_USB);
116  EXPECT_TRUE(MaybeShowUsbChargerNotification(usb_connected));
117  EXPECT_EQ(1, message_center()->add_count());
118  EXPECT_EQ(0, message_center()->remove_count());
119
120  // Change in charge does not trigger the notification again.
121  PowerSupplyProperties more_charge = DefaultPowerSupplyProperties();
122  more_charge.set_external_power(
123      power_manager::PowerSupplyProperties_ExternalPower_USB);
124  more_charge.set_battery_time_to_full_sec(60 * 60);
125  more_charge.set_battery_percent(75.0);
126  SetUsbChargerConnected(true);
127  EXPECT_FALSE(MaybeShowUsbChargerNotification(more_charge));
128  EXPECT_EQ(1, message_center()->add_count());
129  EXPECT_EQ(0, message_center()->remove_count());
130
131  // Disconnecting a USB charger with the notification showing should close
132  // the notification.
133  EXPECT_TRUE(MaybeShowUsbChargerNotification(discharging));
134  EXPECT_EQ(1, message_center()->add_count());
135  EXPECT_EQ(1, message_center()->remove_count());
136}
137
138TEST_F(TrayPowerTest, UpdateNotificationState) {
139  // No notifications when no battery present.
140  PowerSupplyProperties no_battery = DefaultPowerSupplyProperties();
141  no_battery.set_external_power(
142      power_manager::PowerSupplyProperties_ExternalPower_AC);
143  no_battery.set_battery_state(
144      power_manager::PowerSupplyProperties_BatteryState_NOT_PRESENT);
145  EXPECT_FALSE(UpdateNotificationState(no_battery));
146  EXPECT_EQ(TrayPower::NOTIFICATION_NONE, notification_state());
147
148  // No notification when calculating remaining battery time.
149  PowerSupplyProperties calculating = DefaultPowerSupplyProperties();
150  calculating.set_is_calculating_battery_time(true);
151  EXPECT_FALSE(UpdateNotificationState(calculating));
152  EXPECT_EQ(TrayPower::NOTIFICATION_NONE, notification_state());
153
154  // No notification when charging.
155  PowerSupplyProperties charging = DefaultPowerSupplyProperties();
156  charging.set_external_power(
157      power_manager::PowerSupplyProperties_ExternalPower_AC);
158  charging.set_battery_state(
159      power_manager::PowerSupplyProperties_BatteryState_CHARGING);
160  EXPECT_FALSE(UpdateNotificationState(charging));
161  EXPECT_EQ(TrayPower::NOTIFICATION_NONE, notification_state());
162
163  // When the rounded minutes-to-empty are above the threshold, no notification
164  // should be shown.
165  PowerSupplyProperties low = DefaultPowerSupplyProperties();
166  low.set_battery_time_to_empty_sec(TrayPower::kLowPowerMinutes * 60 + 30);
167  EXPECT_FALSE(UpdateNotificationState(low));
168  EXPECT_EQ(TrayPower::NOTIFICATION_NONE, notification_state());
169
170  // When the rounded value matches the threshold, the notification should
171  // appear.
172  low.set_battery_time_to_empty_sec(TrayPower::kLowPowerMinutes * 60 + 29);
173  EXPECT_TRUE(UpdateNotificationState(low));
174  EXPECT_EQ(TrayPower::NOTIFICATION_LOW_POWER, notification_state());
175
176  // It should persist at lower values.
177  low.set_battery_time_to_empty_sec(TrayPower::kLowPowerMinutes * 60 - 20);
178  EXPECT_FALSE(UpdateNotificationState(low));
179  EXPECT_EQ(TrayPower::NOTIFICATION_LOW_POWER, notification_state());
180
181  // The critical low battery notification should be shown when the rounded
182  // value is at the lower threshold.
183  PowerSupplyProperties critical = DefaultPowerSupplyProperties();
184  critical.set_battery_time_to_empty_sec(TrayPower::kCriticalMinutes * 60 + 29);
185  EXPECT_TRUE(UpdateNotificationState(critical));
186  EXPECT_EQ(TrayPower::NOTIFICATION_CRITICAL, notification_state());
187
188  // The notification should be dismissed when the no-warning threshold is
189  // reached.
190  PowerSupplyProperties safe = DefaultPowerSupplyProperties();
191  safe.set_battery_time_to_empty_sec(TrayPower::kNoWarningMinutes * 60 - 29);
192  EXPECT_FALSE(UpdateNotificationState(safe));
193  EXPECT_EQ(TrayPower::NOTIFICATION_NONE, notification_state());
194
195  // Test that rounded percentages are used when a USB charger is connected.
196  PowerSupplyProperties low_usb = DefaultPowerSupplyProperties();
197  low_usb.set_external_power(
198      power_manager::PowerSupplyProperties_ExternalPower_USB);
199  low_usb.set_battery_percent(TrayPower::kLowPowerPercentage + 0.5);
200  EXPECT_FALSE(UpdateNotificationState(low_usb));
201  EXPECT_EQ(TrayPower::NOTIFICATION_NONE, notification_state());
202
203  low_usb.set_battery_percent(TrayPower::kLowPowerPercentage + 0.49);
204  EXPECT_TRUE(UpdateNotificationState(low_usb));
205  EXPECT_EQ(TrayPower::NOTIFICATION_LOW_POWER, notification_state());
206
207  PowerSupplyProperties critical_usb = DefaultPowerSupplyProperties();
208  critical_usb.set_external_power(
209      power_manager::PowerSupplyProperties_ExternalPower_USB);
210  critical_usb.set_battery_percent(TrayPower::kCriticalPercentage + 0.2);
211  EXPECT_TRUE(UpdateNotificationState(critical_usb));
212  EXPECT_EQ(TrayPower::NOTIFICATION_CRITICAL, notification_state());
213
214  PowerSupplyProperties safe_usb = DefaultPowerSupplyProperties();
215  safe_usb.set_external_power(
216      power_manager::PowerSupplyProperties_ExternalPower_USB);
217  safe_usb.set_battery_percent(TrayPower::kNoWarningPercentage - 0.1);
218  EXPECT_FALSE(UpdateNotificationState(safe_usb));
219  EXPECT_EQ(TrayPower::NOTIFICATION_NONE, notification_state());
220
221  // A notification shouldn't be shown when we're in the full state with an
222  // original Spring charger connected: http://crbug.com/338376
223  PowerSupplyProperties spring = DefaultPowerSupplyProperties();
224  spring.set_external_power(power_manager::
225      PowerSupplyProperties_ExternalPower_ORIGINAL_SPRING_CHARGER);
226  spring.set_battery_state(
227      power_manager::PowerSupplyProperties_BatteryState_FULL);
228  spring.set_battery_time_to_empty_sec(0);
229  spring.set_battery_time_to_full_sec(0);
230  EXPECT_FALSE(UpdateNotificationState(spring));
231  EXPECT_EQ(TrayPower::NOTIFICATION_NONE, notification_state());
232}
233
234}  // namespace ash
235