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.
5 #include "chrome/browser/ui/views/critical_notification_bubble_view.h"
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"
30 using base::UserMetricsAction
;
36 const int kImageHeadlinePadding
= 4;
37 const int kHeadlineMessagePadding
= 4;
38 const int kMessageBubblePadding
= 11;
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.
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.
50 ////////////////////////////////////////////////////////////////////////////////
51 // CriticalNotificationBubbleView
53 CriticalNotificationBubbleView::CriticalNotificationBubbleView(
54 views::View
* anchor_view
)
55 : BubbleDelegateView(anchor_view
, views::BubbleBorder::TOP_RIGHT
),
57 restart_button_(NULL
),
58 dismiss_button_(NULL
) {
59 set_close_on_deactivate(false);
60 set_move_with_anchor(true);
63 CriticalNotificationBubbleView::~CriticalNotificationBubbleView() {
66 int CriticalNotificationBubbleView::GetRemainingTime() {
67 base::TimeDelta time_lapsed
= base::Time::Now() - bubble_created_
;
68 return kCountdownDuration
- time_lapsed
.InSeconds();
71 void CriticalNotificationBubbleView::UpdateBubbleHeadline(int seconds
) {
74 l10n_util::GetStringFUTF16(IDS_CRITICAL_NOTIFICATION_HEADLINE
,
75 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME
),
76 base::IntToString16(seconds
)));
79 l10n_util::GetStringFUTF16(IDS_CRITICAL_NOTIFICATION_HEADLINE_ALTERNATE
,
80 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME
)));
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.
92 int seconds
= GetRemainingTime();
95 upgrade_detector
->acknowledge_critical_update();
97 content::RecordAction(
98 UserMetricsAction("CriticalNotification_AutoRestart"));
99 refresh_timer_
.Stop();
100 chrome::AttemptRestart();
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
);
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();
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
);
131 GetWidget()->Close();
134 void CriticalNotificationBubbleView::WindowClosing() {
135 refresh_timer_
.Stop();
138 void CriticalNotificationBubbleView::GetAccessibleState(
139 ui::AccessibleViewState
* state
) {
140 state
->role
= ui::AccessibilityTypes::ROLE_ALERT
;
143 void CriticalNotificationBubbleView::ViewHierarchyChanged(
144 const ViewHierarchyChangedDetails
& details
) {
145 if (details
.is_add
&& details
.child
== this)
146 NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_ALERT
, true);
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
);
156 void CriticalNotificationBubbleView::Init() {
157 bubble_created_
= base::Time::Now();
159 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
161 views::GridLayout
* layout
= views::GridLayout::CreatePanel(this);
162 layout
->SetInsets(0, kInset
, kInset
, kInset
);
163 SetLayoutManager(layout
);
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
);
175 views::ImageView
* image
= new views::ImageView();
176 image
->SetImage(rb
.GetImageSkiaNamed(IDR_UPDATE_MENU_SEVERITY_HIGH
));
177 layout
->AddView(image
);
179 headline_
= new views::Label();
180 headline_
->SetFontList(rb
.GetFontList(ui::ResourceBundle::MediumFont
));
181 UpdateBubbleHeadline(GetRemainingTime());
182 layout
->AddView(headline_
);
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
);
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
);
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
);
211 restart_button_
= new views::LabelButton(this,
212 l10n_util::GetStringUTF16(IDS_CRITICAL_NOTIFICATION_RESTART
));
213 restart_button_
->SetStyle(views::Button::STYLE_BUTTON
);
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_BUTTON
);
219 layout
->AddView(dismiss_button_
);
221 refresh_timer_
.Start(FROM_HERE
,
222 base::TimeDelta::FromMilliseconds(kRefreshBubbleEvery
),
223 this, &CriticalNotificationBubbleView::OnCountdown
);
225 content::RecordAction(UserMetricsAction("CriticalNotificationShown"));