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