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