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/brightness/tray_brightness.h"
6
7#include <algorithm>
8
9#include "ash/accelerators/accelerator_controller.h"
10#include "ash/ash_constants.h"
11#include "ash/display/display_manager.h"
12#include "ash/metrics/user_metrics_recorder.h"
13#include "ash/shell.h"
14#include "ash/shell_observer.h"
15#include "ash/system/brightness_control_delegate.h"
16#include "ash/system/tray/fixed_sized_image_view.h"
17#include "ash/system/tray/system_tray_delegate.h"
18#include "ash/system/tray/system_tray_notifier.h"
19#include "ash/system/tray/tray_constants.h"
20#include "ash/wm/maximize_mode/maximize_mode_controller.h"
21#include "base/bind.h"
22#include "base/message_loop/message_loop.h"
23#include "base/strings/utf_string_conversions.h"
24#include "chromeos/dbus/dbus_thread_manager.h"
25#include "chromeos/dbus/power_manager_client.h"
26#include "grit/ash_resources.h"
27#include "grit/ash_strings.h"
28#include "ui/base/resource/resource_bundle.h"
29#include "ui/gfx/image/image.h"
30#include "ui/views/controls/button/image_button.h"
31#include "ui/views/controls/image_view.h"
32#include "ui/views/controls/label.h"
33#include "ui/views/controls/slider.h"
34#include "ui/views/layout/box_layout.h"
35#include "ui/views/view.h"
36
37namespace ash {
38namespace tray {
39namespace {
40
41// We don't let the screen brightness go lower than this when it's being
42// adjusted via the slider.  Otherwise, if the user doesn't know about the
43// brightness keys, they may turn the backlight off and not know how to turn it
44// back on.
45const double kMinBrightnessPercent = 5.0;
46
47}  // namespace
48
49class BrightnessView : public ShellObserver,
50                       public views::View,
51                       public views::SliderListener {
52 public:
53  BrightnessView(bool default_view, double initial_percent);
54  virtual ~BrightnessView();
55
56  bool is_default_view() const {
57    return is_default_view_;
58  }
59
60  // |percent| is in the range [0.0, 100.0].
61  void SetBrightnessPercent(double percent);
62
63  // ShellObserver:
64  virtual void OnMaximizeModeStarted() OVERRIDE;
65  virtual void OnMaximizeModeEnded() OVERRIDE;
66
67 private:
68  // views::View:
69  virtual void OnBoundsChanged(const gfx::Rect& old_bounds) OVERRIDE;
70
71  // views:SliderListener:
72  virtual void SliderValueChanged(views::Slider* sender,
73                                  float value,
74                                  float old_value,
75                                  views::SliderChangeReason reason) OVERRIDE;
76
77  // views:SliderListener:
78  virtual void SliderDragStarted(views::Slider* slider) OVERRIDE;
79  virtual void SliderDragEnded(views::Slider* slider) OVERRIDE;
80
81  views::Slider* slider_;
82
83  // Is |slider_| currently being dragged?
84  bool dragging_;
85
86  // True if this view is for the default tray view. Used to control hide/show
87  // behaviour of the default view when entering or leaving Maximize Mode.
88  bool is_default_view_;
89
90  // Last brightness level that we observed, in the range [0.0, 100.0].
91  double last_percent_;
92
93  DISALLOW_COPY_AND_ASSIGN(BrightnessView);
94};
95
96BrightnessView::BrightnessView(bool default_view, double initial_percent)
97    : dragging_(false),
98      is_default_view_(default_view),
99      last_percent_(initial_percent) {
100  SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal,
101      kTrayPopupPaddingHorizontal, 0, kTrayPopupPaddingBetweenItems));
102
103  views::ImageView* icon = new FixedSizedImageView(0, kTrayPopupItemHeight);
104  gfx::Image image = ui::ResourceBundle::GetSharedInstance().GetImageNamed(
105      IDR_AURA_UBER_TRAY_BRIGHTNESS);
106  icon->SetImage(image.ToImageSkia());
107  AddChildView(icon);
108
109  slider_ = new views::Slider(this, views::Slider::HORIZONTAL);
110  slider_->set_focus_border_color(kFocusBorderColor);
111  slider_->SetValue(static_cast<float>(initial_percent / 100.0));
112  slider_->SetAccessibleName(
113      ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
114          IDS_ASH_STATUS_TRAY_BRIGHTNESS));
115  AddChildView(slider_);
116
117  if (is_default_view_) {
118    Shell::GetInstance()->AddShellObserver(this);
119    SetVisible(Shell::GetInstance()->maximize_mode_controller()->
120               IsMaximizeModeWindowManagerEnabled());
121  }
122}
123
124BrightnessView::~BrightnessView() {
125  if (is_default_view_)
126    Shell::GetInstance()->RemoveShellObserver(this);
127}
128
129void BrightnessView::SetBrightnessPercent(double percent) {
130  last_percent_ = percent;
131  if (!dragging_)
132    slider_->SetValue(static_cast<float>(percent / 100.0));
133}
134
135void BrightnessView::OnMaximizeModeStarted() {
136  SetVisible(true);
137}
138
139void BrightnessView::OnMaximizeModeEnded() {
140  SetVisible(false);
141}
142
143void BrightnessView::OnBoundsChanged(const gfx::Rect& old_bounds) {
144  int w = width() - slider_->x();
145  slider_->SetSize(gfx::Size(w, slider_->height()));
146}
147
148void BrightnessView::SliderValueChanged(views::Slider* sender,
149                                float value,
150                                float old_value,
151                                views::SliderChangeReason reason) {
152  DCHECK_EQ(sender, slider_);
153  if (reason != views::VALUE_CHANGED_BY_USER)
154    return;
155  AcceleratorController* ac = Shell::GetInstance()->accelerator_controller();
156  if (ac->brightness_control_delegate()) {
157    double percent = std::max(value * 100.0, kMinBrightnessPercent);
158    ac->brightness_control_delegate()->SetBrightnessPercent(percent, true);
159  }
160}
161
162void BrightnessView::SliderDragStarted(views::Slider* slider) {
163  DCHECK_EQ(slider, slider_);
164  dragging_ = true;
165}
166
167void BrightnessView::SliderDragEnded(views::Slider* slider) {
168  DCHECK_EQ(slider, slider_);
169  dragging_ = false;
170  slider_->SetValue(static_cast<float>(last_percent_ / 100.0));
171}
172
173}  // namespace tray
174
175TrayBrightness::TrayBrightness(SystemTray* system_tray)
176    : SystemTrayItem(system_tray),
177      brightness_view_(NULL),
178      current_percent_(100.0),
179      got_current_percent_(false),
180      weak_ptr_factory_(this) {
181  // Post a task to get the initial brightness; the BrightnessControlDelegate
182  // isn't created yet.
183  base::MessageLoopForUI::current()->PostTask(
184      FROM_HERE,
185      base::Bind(&TrayBrightness::GetInitialBrightness,
186                 weak_ptr_factory_.GetWeakPtr()));
187  chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->
188      AddObserver(this);
189}
190
191TrayBrightness::~TrayBrightness() {
192  chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->
193      RemoveObserver(this);
194}
195
196void TrayBrightness::GetInitialBrightness() {
197  BrightnessControlDelegate* delegate =
198      Shell::GetInstance()->accelerator_controller()->
199      brightness_control_delegate();
200  // Worrisome, but happens in unit tests, so don't log anything.
201  if (!delegate)
202    return;
203  delegate->GetBrightnessPercent(
204      base::Bind(&TrayBrightness::HandleInitialBrightness,
205                 weak_ptr_factory_.GetWeakPtr()));
206}
207
208void TrayBrightness::HandleInitialBrightness(double percent) {
209  if (!got_current_percent_)
210    HandleBrightnessChanged(percent, false);
211}
212
213views::View* TrayBrightness::CreateTrayView(user::LoginStatus status) {
214  return NULL;
215}
216
217views::View* TrayBrightness::CreateDefaultView(user::LoginStatus status) {
218  CHECK(brightness_view_ == NULL);
219  brightness_view_ = new tray::BrightnessView(true, current_percent_);
220  return brightness_view_;
221}
222
223views::View* TrayBrightness::CreateDetailedView(user::LoginStatus status) {
224  CHECK(brightness_view_ == NULL);
225  Shell::GetInstance()->metrics()->RecordUserMetricsAction(
226      ash::UMA_STATUS_AREA_DETAILED_BRIGHTNESS_VIEW);
227  brightness_view_ = new tray::BrightnessView(false, current_percent_);
228  return brightness_view_;
229}
230
231void TrayBrightness::DestroyTrayView() {
232}
233
234void TrayBrightness::DestroyDefaultView() {
235  if (brightness_view_ && brightness_view_->is_default_view())
236    brightness_view_ = NULL;
237}
238
239void TrayBrightness::DestroyDetailedView() {
240  if (brightness_view_ && !brightness_view_->is_default_view())
241    brightness_view_ = NULL;
242}
243
244void TrayBrightness::UpdateAfterLoginStatusChange(user::LoginStatus status) {
245}
246
247bool TrayBrightness::ShouldHideArrow() const {
248  return true;
249}
250
251bool TrayBrightness::ShouldShowShelf() const {
252  return false;
253}
254
255void TrayBrightness::BrightnessChanged(int level, bool user_initiated) {
256  Shell::GetInstance()->metrics()->RecordUserMetricsAction(
257      ash::UMA_STATUS_AREA_BRIGHTNESS_CHANGED);
258  double percent = static_cast<double>(level);
259  HandleBrightnessChanged(percent, user_initiated);
260}
261
262void TrayBrightness::HandleBrightnessChanged(double percent,
263                                             bool user_initiated) {
264  current_percent_ = percent;
265  got_current_percent_ = true;
266
267  if (brightness_view_)
268    brightness_view_->SetBrightnessPercent(percent);
269
270  if (!user_initiated)
271    return;
272
273  // Never show the bubble on systems that lack internal displays: if an
274  // external display's brightness is changed, it may already display the new
275  // level via an on-screen display.
276  if (!Shell::GetInstance()->display_manager()->HasInternalDisplay())
277    return;
278
279  if (brightness_view_)
280    SetDetailedViewCloseDelay(kTrayPopupAutoCloseDelayInSeconds);
281  else
282    PopupDetailedView(kTrayPopupAutoCloseDelayInSeconds, false);
283}
284
285}  // namespace ash
286