Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / ash / system / chromeos / session / tray_session_length_limit.cc
1 // Copyright 2014 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/session/tray_session_length_limit.h"
6
7 #include <algorithm>
8
9 #include "ash/shelf/shelf_types.h"
10 #include "ash/shell.h"
11 #include "ash/system/system_notifier.h"
12 #include "ash/system/tray/system_tray.h"
13 #include "ash/system/tray/system_tray_delegate.h"
14 #include "ash/system/tray/system_tray_notifier.h"
15 #include "ash/system/tray/tray_constants.h"
16 #include "ash/system/tray/tray_utils.h"
17 #include "base/location.h"
18 #include "base/logging.h"
19 #include "base/strings/string16.h"
20 #include "base/strings/string_number_conversions.h"
21 #include "base/strings/utf_string_conversions.h"
22 #include "grit/ash_resources.h"
23 #include "grit/ash_strings.h"
24 #include "third_party/skia/include/core/SkColor.h"
25 #include "ui/base/l10n/l10n_util.h"
26 #include "ui/base/l10n/time_format.h"
27 #include "ui/base/resource/resource_bundle.h"
28 #include "ui/gfx/font_list.h"
29 #include "ui/message_center/message_center.h"
30 #include "ui/message_center/notification.h"
31 #include "ui/views/border.h"
32 #include "ui/views/controls/label.h"
33 #include "ui/views/layout/box_layout.h"
34 #include "ui/views/layout/grid_layout.h"
35 #include "ui/views/view.h"
36
37 using message_center::Notification;
38
39 namespace ash {
40 namespace {
41
42 // If the remaining session time falls below this threshold, the user should be
43 // informed that the session is about to expire.
44 const int kExpiringSoonThresholdInSeconds = 5 * 60;  // 5 minutes.
45
46 // Color in which the remaining session time is normally shown.
47 const SkColor kRemainingTimeColor = SK_ColorWHITE;
48 // Color in which the remaining session time is shown when it is expiring soon.
49 const SkColor kRemainingTimeExpiringSoonColor = SK_ColorRED;
50
51 views::Label* CreateAndSetupLabel() {
52   views::Label* label = new views::Label;
53   label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
54   SetupLabelForTray(label);
55   label->SetFontList(label->font_list().DeriveWithStyle(
56       label->font_list().GetFontStyle() & ~gfx::Font::BOLD));
57   return label;
58 }
59
60 base::string16 IntToTwoDigitString(int value) {
61   DCHECK_GE(value, 0);
62   DCHECK_LE(value, 99);
63   if (value < 10)
64     return base::ASCIIToUTF16("0") + base::IntToString16(value);
65   return base::IntToString16(value);
66 }
67
68 base::string16 FormatRemainingSessionTimeNotification(
69     const base::TimeDelta& remaining_session_time) {
70   return l10n_util::GetStringFUTF16(
71       IDS_ASH_STATUS_TRAY_REMAINING_SESSION_TIME_NOTIFICATION,
72       ui::TimeFormat::Detailed(ui::TimeFormat::FORMAT_DURATION,
73                                ui::TimeFormat::LENGTH_LONG,
74                                10,
75                                remaining_session_time));
76 }
77
78 // Creates, or updates the notification for session length timeout with
79 // |remaining_time|.  |state_changed| is true when its internal state has been
80 // changed from another.
81 void CreateOrUpdateNotification(const std::string& notification_id,
82                                 const base::TimeDelta& remaining_time,
83                                 bool state_changed) {
84   message_center::MessageCenter* message_center =
85       message_center::MessageCenter::Get();
86
87   // Do not create a new notification if no state has changed. It may happen
88   // when the notification is already closed by the user, see crbug.com/285941.
89   if (!state_changed && !message_center->HasNotification(notification_id))
90     return;
91
92   ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
93   message_center::RichNotificationData data;
94   // Makes the spoken feedback only when the state has been changed.
95   data.should_make_spoken_feedback_for_popup_updates = state_changed;
96   scoped_ptr<Notification> notification(new Notification(
97       message_center::NOTIFICATION_TYPE_SIMPLE,
98       notification_id,
99       FormatRemainingSessionTimeNotification(remaining_time),
100       base::string16() /* message */,
101       bundle.GetImageNamed(IDR_AURA_UBER_TRAY_SESSION_LENGTH_LIMIT_TIMER),
102       base::string16() /* display_source */,
103       message_center::NotifierId(
104           message_center::NotifierId::SYSTEM_COMPONENT,
105           system_notifier::kNotifierSessionLengthTimeout),
106       data,
107       NULL /* delegate */));
108   notification->SetSystemPriority();
109   message_center::MessageCenter::Get()->AddNotification(notification.Pass());
110 }
111
112 }  // namespace
113
114 namespace tray {
115
116 class RemainingSessionTimeTrayView : public views::View {
117  public:
118   RemainingSessionTimeTrayView(const TraySessionLengthLimit* owner,
119                                ShelfAlignment shelf_alignment);
120   virtual ~RemainingSessionTimeTrayView();
121
122   void UpdateClockLayout(ShelfAlignment shelf_alignment);
123   void Update();
124
125  private:
126   void SetBorderFromAlignment(ShelfAlignment shelf_alignment);
127
128   const TraySessionLengthLimit* owner_;
129
130   views::Label* horizontal_layout_label_;
131   views::Label* vertical_layout_label_hours_left_;
132   views::Label* vertical_layout_label_hours_right_;
133   views::Label* vertical_layout_label_minutes_left_;
134   views::Label* vertical_layout_label_minutes_right_;
135   views::Label* vertical_layout_label_seconds_left_;
136   views::Label* vertical_layout_label_seconds_right_;
137
138   DISALLOW_COPY_AND_ASSIGN(RemainingSessionTimeTrayView);
139 };
140
141 RemainingSessionTimeTrayView::RemainingSessionTimeTrayView(
142     const TraySessionLengthLimit* owner,
143     ShelfAlignment shelf_alignment)
144     : owner_(owner),
145       horizontal_layout_label_(NULL),
146       vertical_layout_label_hours_left_(NULL),
147       vertical_layout_label_hours_right_(NULL),
148       vertical_layout_label_minutes_left_(NULL),
149       vertical_layout_label_minutes_right_(NULL),
150       vertical_layout_label_seconds_left_(NULL),
151       vertical_layout_label_seconds_right_(NULL) {
152   UpdateClockLayout(shelf_alignment);
153 }
154
155 RemainingSessionTimeTrayView::~RemainingSessionTimeTrayView() {
156 }
157
158 void RemainingSessionTimeTrayView::UpdateClockLayout(
159     ShelfAlignment shelf_alignment) {
160   SetBorderFromAlignment(shelf_alignment);
161   const bool horizontal_layout = (shelf_alignment == SHELF_ALIGNMENT_BOTTOM ||
162       shelf_alignment == SHELF_ALIGNMENT_TOP);
163   if (horizontal_layout && !horizontal_layout_label_) {
164     // Remove labels used for vertical layout.
165     RemoveAllChildViews(true);
166     vertical_layout_label_hours_left_ = NULL;
167     vertical_layout_label_hours_right_ = NULL;
168     vertical_layout_label_minutes_left_ = NULL;
169     vertical_layout_label_minutes_right_ = NULL;
170     vertical_layout_label_seconds_left_ = NULL;
171     vertical_layout_label_seconds_right_ = NULL;
172
173     // Create label used for horizontal layout.
174     horizontal_layout_label_ = CreateAndSetupLabel();
175
176     // Construct layout.
177     SetLayoutManager(
178         new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
179     AddChildView(horizontal_layout_label_);
180
181   } else if (!horizontal_layout && horizontal_layout_label_) {
182     // Remove label used for horizontal layout.
183     RemoveAllChildViews(true);
184     horizontal_layout_label_ = NULL;
185
186     // Create labels used for vertical layout.
187     vertical_layout_label_hours_left_ = CreateAndSetupLabel();
188     vertical_layout_label_hours_right_ = CreateAndSetupLabel();
189     vertical_layout_label_minutes_left_ = CreateAndSetupLabel();
190     vertical_layout_label_minutes_right_ = CreateAndSetupLabel();
191     vertical_layout_label_seconds_left_ = CreateAndSetupLabel();
192     vertical_layout_label_seconds_right_ = CreateAndSetupLabel();
193
194     // Construct layout.
195     views::GridLayout* layout = new views::GridLayout(this);
196     SetLayoutManager(layout);
197     views::ColumnSet* columns = layout->AddColumnSet(0);
198     columns->AddPaddingColumn(0, 6);
199     columns->AddColumn(views::GridLayout::CENTER, views::GridLayout::CENTER,
200                        0, views::GridLayout::USE_PREF, 0, 0);
201     columns->AddColumn(views::GridLayout::CENTER, views::GridLayout::CENTER,
202                        0, views::GridLayout::USE_PREF, 0, 0);
203     layout->AddPaddingRow(0, kTrayLabelItemVerticalPaddingVerticalAlignment);
204     layout->StartRow(0, 0);
205     layout->AddView(vertical_layout_label_hours_left_);
206     layout->AddView(vertical_layout_label_hours_right_);
207     layout->StartRow(0, 0);
208     layout->AddView(vertical_layout_label_minutes_left_);
209     layout->AddView(vertical_layout_label_minutes_right_);
210     layout->StartRow(0, 0);
211     layout->AddView(vertical_layout_label_seconds_left_);
212     layout->AddView(vertical_layout_label_seconds_right_);
213     layout->AddPaddingRow(0, kTrayLabelItemVerticalPaddingVerticalAlignment);
214   }
215   Update();
216 }
217
218 void RemainingSessionTimeTrayView::Update() {
219   const TraySessionLengthLimit::LimitState limit_state =
220       owner_->GetLimitState();
221
222   if (limit_state == TraySessionLengthLimit::LIMIT_NONE) {
223     SetVisible(false);
224     return;
225   }
226
227   int seconds = owner_->GetRemainingSessionTime().InSeconds();
228   // If the remaining session time is 100 hours or more, show 99:59:59 instead.
229   seconds = std::min(seconds, 100 * 60 * 60 - 1);  // 100 hours - 1 second.
230   int minutes = seconds / 60;
231   seconds %= 60;
232   const int hours = minutes / 60;
233   minutes %= 60;
234
235   const base::string16 hours_str = IntToTwoDigitString(hours);
236   const base::string16 minutes_str = IntToTwoDigitString(minutes);
237   const base::string16 seconds_str = IntToTwoDigitString(seconds);
238   const SkColor color =
239       limit_state == TraySessionLengthLimit::LIMIT_EXPIRING_SOON ?
240           kRemainingTimeExpiringSoonColor : kRemainingTimeColor;
241
242   if (horizontal_layout_label_) {
243     horizontal_layout_label_->SetText(l10n_util::GetStringFUTF16(
244         IDS_ASH_STATUS_TRAY_REMAINING_SESSION_TIME,
245         hours_str, minutes_str, seconds_str));
246     horizontal_layout_label_->SetEnabledColor(color);
247   } else if (vertical_layout_label_hours_left_) {
248     vertical_layout_label_hours_left_->SetText(hours_str.substr(0, 1));
249     vertical_layout_label_hours_right_->SetText(hours_str.substr(1, 1));
250     vertical_layout_label_minutes_left_->SetText(minutes_str.substr(0, 1));
251     vertical_layout_label_minutes_right_->SetText(minutes_str.substr(1, 1));
252     vertical_layout_label_seconds_left_->SetText(seconds_str.substr(0, 1));
253     vertical_layout_label_seconds_right_->SetText(seconds_str.substr(1, 1));
254     vertical_layout_label_hours_left_->SetEnabledColor(color);
255     vertical_layout_label_hours_right_->SetEnabledColor(color);
256     vertical_layout_label_minutes_left_->SetEnabledColor(color);
257     vertical_layout_label_minutes_right_->SetEnabledColor(color);
258     vertical_layout_label_seconds_left_->SetEnabledColor(color);
259     vertical_layout_label_seconds_right_->SetEnabledColor(color);
260   }
261
262   Layout();
263   SetVisible(true);
264 }
265
266 void RemainingSessionTimeTrayView::SetBorderFromAlignment(
267     ShelfAlignment shelf_alignment) {
268   if (shelf_alignment == SHELF_ALIGNMENT_BOTTOM ||
269       shelf_alignment == SHELF_ALIGNMENT_TOP) {
270     SetBorder(views::Border::CreateEmptyBorder(
271         0,
272         kTrayLabelItemHorizontalPaddingBottomAlignment,
273         0,
274         kTrayLabelItemHorizontalPaddingBottomAlignment));
275   } else {
276     SetBorder(views::Border::NullBorder());
277   }
278 }
279
280 }  // namespace tray
281
282 // static
283 const char TraySessionLengthLimit::kNotificationId[] =
284     "chrome://session/timeout";
285
286 TraySessionLengthLimit::TraySessionLengthLimit(SystemTray* system_tray)
287     : SystemTrayItem(system_tray),
288       tray_view_(NULL),
289       limit_state_(LIMIT_NONE) {
290   Shell::GetInstance()->system_tray_notifier()->
291       AddSessionLengthLimitObserver(this);
292   Update();
293 }
294
295 TraySessionLengthLimit::~TraySessionLengthLimit() {
296   Shell::GetInstance()->system_tray_notifier()->
297       RemoveSessionLengthLimitObserver(this);
298 }
299
300 views::View* TraySessionLengthLimit::CreateTrayView(user::LoginStatus status) {
301   CHECK(tray_view_ == NULL);
302   tray_view_ = new tray::RemainingSessionTimeTrayView(
303       this, system_tray()->shelf_alignment());
304   return tray_view_;
305 }
306
307 void TraySessionLengthLimit::DestroyTrayView() {
308   tray_view_ = NULL;
309 }
310
311 void TraySessionLengthLimit::UpdateAfterShelfAlignmentChange(
312     ShelfAlignment alignment) {
313   if (tray_view_)
314     tray_view_->UpdateClockLayout(alignment);
315 }
316
317 void TraySessionLengthLimit::OnSessionStartTimeChanged() {
318   Update();
319 }
320
321 void TraySessionLengthLimit::OnSessionLengthLimitChanged() {
322   Update();
323 }
324
325 TraySessionLengthLimit::LimitState
326     TraySessionLengthLimit::GetLimitState() const {
327   return limit_state_;
328 }
329
330 base::TimeDelta TraySessionLengthLimit::GetRemainingSessionTime() const {
331   return remaining_session_time_;
332 }
333
334 void TraySessionLengthLimit::Update() {
335   SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
336   const LimitState previous_limit_state = limit_state_;
337   if (!delegate->GetSessionStartTime(&session_start_time_) ||
338       !delegate->GetSessionLengthLimit(&limit_)) {
339     remaining_session_time_ = base::TimeDelta();
340     limit_state_ = LIMIT_NONE;
341     timer_.reset();
342   } else {
343     remaining_session_time_ = std::max(
344         limit_ - (base::TimeTicks::Now() - session_start_time_),
345         base::TimeDelta());
346     limit_state_ = remaining_session_time_.InSeconds() <=
347         kExpiringSoonThresholdInSeconds ? LIMIT_EXPIRING_SOON : LIMIT_SET;
348     if (!timer_)
349       timer_.reset(new base::RepeatingTimer<TraySessionLengthLimit>);
350     if (!timer_->IsRunning()) {
351       // Start a timer that will update the remaining session time every second.
352       timer_->Start(FROM_HERE,
353                     base::TimeDelta::FromSeconds(1),
354                     this,
355                     &TraySessionLengthLimit::Update);
356     }
357   }
358
359   switch (limit_state_) {
360     case LIMIT_NONE:
361       message_center::MessageCenter::Get()->RemoveNotification(
362           kNotificationId, false /* by_user */);
363       break;
364     case LIMIT_SET:
365       CreateOrUpdateNotification(
366           kNotificationId,
367           remaining_session_time_,
368           previous_limit_state == LIMIT_NONE);
369       break;
370     case LIMIT_EXPIRING_SOON:
371       CreateOrUpdateNotification(
372           kNotificationId,
373           remaining_session_time_,
374           previous_limit_state == LIMIT_NONE ||
375           previous_limit_state == LIMIT_SET);
376       break;
377   }
378
379   // Update the tray view last so that it can check whether the notification
380   // view is currently visible or not.
381   if (tray_view_)
382     tray_view_->Update();
383 }
384
385 bool TraySessionLengthLimit::IsTrayViewVisibleForTest() {
386   return tray_view_ && tray_view_->visible();
387 }
388
389 }  // namespace ash