| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/views/critical_notification_bubble_view.h" |
| |
| #include "base/prefs/pref_service.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/lifetime/application_lifetime.h" |
| #include "chrome/browser/upgrade_detector.h" |
| #include "chrome/common/pref_names.h" |
| #include "content/public/browser/user_metrics.h" |
| #include "grit/chromium_strings.h" |
| #include "grit/generated_resources.h" |
| #include "grit/locale_settings.h" |
| #include "grit/theme_resources.h" |
| #include "ui/base/accelerators/accelerator.h" |
| #include "ui/base/accessibility/accessible_view_state.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/views/controls/button/label_button.h" |
| #include "ui/views/controls/image_view.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/layout/grid_layout.h" |
| #include "ui/views/layout/layout_constants.h" |
| #include "ui/views/widget/widget.h" |
| |
| using content::UserMetricsAction; |
| |
| namespace { |
| |
| // Layout constants. |
| const int kInset = 2; |
| const int kImageHeadlinePadding = 4; |
| const int kHeadlineMessagePadding = 4; |
| const int kMessageBubblePadding = 11; |
| |
| // How long to give the user until auto-restart if no action is taken. The code |
| // assumes this to be less than a minute. |
| const int kCountdownDuration = 30; // Seconds. |
| |
| // How often to refresh the bubble UI to update the counter. As long as the |
| // countdown is in seconds, this should be 1000 or lower. |
| const int kRefreshBubbleEvery = 1000; // Millisecond. |
| |
| } // namespace |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // CriticalNotificationBubbleView |
| |
| CriticalNotificationBubbleView::CriticalNotificationBubbleView( |
| views::View* anchor_view) |
| : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT), |
| headline_(NULL), |
| restart_button_(NULL), |
| dismiss_button_(NULL) { |
| set_close_on_deactivate(false); |
| set_move_with_anchor(true); |
| } |
| |
| CriticalNotificationBubbleView::~CriticalNotificationBubbleView() { |
| } |
| |
| int CriticalNotificationBubbleView::GetRemainingTime() { |
| base::TimeDelta time_lapsed = base::Time::Now() - bubble_created_; |
| return kCountdownDuration - time_lapsed.InSeconds(); |
| } |
| |
| void CriticalNotificationBubbleView::UpdateBubbleHeadline(int seconds) { |
| if (seconds > 0) { |
| headline_->SetText( |
| l10n_util::GetStringFUTF16(IDS_CRITICAL_NOTIFICATION_HEADLINE, |
| l10n_util::GetStringUTF16(IDS_PRODUCT_NAME), |
| base::IntToString16(seconds))); |
| } else { |
| headline_->SetText( |
| l10n_util::GetStringFUTF16(IDS_CRITICAL_NOTIFICATION_HEADLINE_ALTERNATE, |
| l10n_util::GetStringUTF16(IDS_PRODUCT_NAME))); |
| } |
| } |
| |
| void CriticalNotificationBubbleView::OnCountdown() { |
| UpgradeDetector* upgrade_detector = UpgradeDetector::GetInstance(); |
| if (upgrade_detector->critical_update_acknowledged()) { |
| // The user has already interacted with the bubble and chosen a path. |
| GetWidget()->Close(); |
| return; |
| } |
| |
| int seconds = GetRemainingTime(); |
| if (seconds <= 0) { |
| // Time's up! |
| upgrade_detector->acknowledge_critical_update(); |
| |
| content::RecordAction( |
| UserMetricsAction("CriticalNotification_AutoRestart")); |
| refresh_timer_.Stop(); |
| chrome::AttemptRestart(); |
| } |
| |
| // Update the counter. It may seem counter-intuitive to update the message |
| // after we attempt restart, but remember that shutdown may be aborted by |
| // an onbeforeunload handler, leaving the bubble up when the browser should |
| // have restarted (giving the user another chance). |
| UpdateBubbleHeadline(seconds); |
| SchedulePaint(); |
| } |
| |
| void CriticalNotificationBubbleView::ButtonPressed( |
| views::Button* sender, const ui::Event& event) { |
| // Let other bubbles know we have an answer from the user. |
| UpgradeDetector::GetInstance()->acknowledge_critical_update(); |
| |
| if (sender == restart_button_) { |
| content::RecordAction(UserMetricsAction("CriticalNotification_Restart")); |
| chrome::AttemptRestart(); |
| } else if (sender == dismiss_button_) { |
| content::RecordAction(UserMetricsAction("CriticalNotification_Ignore")); |
| // If the counter reaches 0, we set a restart flag that must be cleared if |
| // the user selects, for example, "Stay on this page" during an |
| // onbeforeunload handler. |
| PrefService* prefs = g_browser_process->local_state(); |
| if (prefs->HasPrefPath(prefs::kRestartLastSessionOnShutdown)) |
| prefs->ClearPref(prefs::kRestartLastSessionOnShutdown); |
| } else { |
| NOTREACHED(); |
| } |
| |
| GetWidget()->Close(); |
| } |
| |
| void CriticalNotificationBubbleView::WindowClosing() { |
| refresh_timer_.Stop(); |
| } |
| |
| void CriticalNotificationBubbleView::GetAccessibleState( |
| ui::AccessibleViewState* state) { |
| state->role = ui::AccessibilityTypes::ROLE_ALERT; |
| } |
| |
| void CriticalNotificationBubbleView::ViewHierarchyChanged( |
| const ViewHierarchyChangedDetails& details) { |
| if (details.is_add && details.child == this) |
| NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_ALERT, true); |
| } |
| |
| bool CriticalNotificationBubbleView::AcceleratorPressed( |
| const ui::Accelerator& accelerator) { |
| if (accelerator.key_code() == ui::VKEY_ESCAPE) |
| UpgradeDetector::GetInstance()->acknowledge_critical_update(); |
| return BubbleDelegateView::AcceleratorPressed(accelerator); |
| } |
| |
| void CriticalNotificationBubbleView::Init() { |
| bubble_created_ = base::Time::Now(); |
| |
| ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| |
| views::GridLayout* layout = views::GridLayout::CreatePanel(this); |
| layout->SetInsets(0, kInset, kInset, kInset); |
| SetLayoutManager(layout); |
| |
| const int top_column_set_id = 0; |
| views::ColumnSet* top_columns = layout->AddColumnSet(top_column_set_id); |
| top_columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER, |
| 0, views::GridLayout::USE_PREF, 0, 0); |
| top_columns->AddPaddingColumn(0, kImageHeadlinePadding); |
| top_columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER, |
| 0, views::GridLayout::USE_PREF, 0, 0); |
| top_columns->AddPaddingColumn(1, 0); |
| layout->StartRow(0, top_column_set_id); |
| |
| views::ImageView* image = new views::ImageView(); |
| image->SetImage(rb.GetImageSkiaNamed(IDR_UPDATE_MENU_SEVERITY_HIGH)); |
| layout->AddView(image); |
| |
| headline_ = new views::Label(); |
| headline_->SetFont(rb.GetFont(ui::ResourceBundle::MediumFont)); |
| UpdateBubbleHeadline(GetRemainingTime()); |
| layout->AddView(headline_); |
| |
| const int middle_column_set_id = 1; |
| views::ColumnSet* middle_column = layout->AddColumnSet(middle_column_set_id); |
| middle_column->AddColumn(views::GridLayout::CENTER, views::GridLayout::CENTER, |
| 0, views::GridLayout::USE_PREF, 0, 0); |
| layout->StartRowWithPadding(0, middle_column_set_id, |
| 0, kHeadlineMessagePadding); |
| |
| views::Label* message = new views::Label(); |
| message->SetMultiLine(true); |
| message->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| message->SetText(l10n_util::GetStringFUTF16(IDS_CRITICAL_NOTIFICATION_TEXT, |
| l10n_util::GetStringUTF16(IDS_PRODUCT_NAME))); |
| message->SizeToFit(views::Widget::GetLocalizedContentsWidth( |
| IDS_CRUCIAL_NOTIFICATION_BUBBLE_WIDTH_CHARS)); |
| layout->AddView(message); |
| |
| const int bottom_column_set_id = 2; |
| views::ColumnSet* bottom_columns = layout->AddColumnSet(bottom_column_set_id); |
| bottom_columns->AddPaddingColumn(1, 0); |
| bottom_columns->AddColumn(views::GridLayout::CENTER, |
| views::GridLayout::CENTER, 0, views::GridLayout::USE_PREF, 0, 0); |
| bottom_columns->AddPaddingColumn(0, views::kRelatedButtonHSpacing); |
| bottom_columns->AddColumn(views::GridLayout::CENTER, |
| views::GridLayout::CENTER, 0, views::GridLayout::USE_PREF, 0, 0); |
| layout->StartRowWithPadding(0, bottom_column_set_id, |
| 0, kMessageBubblePadding); |
| |
| restart_button_ = new views::LabelButton(this, |
| l10n_util::GetStringUTF16(IDS_CRITICAL_NOTIFICATION_RESTART)); |
| restart_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); |
| restart_button_->SetIsDefault(true); |
| layout->AddView(restart_button_); |
| dismiss_button_ = new views::LabelButton(this, |
| l10n_util::GetStringUTF16(IDS_CRITICAL_NOTIFICATION_DISMISS)); |
| dismiss_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); |
| layout->AddView(dismiss_button_); |
| |
| refresh_timer_.Start(FROM_HERE, |
| base::TimeDelta::FromMilliseconds(kRefreshBubbleEvery), |
| this, &CriticalNotificationBubbleView::OnCountdown); |
| |
| content::RecordAction(UserMetricsAction("CriticalNotificationShown")); |
| } |