Home | History | Annotate | Download | only in views
      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 "chrome/browser/ui/views/critical_notification_bubble_view.h"
      6 
      7 #include "base/prefs/pref_service.h"
      8 #include "base/strings/string_number_conversions.h"
      9 #include "base/strings/utf_string_conversions.h"
     10 #include "chrome/browser/browser_process.h"
     11 #include "chrome/browser/lifetime/application_lifetime.h"
     12 #include "chrome/browser/upgrade_detector.h"
     13 #include "chrome/common/pref_names.h"
     14 #include "content/public/browser/user_metrics.h"
     15 #include "grit/chromium_strings.h"
     16 #include "grit/generated_resources.h"
     17 #include "grit/locale_settings.h"
     18 #include "grit/theme_resources.h"
     19 #include "ui/base/accelerators/accelerator.h"
     20 #include "ui/base/accessibility/accessible_view_state.h"
     21 #include "ui/base/l10n/l10n_util.h"
     22 #include "ui/base/resource/resource_bundle.h"
     23 #include "ui/views/controls/button/label_button.h"
     24 #include "ui/views/controls/image_view.h"
     25 #include "ui/views/controls/label.h"
     26 #include "ui/views/layout/grid_layout.h"
     27 #include "ui/views/layout/layout_constants.h"
     28 #include "ui/views/widget/widget.h"
     29 
     30 using content::UserMetricsAction;
     31 
     32 namespace {
     33 
     34 // Layout constants.
     35 const int kInset = 2;
     36 const int kImageHeadlinePadding = 4;
     37 const int kHeadlineMessagePadding = 4;
     38 const int kMessageBubblePadding = 11;
     39 
     40 // How long to give the user until auto-restart if no action is taken. The code
     41 // assumes this to be less than a minute.
     42 const int kCountdownDuration = 30;  // Seconds.
     43 
     44 // How often to refresh the bubble UI to update the counter. As long as the
     45 // countdown is in seconds, this should be 1000 or lower.
     46 const int kRefreshBubbleEvery = 1000;  // Millisecond.
     47 
     48 }  // namespace
     49 
     50 ////////////////////////////////////////////////////////////////////////////////
     51 // CriticalNotificationBubbleView
     52 
     53 CriticalNotificationBubbleView::CriticalNotificationBubbleView(
     54     views::View* anchor_view)
     55     : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
     56       headline_(NULL),
     57       restart_button_(NULL),
     58       dismiss_button_(NULL) {
     59   set_close_on_deactivate(false);
     60   set_move_with_anchor(true);
     61 }
     62 
     63 CriticalNotificationBubbleView::~CriticalNotificationBubbleView() {
     64 }
     65 
     66 int CriticalNotificationBubbleView::GetRemainingTime() {
     67   base::TimeDelta time_lapsed = base::Time::Now() - bubble_created_;
     68   return kCountdownDuration - time_lapsed.InSeconds();
     69 }
     70 
     71 void CriticalNotificationBubbleView::UpdateBubbleHeadline(int seconds) {
     72   if (seconds > 0) {
     73     headline_->SetText(
     74         l10n_util::GetStringFUTF16(IDS_CRITICAL_NOTIFICATION_HEADLINE,
     75             l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
     76             base::IntToString16(seconds)));
     77   } else {
     78     headline_->SetText(
     79         l10n_util::GetStringFUTF16(IDS_CRITICAL_NOTIFICATION_HEADLINE_ALTERNATE,
     80             l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)));
     81   }
     82 }
     83 
     84 void CriticalNotificationBubbleView::OnCountdown() {
     85   UpgradeDetector* upgrade_detector = UpgradeDetector::GetInstance();
     86   if (upgrade_detector->critical_update_acknowledged()) {
     87     // The user has already interacted with the bubble and chosen a path.
     88     GetWidget()->Close();
     89     return;
     90   }
     91 
     92   int seconds = GetRemainingTime();
     93   if (seconds <= 0) {
     94     // Time's up!
     95     upgrade_detector->acknowledge_critical_update();
     96 
     97     content::RecordAction(
     98         UserMetricsAction("CriticalNotification_AutoRestart"));
     99     refresh_timer_.Stop();
    100     chrome::AttemptRestart();
    101   }
    102 
    103   // Update the counter. It may seem counter-intuitive to update the message
    104   // after we attempt restart, but remember that shutdown may be aborted by
    105   // an onbeforeunload handler, leaving the bubble up when the browser should
    106   // have restarted (giving the user another chance).
    107   UpdateBubbleHeadline(seconds);
    108   SchedulePaint();
    109 }
    110 
    111 void CriticalNotificationBubbleView::ButtonPressed(
    112     views::Button* sender, const ui::Event& event) {
    113   // Let other bubbles know we have an answer from the user.
    114   UpgradeDetector::GetInstance()->acknowledge_critical_update();
    115 
    116   if (sender == restart_button_) {
    117     content::RecordAction(UserMetricsAction("CriticalNotification_Restart"));
    118     chrome::AttemptRestart();
    119   } else if (sender == dismiss_button_) {
    120     content::RecordAction(UserMetricsAction("CriticalNotification_Ignore"));
    121     // If the counter reaches 0, we set a restart flag that must be cleared if
    122     // the user selects, for example, "Stay on this page" during an
    123     // onbeforeunload handler.
    124     PrefService* prefs = g_browser_process->local_state();
    125     if (prefs->HasPrefPath(prefs::kRestartLastSessionOnShutdown))
    126       prefs->ClearPref(prefs::kRestartLastSessionOnShutdown);
    127   } else {
    128     NOTREACHED();
    129   }
    130 
    131   GetWidget()->Close();
    132 }
    133 
    134 void CriticalNotificationBubbleView::WindowClosing() {
    135   refresh_timer_.Stop();
    136 }
    137 
    138 void CriticalNotificationBubbleView::GetAccessibleState(
    139     ui::AccessibleViewState* state) {
    140   state->role = ui::AccessibilityTypes::ROLE_ALERT;
    141 }
    142 
    143 void CriticalNotificationBubbleView::ViewHierarchyChanged(
    144     const ViewHierarchyChangedDetails& details) {
    145   if (details.is_add && details.child == this)
    146     NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_ALERT, true);
    147 }
    148 
    149 bool CriticalNotificationBubbleView::AcceleratorPressed(
    150     const ui::Accelerator& accelerator) {
    151   if (accelerator.key_code() == ui::VKEY_ESCAPE)
    152     UpgradeDetector::GetInstance()->acknowledge_critical_update();
    153   return BubbleDelegateView::AcceleratorPressed(accelerator);
    154 }
    155 
    156 void CriticalNotificationBubbleView::Init() {
    157   bubble_created_ = base::Time::Now();
    158 
    159   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    160 
    161   views::GridLayout* layout = views::GridLayout::CreatePanel(this);
    162   layout->SetInsets(0, kInset, kInset, kInset);
    163   SetLayoutManager(layout);
    164 
    165   const int top_column_set_id = 0;
    166   views::ColumnSet* top_columns = layout->AddColumnSet(top_column_set_id);
    167   top_columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER,
    168                          0, views::GridLayout::USE_PREF, 0, 0);
    169   top_columns->AddPaddingColumn(0, kImageHeadlinePadding);
    170   top_columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER,
    171                          0, views::GridLayout::USE_PREF, 0, 0);
    172   top_columns->AddPaddingColumn(1, 0);
    173   layout->StartRow(0, top_column_set_id);
    174 
    175   views::ImageView* image = new views::ImageView();
    176   image->SetImage(rb.GetImageSkiaNamed(IDR_UPDATE_MENU_SEVERITY_HIGH));
    177   layout->AddView(image);
    178 
    179   headline_ = new views::Label();
    180   headline_->SetFont(rb.GetFont(ui::ResourceBundle::MediumFont));
    181   UpdateBubbleHeadline(GetRemainingTime());
    182   layout->AddView(headline_);
    183 
    184   const int middle_column_set_id = 1;
    185   views::ColumnSet* middle_column = layout->AddColumnSet(middle_column_set_id);
    186   middle_column->AddColumn(views::GridLayout::CENTER, views::GridLayout::CENTER,
    187                            0, views::GridLayout::USE_PREF, 0, 0);
    188   layout->StartRowWithPadding(0, middle_column_set_id,
    189                               0, kHeadlineMessagePadding);
    190 
    191   views::Label* message = new views::Label();
    192   message->SetMultiLine(true);
    193   message->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    194   message->SetText(l10n_util::GetStringFUTF16(IDS_CRITICAL_NOTIFICATION_TEXT,
    195       l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)));
    196   message->SizeToFit(views::Widget::GetLocalizedContentsWidth(
    197       IDS_CRUCIAL_NOTIFICATION_BUBBLE_WIDTH_CHARS));
    198   layout->AddView(message);
    199 
    200   const int bottom_column_set_id = 2;
    201   views::ColumnSet* bottom_columns = layout->AddColumnSet(bottom_column_set_id);
    202   bottom_columns->AddPaddingColumn(1, 0);
    203   bottom_columns->AddColumn(views::GridLayout::CENTER,
    204       views::GridLayout::CENTER, 0, views::GridLayout::USE_PREF, 0, 0);
    205   bottom_columns->AddPaddingColumn(0, views::kRelatedButtonHSpacing);
    206   bottom_columns->AddColumn(views::GridLayout::CENTER,
    207       views::GridLayout::CENTER, 0, views::GridLayout::USE_PREF, 0, 0);
    208   layout->StartRowWithPadding(0, bottom_column_set_id,
    209                               0, kMessageBubblePadding);
    210 
    211   restart_button_ = new views::LabelButton(this,
    212       l10n_util::GetStringUTF16(IDS_CRITICAL_NOTIFICATION_RESTART));
    213   restart_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
    214   restart_button_->SetIsDefault(true);
    215   layout->AddView(restart_button_);
    216   dismiss_button_ = new views::LabelButton(this,
    217       l10n_util::GetStringUTF16(IDS_CRITICAL_NOTIFICATION_DISMISS));
    218   dismiss_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
    219   layout->AddView(dismiss_button_);
    220 
    221   refresh_timer_.Start(FROM_HERE,
    222       base::TimeDelta::FromMilliseconds(kRefreshBubbleEvery),
    223       this, &CriticalNotificationBubbleView::OnCountdown);
    224 
    225   content::RecordAction(UserMetricsAction("CriticalNotificationShown"));
    226 }
    227