critical_notification_bubble_view.cc revision a93a17c8d99d686bd4a1511e5504e5e6cc9fcadf
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/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
30using content::UserMetricsAction;
31
32namespace {
33
34// Layout constants.
35const int kInset = 2;
36const int kImageHeadlinePadding = 4;
37const int kHeadlineMessagePadding = 4;
38const 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.
42const 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.
46const int kRefreshBubbleEvery = 1000;  // Millisecond.
47
48}  // namespace
49
50////////////////////////////////////////////////////////////////////////////////
51// CriticalNotificationBubbleView
52
53CriticalNotificationBubbleView::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
63CriticalNotificationBubbleView::~CriticalNotificationBubbleView() {
64}
65
66int CriticalNotificationBubbleView::GetRemainingTime() {
67  base::TimeDelta time_lapsed = base::Time::Now() - bubble_created_;
68  return kCountdownDuration - time_lapsed.InSeconds();
69}
70
71void 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
84void 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
111void 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
134void CriticalNotificationBubbleView::WindowClosing() {
135  refresh_timer_.Stop();
136}
137
138void CriticalNotificationBubbleView::GetAccessibleState(
139    ui::AccessibleViewState* state) {
140  state->role = ui::AccessibilityTypes::ROLE_ALERT;
141}
142
143void CriticalNotificationBubbleView::ViewHierarchyChanged(
144    const ViewHierarchyChangedDetails& details) {
145  if (details.is_add && details.child == this)
146    NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_ALERT, true);
147}
148
149bool 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
156void 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