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 "chrome/grit/chromium_strings.h" 15 #include "chrome/grit/generated_resources.h" 16 #include "chrome/grit/locale_settings.h" 17 #include "content/public/browser/user_metrics.h" 18 #include "grit/theme_resources.h" 19 #include "ui/accessibility/ax_view_state.h" 20 #include "ui/base/accelerators/accelerator.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 base::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 } 61 62 CriticalNotificationBubbleView::~CriticalNotificationBubbleView() { 63 } 64 65 int CriticalNotificationBubbleView::GetRemainingTime() { 66 base::TimeDelta time_lapsed = base::Time::Now() - bubble_created_; 67 return kCountdownDuration - time_lapsed.InSeconds(); 68 } 69 70 void CriticalNotificationBubbleView::UpdateBubbleHeadline(int seconds) { 71 if (seconds > 0) { 72 headline_->SetText( 73 l10n_util::GetStringFUTF16(IDS_CRITICAL_NOTIFICATION_HEADLINE, 74 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME), 75 base::IntToString16(seconds))); 76 } else { 77 headline_->SetText( 78 l10n_util::GetStringFUTF16(IDS_CRITICAL_NOTIFICATION_HEADLINE_ALTERNATE, 79 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME))); 80 } 81 } 82 83 void CriticalNotificationBubbleView::OnCountdown() { 84 UpgradeDetector* upgrade_detector = UpgradeDetector::GetInstance(); 85 if (upgrade_detector->critical_update_acknowledged()) { 86 // The user has already interacted with the bubble and chosen a path. 87 GetWidget()->Close(); 88 return; 89 } 90 91 int seconds = GetRemainingTime(); 92 if (seconds <= 0) { 93 // Time's up! 94 upgrade_detector->acknowledge_critical_update(); 95 96 content::RecordAction( 97 UserMetricsAction("CriticalNotification_AutoRestart")); 98 refresh_timer_.Stop(); 99 chrome::AttemptRestart(); 100 } 101 102 // Update the counter. It may seem counter-intuitive to update the message 103 // after we attempt restart, but remember that shutdown may be aborted by 104 // an onbeforeunload handler, leaving the bubble up when the browser should 105 // have restarted (giving the user another chance). 106 UpdateBubbleHeadline(seconds); 107 SchedulePaint(); 108 } 109 110 void CriticalNotificationBubbleView::ButtonPressed( 111 views::Button* sender, const ui::Event& event) { 112 // Let other bubbles know we have an answer from the user. 113 UpgradeDetector::GetInstance()->acknowledge_critical_update(); 114 115 if (sender == restart_button_) { 116 content::RecordAction(UserMetricsAction("CriticalNotification_Restart")); 117 chrome::AttemptRestart(); 118 } else if (sender == dismiss_button_) { 119 content::RecordAction(UserMetricsAction("CriticalNotification_Ignore")); 120 // If the counter reaches 0, we set a restart flag that must be cleared if 121 // the user selects, for example, "Stay on this page" during an 122 // onbeforeunload handler. 123 PrefService* prefs = g_browser_process->local_state(); 124 if (prefs->HasPrefPath(prefs::kRestartLastSessionOnShutdown)) 125 prefs->ClearPref(prefs::kRestartLastSessionOnShutdown); 126 } else { 127 NOTREACHED(); 128 } 129 130 GetWidget()->Close(); 131 } 132 133 void CriticalNotificationBubbleView::WindowClosing() { 134 refresh_timer_.Stop(); 135 } 136 137 void CriticalNotificationBubbleView::GetAccessibleState( 138 ui::AXViewState* state) { 139 state->role = ui::AX_ROLE_ALERT; 140 } 141 142 void CriticalNotificationBubbleView::ViewHierarchyChanged( 143 const ViewHierarchyChangedDetails& details) { 144 if (details.is_add && details.child == this) 145 NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true); 146 } 147 148 bool CriticalNotificationBubbleView::AcceleratorPressed( 149 const ui::Accelerator& accelerator) { 150 if (accelerator.key_code() == ui::VKEY_ESCAPE) 151 UpgradeDetector::GetInstance()->acknowledge_critical_update(); 152 return BubbleDelegateView::AcceleratorPressed(accelerator); 153 } 154 155 void CriticalNotificationBubbleView::Init() { 156 bubble_created_ = base::Time::Now(); 157 158 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 159 160 views::GridLayout* layout = views::GridLayout::CreatePanel(this); 161 layout->SetInsets(0, kInset, kInset, kInset); 162 SetLayoutManager(layout); 163 164 const int top_column_set_id = 0; 165 views::ColumnSet* top_columns = layout->AddColumnSet(top_column_set_id); 166 top_columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER, 167 0, views::GridLayout::USE_PREF, 0, 0); 168 top_columns->AddPaddingColumn(0, kImageHeadlinePadding); 169 top_columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER, 170 0, views::GridLayout::USE_PREF, 0, 0); 171 top_columns->AddPaddingColumn(1, 0); 172 layout->StartRow(0, top_column_set_id); 173 174 views::ImageView* image = new views::ImageView(); 175 image->SetImage(rb.GetImageSkiaNamed(IDR_UPDATE_MENU_SEVERITY_HIGH)); 176 layout->AddView(image); 177 178 headline_ = new views::Label(); 179 headline_->SetFontList(rb.GetFontList(ui::ResourceBundle::MediumFont)); 180 UpdateBubbleHeadline(GetRemainingTime()); 181 layout->AddView(headline_); 182 183 const int middle_column_set_id = 1; 184 views::ColumnSet* middle_column = layout->AddColumnSet(middle_column_set_id); 185 middle_column->AddColumn(views::GridLayout::CENTER, views::GridLayout::CENTER, 186 0, views::GridLayout::USE_PREF, 0, 0); 187 layout->StartRowWithPadding(0, middle_column_set_id, 188 0, kHeadlineMessagePadding); 189 190 views::Label* message = new views::Label(); 191 message->SetMultiLine(true); 192 message->SetHorizontalAlignment(gfx::ALIGN_LEFT); 193 message->SetText(l10n_util::GetStringFUTF16(IDS_CRITICAL_NOTIFICATION_TEXT, 194 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME))); 195 message->SizeToFit(views::Widget::GetLocalizedContentsWidth( 196 IDS_CRUCIAL_NOTIFICATION_BUBBLE_WIDTH_CHARS)); 197 layout->AddView(message); 198 199 const int bottom_column_set_id = 2; 200 views::ColumnSet* bottom_columns = layout->AddColumnSet(bottom_column_set_id); 201 bottom_columns->AddPaddingColumn(1, 0); 202 bottom_columns->AddColumn(views::GridLayout::CENTER, 203 views::GridLayout::CENTER, 0, views::GridLayout::USE_PREF, 0, 0); 204 bottom_columns->AddPaddingColumn(0, views::kRelatedButtonHSpacing); 205 bottom_columns->AddColumn(views::GridLayout::CENTER, 206 views::GridLayout::CENTER, 0, views::GridLayout::USE_PREF, 0, 0); 207 layout->StartRowWithPadding(0, bottom_column_set_id, 208 0, kMessageBubblePadding); 209 210 restart_button_ = new views::LabelButton(this, 211 l10n_util::GetStringUTF16(IDS_CRITICAL_NOTIFICATION_RESTART)); 212 restart_button_->SetStyle(views::Button::STYLE_BUTTON); 213 restart_button_->SetIsDefault(true); 214 layout->AddView(restart_button_); 215 dismiss_button_ = new views::LabelButton(this, 216 l10n_util::GetStringUTF16(IDS_CRITICAL_NOTIFICATION_DISMISS)); 217 dismiss_button_->SetStyle(views::Button::STYLE_BUTTON); 218 layout->AddView(dismiss_button_); 219 220 refresh_timer_.Start(FROM_HERE, 221 base::TimeDelta::FromMilliseconds(kRefreshBubbleEvery), 222 this, &CriticalNotificationBubbleView::OnCountdown); 223 224 content::RecordAction(UserMetricsAction("CriticalNotificationShown")); 225 } 226