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