1 // Copyright 2018 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/policy/enterprise_startup_dialog_view.h"
6 
7 #include <utility>
8 
9 #include "base/bind.h"
10 #include "base/i18n/message_formatter.h"
11 #include "base/strings/string16.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/threading/thread_task_runner_handle.h"
14 #include "build/branding_buildflags.h"
15 #include "build/build_config.h"
16 #include "chrome/app/vector_icons/vector_icons.h"
17 #include "chrome/browser/ui/views/chrome_layout_provider.h"
18 #include "chrome/grit/chromium_strings.h"
19 #include "chrome/grit/theme_resources.h"
20 #include "components/constrained_window/constrained_window_views.h"
21 #include "ui/base/l10n/l10n_util.h"
22 #include "ui/base/resource/resource_bundle.h"
23 #include "ui/gfx/color_palette.h"
24 #include "ui/gfx/paint_vector_icon.h"
25 #include "ui/native_theme/native_theme.h"
26 #include "ui/views/background.h"
27 #include "ui/views/border.h"
28 #include "ui/views/controls/button/label_button.h"
29 #include "ui/views/controls/image_view.h"
30 #include "ui/views/controls/label.h"
31 #include "ui/views/controls/throbber.h"
32 #include "ui/views/layout/grid_layout.h"
33 
34 #if defined(OS_MAC)
35 #include "base/task/current_thread.h"
36 #include "chrome/browser/ui/views/policy/enterprise_startup_dialog_mac_util.h"
37 #endif
38 
39 namespace policy {
40 namespace {
41 
42 constexpr int kDialogContentHeight = 190;  // The height of dialog content area.
43 constexpr int kDialogContentWidth = 670;   // The width of dialog content area.
44 constexpr int kIconSize = 24;      // The size of throbber and error icon.
45 constexpr int kLineHeight = 22;    // The height of text line.
46 constexpr int kFontSizeDelta = 3;  // The font size of text.
47 
48 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
49 constexpr int kLogoHeight = 20;  // The height of Chrome enterprise logo.
50 #endif
51 
GetDialogInsets()52 gfx::Insets GetDialogInsets() {
53   return ChromeLayoutProvider::Get()->GetDialogInsetsForContentType(
54       views::CONTROL, views::TEXT);
55 }
56 
CreateText(const base::string16 & message)57 std::unique_ptr<views::Label> CreateText(const base::string16& message) {
58   auto text = std::make_unique<views::Label>(message);
59   text->SetFontList(gfx::FontList().Derive(kFontSizeDelta, gfx::Font::NORMAL,
60                                            gfx::Font::Weight::MEDIUM));
61   text->SetEnabledColor(
62       views::style::GetColor(*text, views::style::CONTEXT_DIALOG_BODY_TEXT,
63                              views::style::STYLE_PRIMARY));
64   text->SetLineHeight(kLineHeight);
65   return text;
66 }
67 
CreateLogoView()68 std::unique_ptr<views::View> CreateLogoView() {
69 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
70   // Show Google Chrome Enterprise logo only for official build.
71   auto logo_image = std::make_unique<views::ImageView>();
72   logo_image->SetImage(
73       ui::ResourceBundle::GetSharedInstance()
74           .GetImageNamed((logo_image->GetNativeTheme()->ShouldUseDarkColors())
75                              ? IDR_PRODUCT_LOGO_ENTERPRISE_WHITE
76                              : IDR_PRODUCT_LOGO_ENTERPRISE)
77           .AsImageSkia());
78   logo_image->SetTooltipText(
79       l10n_util::GetStringUTF16(IDS_PRODUCT_LOGO_ENTERPRISE_ALT_TEXT));
80   gfx::Rect logo_bounds = logo_image->GetImageBounds();
81   logo_image->SetImageSize(gfx::Size(
82       logo_bounds.width() * kLogoHeight / logo_bounds.height(), kLogoHeight));
83   logo_image->SetVerticalAlignment(views::ImageView::Alignment::kCenter);
84   return logo_image;
85 #else
86   return nullptr;
87 #endif
88 }
89 
90 }  // namespace
91 
EnterpriseStartupDialogView(EnterpriseStartupDialog::DialogResultCallback callback)92 EnterpriseStartupDialogView::EnterpriseStartupDialogView(
93     EnterpriseStartupDialog::DialogResultCallback callback)
94     : callback_(std::move(callback)) {
95   set_draggable(true);
96   SetButtons(ui::DIALOG_BUTTON_OK);
97   SetExtraView(CreateLogoView());
98   SetAcceptCallback(
99       base::BindOnce(&EnterpriseStartupDialogView::RunDialogCallback,
100                      base::Unretained(this), true));
101   SetCancelCallback(
102       base::BindOnce(&EnterpriseStartupDialogView::RunDialogCallback,
103                      base::Unretained(this), false));
104   SetCloseCallback(
105       base::BindOnce(&EnterpriseStartupDialogView::RunDialogCallback,
106                      base::Unretained(this), false));
107   SetBorder(views::CreateEmptyBorder(GetDialogInsets()));
108   CreateDialogWidget(this, nullptr, nullptr)->Show();
109 #if defined(OS_MAC)
110   base::ThreadTaskRunnerHandle::Get()->PostTask(
111       FROM_HERE, base::BindOnce(&EnterpriseStartupDialogView::StartModalDialog,
112                                 weak_factory_.GetWeakPtr()));
113 #endif
114 }
115 
~EnterpriseStartupDialogView()116 EnterpriseStartupDialogView::~EnterpriseStartupDialogView() {}
117 
DisplayLaunchingInformationWithThrobber(const base::string16 & information)118 void EnterpriseStartupDialogView::DisplayLaunchingInformationWithThrobber(
119     const base::string16& information) {
120   ResetDialog(false);
121 
122   std::unique_ptr<views::Label> text = CreateText(information);
123   auto throbber = std::make_unique<views::Throbber>();
124   gfx::Size throbber_size = gfx::Size(kIconSize, kIconSize);
125   throbber->SetPreferredSize(throbber_size);
126   throbber->Start();
127 
128   SetupLayout(std::move(throbber), std::move(text));
129 }
130 
DisplayErrorMessage(const base::string16 & error_message,const base::Optional<base::string16> & accept_button)131 void EnterpriseStartupDialogView::DisplayErrorMessage(
132     const base::string16& error_message,
133     const base::Optional<base::string16>& accept_button) {
134   ResetDialog(accept_button.has_value());
135   std::unique_ptr<views::Label> text = CreateText(error_message);
136   auto error_icon = std::make_unique<views::ImageView>();
137   error_icon->SetImage(
138       gfx::CreateVectorIcon(kBrowserToolsErrorIcon, kIconSize,
139                             GetNativeTheme()->GetSystemColor(
140                                 ui::NativeTheme::kColorId_AlertSeverityHigh)));
141 
142   if (accept_button) {
143     // TODO(ellyjones): This should use SetButtonLabel()
144     // instead of changing the button text directly - this might break the
145     // dialog's layout.
146     GetOkButton()->SetText(*accept_button);
147   }
148   SetupLayout(std::move(error_icon), std::move(text));
149 }
150 
CloseDialog()151 void EnterpriseStartupDialogView::CloseDialog() {
152   can_show_browser_window_ = true;
153   GetWidget()->Close();
154 }
155 
AddWidgetObserver(views::WidgetObserver * observer)156 void EnterpriseStartupDialogView::AddWidgetObserver(
157     views::WidgetObserver* observer) {
158   GetWidget()->AddObserver(observer);
159 }
RemoveWidgetObserver(views::WidgetObserver * observer)160 void EnterpriseStartupDialogView::RemoveWidgetObserver(
161     views::WidgetObserver* observer) {
162   GetWidget()->RemoveObserver(observer);
163 }
164 
StartModalDialog()165 void EnterpriseStartupDialogView::StartModalDialog() {
166 #if defined(OS_MAC)
167   base::CurrentThread::ScopedNestableTaskAllower allow_nested;
168   StartModal(GetWidget()->GetNativeWindow());
169 #endif
170 }
171 
RunDialogCallback(bool was_accepted)172 void EnterpriseStartupDialogView::RunDialogCallback(bool was_accepted) {
173 #if defined(OS_MAC)
174   // On mac, we need to stop the modal message loop before returning the result
175   // to the caller who controls its own run loop.
176   StopModal();
177   if (can_show_browser_window_) {
178     std::move(callback_).Run(was_accepted, can_show_browser_window_);
179   } else {
180     base::ThreadTaskRunnerHandle::Get()->PostTask(
181         FROM_HERE, base::BindOnce(std::move(callback_), was_accepted,
182                                   can_show_browser_window_));
183   }
184 #else
185   std::move(callback_).Run(was_accepted, can_show_browser_window_);
186 #endif
187 }
188 
ShouldShowWindowTitle() const189 bool EnterpriseStartupDialogView::ShouldShowWindowTitle() const {
190   return false;
191 }
192 
GetModalType() const193 ui::ModalType EnterpriseStartupDialogView::GetModalType() const {
194   return ui::MODAL_TYPE_NONE;
195 }
196 
CalculatePreferredSize() const197 gfx::Size EnterpriseStartupDialogView::CalculatePreferredSize() const {
198   return gfx::Size(kDialogContentWidth, kDialogContentHeight);
199 }
200 
ResetDialog(bool show_accept_button)201 void EnterpriseStartupDialogView::ResetDialog(bool show_accept_button) {
202   DCHECK(GetOkButton());
203 
204   GetOkButton()->SetVisible(show_accept_button);
205   RemoveAllChildViews(true);
206 }
207 
SetupLayout(std::unique_ptr<views::View> icon,std::unique_ptr<views::View> text)208 void EnterpriseStartupDialogView::SetupLayout(
209     std::unique_ptr<views::View> icon,
210     std::unique_ptr<views::View> text) {
211   // Padding between icon and text
212   int text_padding = ChromeLayoutProvider::Get()->GetDistanceMetric(
213       views::DISTANCE_TEXTFIELD_HORIZONTAL_TEXT_PADDING);
214 
215   views::GridLayout* layout =
216       SetLayoutManager(std::make_unique<views::GridLayout>());
217   auto* columnset = layout->AddColumnSet(0);
218   // Horizontally centre the content.
219   columnset->AddPaddingColumn(1.0, 0);
220   columnset->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
221                        views::GridLayout::kFixedSize,
222                        views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
223   columnset->AddPaddingColumn(views::GridLayout::kFixedSize, text_padding);
224   columnset->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
225                        views::GridLayout::kFixedSize,
226                        views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
227   columnset->AddPaddingColumn(1.0, 0);
228 
229   layout->AddPaddingRow(1.0, 0);
230   layout->StartRow(views::GridLayout::kFixedSize, 0);
231   layout->AddView(std::move(icon));
232   layout->AddView(std::move(text));
233   layout->AddPaddingRow(1.0, 0);
234 
235   // TODO(ellyjones): Why is this being done here?
236   GetWidget()->GetRootView()->Layout();
237   GetWidget()->GetRootView()->SchedulePaint();
238 }
239 
240 /*
241  * EnterpriseStartupDialogImpl
242  */
243 
EnterpriseStartupDialogImpl(DialogResultCallback callback)244 EnterpriseStartupDialogImpl::EnterpriseStartupDialogImpl(
245     DialogResultCallback callback) {
246   dialog_view_ = new EnterpriseStartupDialogView(std::move(callback));
247   dialog_view_->AddWidgetObserver(this);
248 }
249 
~EnterpriseStartupDialogImpl()250 EnterpriseStartupDialogImpl::~EnterpriseStartupDialogImpl() {
251   if (dialog_view_) {
252     dialog_view_->RemoveWidgetObserver(this);
253     dialog_view_->CloseDialog();
254   }
255   CHECK(!IsInObserverList());
256 }
257 
DisplayLaunchingInformationWithThrobber(const base::string16 & information)258 void EnterpriseStartupDialogImpl::DisplayLaunchingInformationWithThrobber(
259     const base::string16& information) {
260   if (dialog_view_)
261     dialog_view_->DisplayLaunchingInformationWithThrobber(information);
262 }
263 
DisplayErrorMessage(const base::string16 & error_message,const base::Optional<base::string16> & accept_button)264 void EnterpriseStartupDialogImpl::DisplayErrorMessage(
265     const base::string16& error_message,
266     const base::Optional<base::string16>& accept_button) {
267   if (dialog_view_)
268     dialog_view_->DisplayErrorMessage(error_message, accept_button);
269 }
IsShowing()270 bool EnterpriseStartupDialogImpl::IsShowing() {
271   return dialog_view_;
272 }
273 
274 // views::WidgetObserver:
OnWidgetClosing(views::Widget * widget)275 void EnterpriseStartupDialogImpl::OnWidgetClosing(views::Widget* widget) {
276   dialog_view_->RemoveWidgetObserver(this);
277   dialog_view_ = nullptr;
278 }
279 
280 /*
281  * EnterpriseStartupDialog
282  */
283 
284 // static
285 std::unique_ptr<EnterpriseStartupDialog>
CreateAndShowDialog(DialogResultCallback callback)286 EnterpriseStartupDialog::CreateAndShowDialog(DialogResultCallback callback) {
287   return std::make_unique<EnterpriseStartupDialogImpl>(std::move(callback));
288 }
289 
290 }  // namespace policy
291