Home | History | Annotate | Download | only in session_length_limit
      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/tray/system_tray.h"
     12 #include "ash/system/tray/system_tray_delegate.h"
     13 #include "ash/system/tray/system_tray_notifier.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/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/gfx/font.h"
     27 #include "ui/gfx/text_constants.h"
     28 #include "ui/views/border.h"
     29 #include "ui/views/controls/label.h"
     30 #include "ui/views/layout/box_layout.h"
     31 #include "ui/views/layout/grid_layout.h"
     32 #include "ui/views/view.h"
     33 
     34 namespace ash {
     35 namespace internal {
     36 
     37 namespace {
     38 
     39 // If the remaining session time falls below this threshold, the user should be
     40 // informed that the session is about to expire.
     41 const int kExpiringSoonThresholdInSeconds = 5 * 60;  // 5 minutes.
     42 
     43 // Color in which the remaining session time is normally shown.
     44 const SkColor kRemainingTimeColor = SK_ColorWHITE;
     45 // Color in which the remaining session time is shown when it is expiring soon.
     46 const SkColor kRemainingTimeExpiringSoonColor = SK_ColorRED;
     47 
     48 views::Label* CreateAndSetupLabel() {
     49   views::Label* label = new views::Label;
     50   label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
     51   SetupLabelForTray(label);
     52   gfx::Font font = label->font();
     53   label->SetFont(font.DeriveFont(0, font.GetStyle() & ~gfx::Font::BOLD));
     54   return label;
     55 }
     56 
     57 base::string16 IntToTwoDigitString(int value) {
     58   DCHECK_GE(value, 0);
     59   DCHECK_LE(value, 99);
     60   if (value < 10)
     61     return ASCIIToUTF16("0") + base::IntToString16(value);
     62   return base::IntToString16(value);
     63 }
     64 
     65 base::string16 FormatRemainingSessionTimeNotification(
     66     const base::TimeDelta& remaining_session_time) {
     67   return l10n_util::GetStringFUTF16(
     68       IDS_ASH_STATUS_TRAY_REMAINING_SESSION_TIME_NOTIFICATION,
     69       Shell::GetInstance()->system_tray_delegate()->
     70           FormatTimeDuration(remaining_session_time));
     71 }
     72 
     73 }  // namespace
     74 
     75 namespace tray {
     76 
     77 class RemainingSessionTimeNotificationView : public TrayNotificationView {
     78  public:
     79   explicit RemainingSessionTimeNotificationView(TraySessionLengthLimit* owner);
     80   virtual ~RemainingSessionTimeNotificationView();
     81 
     82   // TrayNotificationView:
     83   virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE;
     84 
     85   void Update();
     86 
     87  private:
     88   views::Label* label_;
     89 
     90   DISALLOW_COPY_AND_ASSIGN(RemainingSessionTimeNotificationView);
     91 };
     92 
     93 class RemainingSessionTimeTrayView : public views::View {
     94  public:
     95   RemainingSessionTimeTrayView(const TraySessionLengthLimit* owner,
     96                                ShelfAlignment shelf_alignment);
     97   virtual ~RemainingSessionTimeTrayView();
     98 
     99   void UpdateClockLayout(ShelfAlignment shelf_alignment);
    100   void Update();
    101 
    102  private:
    103   void SetBorder(ShelfAlignment shelf_alignment);
    104 
    105   const TraySessionLengthLimit* owner_;
    106 
    107   views::Label* horizontal_layout_label_;
    108   views::Label* vertical_layout_label_hours_left_;
    109   views::Label* vertical_layout_label_hours_right_;
    110   views::Label* vertical_layout_label_minutes_left_;
    111   views::Label* vertical_layout_label_minutes_right_;
    112   views::Label* vertical_layout_label_seconds_left_;
    113   views::Label* vertical_layout_label_seconds_right_;
    114 
    115   DISALLOW_COPY_AND_ASSIGN(RemainingSessionTimeTrayView);
    116 };
    117 
    118 RemainingSessionTimeNotificationView::RemainingSessionTimeNotificationView(
    119     TraySessionLengthLimit* owner)
    120     : TrayNotificationView(owner,
    121                            IDR_AURA_UBER_TRAY_SESSION_LENGTH_LIMIT_TIMER) {
    122   label_ = new views::Label;
    123   label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    124   label_->SetMultiLine(true);
    125   Update();
    126   InitView(label_);
    127 }
    128 
    129 RemainingSessionTimeNotificationView::~RemainingSessionTimeNotificationView() {
    130 }
    131 
    132 void RemainingSessionTimeNotificationView::ChildPreferredSizeChanged(
    133     views::View* child) {
    134   PreferredSizeChanged();
    135 }
    136 
    137 void RemainingSessionTimeNotificationView::Update() {
    138   label_->SetText(FormatRemainingSessionTimeNotification(
    139       reinterpret_cast<TraySessionLengthLimit*>(owner())->
    140           GetRemainingSessionTime()));
    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   SetBorder(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, kTrayLabelItemVerticalPaddingVeriticalAlignment);
    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, kTrayLabelItemVerticalPaddingVeriticalAlignment);
    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::SetBorder(ShelfAlignment shelf_alignment) {
    269   if (shelf_alignment == SHELF_ALIGNMENT_BOTTOM ||
    270       shelf_alignment == SHELF_ALIGNMENT_TOP) {
    271     set_border(views::Border::CreateEmptyBorder(
    272         0, kTrayLabelItemHorizontalPaddingBottomAlignment,
    273         0, kTrayLabelItemHorizontalPaddingBottomAlignment));
    274   } else {
    275     set_border(NULL);
    276   }
    277 }
    278 
    279 }  // namespace tray
    280 
    281 TraySessionLengthLimit::TraySessionLengthLimit(SystemTray* system_tray)
    282     : SystemTrayItem(system_tray),
    283       tray_view_(NULL),
    284       notification_view_(NULL),
    285       limit_state_(LIMIT_NONE) {
    286   Shell::GetInstance()->system_tray_notifier()->
    287       AddSessionLengthLimitObserver(this);
    288   Update();
    289 }
    290 
    291 TraySessionLengthLimit::~TraySessionLengthLimit() {
    292   Shell::GetInstance()->system_tray_notifier()->
    293       RemoveSessionLengthLimitObserver(this);
    294 }
    295 
    296 views::View* TraySessionLengthLimit::CreateTrayView(user::LoginStatus status) {
    297   CHECK(tray_view_ == NULL);
    298   tray_view_ = new tray::RemainingSessionTimeTrayView(
    299       this, system_tray()->shelf_alignment());
    300   return tray_view_;
    301 }
    302 
    303 views::View* TraySessionLengthLimit::CreateNotificationView(
    304     user::LoginStatus status) {
    305   CHECK(notification_view_ == NULL);
    306   notification_view_ = new tray::RemainingSessionTimeNotificationView(this);
    307   return notification_view_;
    308 }
    309 
    310 void TraySessionLengthLimit::DestroyTrayView() {
    311   tray_view_ = NULL;
    312 }
    313 
    314 void TraySessionLengthLimit::DestroyNotificationView() {
    315   notification_view_ = NULL;
    316 }
    317 
    318 void TraySessionLengthLimit::UpdateAfterShelfAlignmentChange(
    319     ShelfAlignment alignment) {
    320   if (tray_view_)
    321     tray_view_->UpdateClockLayout(alignment);
    322 }
    323 
    324 void TraySessionLengthLimit::OnSessionStartTimeChanged() {
    325   Update();
    326 }
    327 
    328 void TraySessionLengthLimit::OnSessionLengthLimitChanged() {
    329   Update();
    330 }
    331 
    332 TraySessionLengthLimit::LimitState
    333     TraySessionLengthLimit::GetLimitState() const {
    334   return limit_state_;
    335 }
    336 
    337 base::TimeDelta TraySessionLengthLimit::GetRemainingSessionTime() const {
    338   return remaining_session_time_;
    339 }
    340 
    341 void TraySessionLengthLimit::ShowAndSpeakNotification() {
    342   ShowNotificationView();
    343   Shell::GetInstance()->system_tray_delegate()->MaybeSpeak(UTF16ToUTF8(
    344       FormatRemainingSessionTimeNotification(remaining_session_time_)));
    345 }
    346 
    347 void TraySessionLengthLimit::Update() {
    348   SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
    349   const LimitState previous_limit_state = limit_state_;
    350   if (!delegate->GetSessionStartTime(&session_start_time_) ||
    351       !delegate->GetSessionLengthLimit(&limit_)) {
    352     remaining_session_time_ = base::TimeDelta();
    353     limit_state_ = LIMIT_NONE;
    354     timer_.reset();
    355   } else {
    356     remaining_session_time_ = std::max(
    357         limit_ - (base::TimeTicks::Now() - session_start_time_),
    358         base::TimeDelta());
    359     limit_state_ = remaining_session_time_.InSeconds() <=
    360         kExpiringSoonThresholdInSeconds ? LIMIT_EXPIRING_SOON : LIMIT_SET;
    361     if (!timer_)
    362       timer_.reset(new base::RepeatingTimer<TraySessionLengthLimit>);
    363     if (!timer_->IsRunning()) {
    364       // Start a timer that will update the remaining session time every second.
    365       timer_->Start(FROM_HERE,
    366                     base::TimeDelta::FromSeconds(1),
    367                     this,
    368                     &TraySessionLengthLimit::Update);
    369     }
    370   }
    371 
    372   if (notification_view_)
    373     notification_view_->Update();
    374 
    375   switch (limit_state_) {
    376     case LIMIT_NONE:
    377       HideNotificationView();
    378       break;
    379     case LIMIT_SET:
    380       if (previous_limit_state == LIMIT_NONE)
    381         ShowAndSpeakNotification();
    382       break;
    383     case LIMIT_EXPIRING_SOON:
    384       if (previous_limit_state == LIMIT_NONE ||
    385           previous_limit_state == LIMIT_SET) {
    386         ShowAndSpeakNotification();
    387       }
    388       break;
    389   }
    390 
    391   // Update the tray view last so that it can check whether the notification
    392   // view is currently visible or not.
    393   if (tray_view_)
    394     tray_view_->Update();
    395 }
    396 
    397 }  // namespace internal
    398 }  // namespace ash
    399