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