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/shell.h" 10 #include "ash/system/chromeos/label_tray_view.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 "base/logging.h" 16 #include "base/strings/utf_string_conversions.h" 17 #include "grit/ash_resources.h" 18 #include "grit/ash_strings.h" 19 #include "ui/base/l10n/l10n_util.h" 20 #include "ui/base/l10n/time_format.h" 21 #include "ui/base/resource/resource_bundle.h" 22 #include "ui/message_center/message_center.h" 23 #include "ui/message_center/notification.h" 24 #include "ui/views/view.h" 25 26 namespace ash { 27 namespace { 28 29 // If the remaining session time falls below this threshold, the user should be 30 // informed that the session is about to expire. 31 const int kExpiringSoonThresholdInMinutes = 5; 32 33 // Use 500ms interval for updates to notification and tray bubble to reduce the 34 // likelihood of a user-visible skip in high load situations (as might happen 35 // with 1000ms). 36 const int kTimerIntervalInMilliseconds = 500; 37 38 } // namespace 39 40 // static 41 const char TraySessionLengthLimit::kNotificationId[] = 42 "chrome://session/timeout"; 43 44 TraySessionLengthLimit::TraySessionLengthLimit(SystemTray* system_tray) 45 : SystemTrayItem(system_tray), 46 limit_state_(LIMIT_NONE), 47 last_limit_state_(LIMIT_NONE), 48 tray_bubble_view_(NULL) { 49 Shell::GetInstance()->system_tray_notifier()-> 50 AddSessionLengthLimitObserver(this); 51 Update(); 52 } 53 54 TraySessionLengthLimit::~TraySessionLengthLimit() { 55 Shell::GetInstance()->system_tray_notifier()-> 56 RemoveSessionLengthLimitObserver(this); 57 } 58 59 // Add view to tray bubble. 60 views::View* TraySessionLengthLimit::CreateDefaultView( 61 user::LoginStatus status) { 62 CHECK(!tray_bubble_view_); 63 UpdateState(); 64 if (limit_state_ == LIMIT_NONE) 65 return NULL; 66 tray_bubble_view_ = new LabelTrayView( 67 NULL /* click_listener */, 68 IDR_AURA_UBER_TRAY_BUBBLE_SESSION_LENGTH_LIMIT); 69 tray_bubble_view_->SetMessage(ComposeTrayBubbleMessage()); 70 return tray_bubble_view_; 71 } 72 73 // View has been removed from tray bubble. 74 void TraySessionLengthLimit::DestroyDefaultView() { 75 tray_bubble_view_ = NULL; 76 } 77 78 void TraySessionLengthLimit::OnSessionStartTimeChanged() { 79 Update(); 80 } 81 82 void TraySessionLengthLimit::OnSessionLengthLimitChanged() { 83 Update(); 84 } 85 86 void TraySessionLengthLimit::Update() { 87 UpdateState(); 88 UpdateNotification(); 89 UpdateTrayBubbleView(); 90 } 91 92 void TraySessionLengthLimit::UpdateState() { 93 SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate(); 94 if (delegate->GetSessionStartTime(&session_start_time_) && 95 delegate->GetSessionLengthLimit(&time_limit_)) { 96 const base::TimeDelta expiring_soon_threshold( 97 base::TimeDelta::FromMinutes(kExpiringSoonThresholdInMinutes)); 98 remaining_session_time_ = std::max( 99 time_limit_ - (base::TimeTicks::Now() - session_start_time_), 100 base::TimeDelta()); 101 limit_state_ = remaining_session_time_ <= expiring_soon_threshold ? 102 LIMIT_EXPIRING_SOON : LIMIT_SET; 103 if (!timer_) 104 timer_.reset(new base::RepeatingTimer<TraySessionLengthLimit>); 105 if (!timer_->IsRunning()) { 106 timer_->Start(FROM_HERE, 107 base::TimeDelta::FromMilliseconds( 108 kTimerIntervalInMilliseconds), 109 this, 110 &TraySessionLengthLimit::Update); 111 } 112 } else { 113 remaining_session_time_ = base::TimeDelta(); 114 limit_state_ = LIMIT_NONE; 115 timer_.reset(); 116 } 117 } 118 119 void TraySessionLengthLimit::UpdateNotification() { 120 message_center::MessageCenter* message_center = 121 message_center::MessageCenter::Get(); 122 123 // If state hasn't changed and the notification has already been acknowledged, 124 // we won't re-create it. 125 if (limit_state_ == last_limit_state_ && 126 !message_center->FindVisibleNotificationById(kNotificationId)) { 127 return; 128 } 129 130 // After state change, any possibly existing notification is removed to make 131 // sure it is re-shown even if it had been acknowledged by the user before 132 // (and in the rare case of state change towards LIMIT_NONE to make the 133 // notification disappear). 134 if (limit_state_ != last_limit_state_ && 135 message_center->FindVisibleNotificationById(kNotificationId)) { 136 message_center::MessageCenter::Get()->RemoveNotification( 137 kNotificationId, false /* by_user */); 138 } 139 140 // For LIMIT_NONE, there's nothing more to do. 141 if (limit_state_ == LIMIT_NONE) { 142 last_limit_state_ = limit_state_; 143 return; 144 } 145 146 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); 147 message_center::RichNotificationData data; 148 data.should_make_spoken_feedback_for_popup_updates = 149 (limit_state_ != last_limit_state_); 150 scoped_ptr<message_center::Notification> notification( 151 new message_center::Notification( 152 message_center::NOTIFICATION_TYPE_SIMPLE, 153 kNotificationId, 154 base::string16() /* title */, 155 ComposeNotificationMessage() /* message */, 156 bundle.GetImageNamed( 157 IDR_AURA_UBER_TRAY_NOTIFICATION_SESSION_LENGTH_LIMIT), 158 base::string16() /* display_source */, 159 message_center::NotifierId( 160 message_center::NotifierId::SYSTEM_COMPONENT, 161 system_notifier::kNotifierSessionLengthTimeout), 162 data, 163 NULL /* delegate */)); 164 notification->SetSystemPriority(); 165 if (message_center->FindVisibleNotificationById(kNotificationId)) 166 message_center->UpdateNotification(kNotificationId, notification.Pass()); 167 else 168 message_center->AddNotification(notification.Pass()); 169 last_limit_state_ = limit_state_; 170 } 171 172 void TraySessionLengthLimit::UpdateTrayBubbleView() const { 173 if (!tray_bubble_view_) 174 return; 175 if (limit_state_ == LIMIT_NONE) 176 tray_bubble_view_->SetMessage(base::string16()); 177 else 178 tray_bubble_view_->SetMessage(ComposeTrayBubbleMessage()); 179 tray_bubble_view_->Layout(); 180 } 181 182 base::string16 TraySessionLengthLimit::ComposeNotificationMessage() const { 183 return l10n_util::GetStringFUTF16( 184 IDS_ASH_STATUS_TRAY_NOTIFICATION_SESSION_LENGTH_LIMIT, 185 ui::TimeFormat::Detailed(ui::TimeFormat::FORMAT_DURATION, 186 ui::TimeFormat::LENGTH_LONG, 187 10, 188 remaining_session_time_)); 189 } 190 191 base::string16 TraySessionLengthLimit::ComposeTrayBubbleMessage() const { 192 return l10n_util::GetStringFUTF16( 193 IDS_ASH_STATUS_TRAY_BUBBLE_SESSION_LENGTH_LIMIT, 194 ui::TimeFormat::Detailed(ui::TimeFormat::FORMAT_DURATION, 195 ui::TimeFormat::LENGTH_LONG, 196 10, 197 remaining_session_time_)); 198 } 199 200 } // namespace ash 201