1 // Copyright (c) 2013 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/outdated_upgrade_bubble_view.h" 6 7 #include "base/metrics/histogram.h" 8 #include "base/path_service.h" 9 #include "base/prefs/pref_service.h" 10 #include "chrome/browser/browser_process.h" 11 #include "chrome/browser/ui/views/elevation_icon_setter.h" 12 #include "chrome/browser/upgrade_detector.h" 13 #include "chrome/common/pref_names.h" 14 #include "content/public/browser/browser_thread.h" 15 #include "content/public/browser/page_navigator.h" 16 #include "content/public/browser/user_metrics.h" 17 #include "grit/chromium_strings.h" 18 #include "grit/generated_resources.h" 19 #include "grit/theme_resources.h" 20 #include "ui/base/l10n/l10n_util.h" 21 #include "ui/base/resource/resource_bundle.h" 22 #include "ui/views/controls/button/label_button.h" 23 #include "ui/views/controls/image_view.h" 24 #include "ui/views/controls/label.h" 25 #include "ui/views/layout/grid_layout.h" 26 #include "ui/views/layout/layout_constants.h" 27 #include "ui/views/widget/widget.h" 28 #include "url/gurl.h" 29 30 #if defined(OS_WIN) 31 #include "chrome/installer/util/google_update_util.h" 32 #endif 33 34 namespace { 35 36 // Fixed width of the column holding the description label of the bubble. 37 // TODO(mad): Make sure there is enough room for all languages. 38 const int kWidthOfDescriptionText = 330; 39 40 // We subtract 2 to account for the natural button padding, and 41 // to bring the separation visually in line with the row separation 42 // height. 43 const int kButtonPadding = views::kRelatedButtonHSpacing - 2; 44 45 // The URL to be used to re-install Chrome when auto-update failed for too long. 46 const char kDownloadChromeUrl[] = "https://www.google.com/chrome/?&brand=CHWL" 47 "&utm_campaign=en&utm_source=en-et-na-us-chrome-bubble&utm_medium=et"; 48 49 // The maximum number of ignored bubble we track in the NumLaterPerReinstall 50 // histogram. 51 const int kMaxIgnored = 50; 52 // The number of buckets we want the NumLaterPerReinstall histogram to use. 53 const int kNumIgnoredBuckets = 5; 54 55 } // namespace 56 57 // OutdatedUpgradeBubbleView --------------------------------------------------- 58 59 OutdatedUpgradeBubbleView* OutdatedUpgradeBubbleView::upgrade_bubble_ = NULL; 60 int OutdatedUpgradeBubbleView::num_ignored_bubbles_ = 0; 61 62 // static 63 void OutdatedUpgradeBubbleView::ShowBubble(views::View* anchor_view, 64 content::PageNavigator* navigator, 65 bool auto_update_enabled) { 66 if (IsShowing()) 67 return; 68 upgrade_bubble_ = new OutdatedUpgradeBubbleView( 69 anchor_view, navigator, auto_update_enabled); 70 views::BubbleDelegateView::CreateBubble(upgrade_bubble_)->Show(); 71 content::RecordAction(base::UserMetricsAction( 72 auto_update_enabled ? "OutdatedUpgradeBubble.Show" 73 : "OutdatedUpgradeBubble.ShowNoAU")); 74 } 75 76 bool OutdatedUpgradeBubbleView::IsAvailable() { 77 // This should only work on non-Chrome OS desktop platforms. 78 #if defined(OS_WIN) || defined(OS_MACOSX) || \ 79 (defined(OS_LINUX) && !defined(OS_CHROMEOS)) 80 return true; 81 #else 82 return false; 83 #endif 84 } 85 86 OutdatedUpgradeBubbleView::~OutdatedUpgradeBubbleView() { 87 if (!accepted_ && num_ignored_bubbles_ < kMaxIgnored) 88 ++num_ignored_bubbles_; 89 90 // Ensure |elevation_icon_setter_| is destroyed before |accept_button_|. 91 elevation_icon_setter_.reset(); 92 } 93 94 views::View* OutdatedUpgradeBubbleView::GetInitiallyFocusedView() { 95 return accept_button_; 96 } 97 98 void OutdatedUpgradeBubbleView::WindowClosing() { 99 // Reset |upgrade_bubble_| here, not in destructor, because destruction is 100 // asynchronous and ShowBubble may be called before full destruction and 101 // would attempt to show a bubble that is closing. 102 DCHECK_EQ(upgrade_bubble_, this); 103 upgrade_bubble_ = NULL; 104 } 105 106 void OutdatedUpgradeBubbleView::Init() { 107 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 108 accept_button_ = new views::LabelButton( 109 this, l10n_util::GetStringUTF16( 110 auto_update_enabled_ ? IDS_REINSTALL_APP : IDS_REENABLE_UPDATES)); 111 accept_button_->SetStyle(views::Button::STYLE_BUTTON); 112 accept_button_->SetIsDefault(true); 113 accept_button_->SetFontList(rb.GetFontList(ui::ResourceBundle::BoldFont)); 114 elevation_icon_setter_.reset(new ElevationIconSetter(accept_button_)); 115 116 later_button_ = new views::LabelButton( 117 this, l10n_util::GetStringUTF16(IDS_LATER)); 118 later_button_->SetStyle(views::Button::STYLE_BUTTON); 119 120 views::Label* title_label = new views::Label( 121 l10n_util::GetStringUTF16(IDS_UPGRADE_BUBBLE_TITLE)); 122 title_label->SetFontList(rb.GetFontList(ui::ResourceBundle::MediumFont)); 123 title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 124 125 views::Label* text_label = new views::Label(l10n_util::GetStringUTF16( 126 auto_update_enabled_ ? IDS_UPGRADE_BUBBLE_TEXT 127 : IDS_UPGRADE_BUBBLE_REENABLE_TEXT)); 128 text_label->SetMultiLine(true); 129 text_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 130 131 views::ImageView* image_view = new views::ImageView(); 132 image_view->SetImage(rb.GetImageSkiaNamed(IDR_UPDATE_MENU_SEVERITY_HIGH)); 133 134 views::GridLayout* layout = new views::GridLayout(this); 135 SetLayoutManager(layout); 136 137 const int kIconTitleColumnSetId = 0; 138 views::ColumnSet* cs = layout->AddColumnSet(kIconTitleColumnSetId); 139 140 // Top (icon-title) row. 141 cs->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER, 0, 142 views::GridLayout::USE_PREF, 0, 0); 143 cs->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing); 144 cs->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER, 0, 145 views::GridLayout::USE_PREF, 0, 0); 146 cs->AddPaddingColumn(1, views::kUnrelatedControlHorizontalSpacing); 147 148 // Middle (text) row. 149 const int kTextColumnSetId = 1; 150 cs = layout->AddColumnSet(kTextColumnSetId); 151 cs->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1, 152 views::GridLayout::FIXED, kWidthOfDescriptionText, 0); 153 154 // Bottom (buttons) row. 155 const int kButtonsColumnSetId = 2; 156 cs = layout->AddColumnSet(kButtonsColumnSetId); 157 cs->AddPaddingColumn(1, views::kRelatedControlHorizontalSpacing); 158 cs->AddColumn(views::GridLayout::LEADING, views::GridLayout::TRAILING, 0, 159 views::GridLayout::USE_PREF, 0, 0); 160 cs->AddPaddingColumn(0, kButtonPadding); 161 cs->AddColumn(views::GridLayout::LEADING, views::GridLayout::TRAILING, 0, 162 views::GridLayout::USE_PREF, 0, 0); 163 164 layout->StartRow(0, kIconTitleColumnSetId); 165 layout->AddView(image_view); 166 layout->AddView(title_label); 167 168 layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing); 169 layout->StartRow(0, kTextColumnSetId); 170 layout->AddView(text_label); 171 172 layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing); 173 174 layout->StartRow(0, kButtonsColumnSetId); 175 layout->AddView(accept_button_); 176 layout->AddView(later_button_); 177 178 AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE)); 179 } 180 181 OutdatedUpgradeBubbleView::OutdatedUpgradeBubbleView( 182 views::View* anchor_view, 183 content::PageNavigator* navigator, 184 bool auto_update_enabled) 185 : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT), 186 auto_update_enabled_(auto_update_enabled), 187 accepted_(false), 188 accept_button_(NULL), 189 later_button_(NULL), 190 navigator_(navigator) { 191 // Compensate for built-in vertical padding in the anchor view's image. 192 set_anchor_view_insets(gfx::Insets(5, 0, 5, 0)); 193 } 194 195 void OutdatedUpgradeBubbleView::ButtonPressed( 196 views::Button* sender, const ui::Event& event) { 197 if (event.IsMouseEvent() && 198 !(static_cast<const ui::MouseEvent*>(&event))->IsOnlyLeftMouseButton()) { 199 return; 200 } 201 HandleButtonPressed(sender); 202 } 203 204 void OutdatedUpgradeBubbleView::HandleButtonPressed(views::Button* sender) { 205 if (sender == accept_button_) { 206 accepted_ = true; 207 if (auto_update_enabled_) { 208 DCHECK(UpgradeDetector::GetInstance()->is_outdated_install()); 209 UMA_HISTOGRAM_CUSTOM_COUNTS( 210 "OutdatedUpgradeBubble.NumLaterPerReinstall", num_ignored_bubbles_, 211 0, kMaxIgnored, kNumIgnoredBuckets); 212 content::RecordAction( 213 base::UserMetricsAction("OutdatedUpgradeBubble.Reinstall")); 214 navigator_->OpenURL(content::OpenURLParams(GURL(kDownloadChromeUrl), 215 content::Referrer(), 216 NEW_FOREGROUND_TAB, 217 content::PAGE_TRANSITION_LINK, 218 false)); 219 #if defined(OS_WIN) 220 } else { 221 DCHECK(UpgradeDetector::GetInstance()->is_outdated_install_no_au()); 222 UMA_HISTOGRAM_CUSTOM_COUNTS( 223 "OutdatedUpgradeBubble.NumLaterPerEnableAU", num_ignored_bubbles_, 224 0, kMaxIgnored, kNumIgnoredBuckets); 225 content::RecordAction( 226 base::UserMetricsAction("OutdatedUpgradeBubble.EnableAU")); 227 // Record that the autoupdate flavour of the dialog has been shown. 228 if (g_browser_process->local_state()) { 229 g_browser_process->local_state()->SetBoolean( 230 prefs::kAttemptedToEnableAutoupdate, true); 231 } 232 233 // Re-enable updates by shelling out to setup.exe in the blocking pool. 234 content::BrowserThread::PostBlockingPoolTask( 235 FROM_HERE, 236 base::Bind(&google_update::ElevateIfNeededToReenableUpdates)); 237 #endif // defined(OS_WIN) 238 } 239 } else { 240 DCHECK_EQ(later_button_, sender); 241 content::RecordAction( 242 base::UserMetricsAction("OutdatedUpgradeBubble.Later")); 243 } 244 GetWidget()->Close(); 245 } 246