1 // Copyright (c) 2019 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/enterprise/connectors/content_analysis_dialog.h"
6 
7 #include <memory>
8 
9 #include "base/bind.h"
10 #include "cc/paint/paint_flags.h"
11 #include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h"
12 #include "chrome/browser/ui/views/chrome_layout_provider.h"
13 #include "chrome/grit/generated_resources.h"
14 #include "chrome/grit/theme_resources.h"
15 #include "components/constrained_window/constrained_window_views.h"
16 #include "components/strings/grit/components_strings.h"
17 #include "components/vector_icons/vector_icons.h"
18 #include "content/public/browser/browser_task_traits.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "ui/base/l10n/l10n_util.h"
21 #include "ui/base/resource/resource_bundle.h"
22 #include "ui/base/ui_base_types.h"
23 #include "ui/gfx/canvas.h"
24 #include "ui/gfx/color_palette.h"
25 #include "ui/gfx/color_utils.h"
26 #include "ui/gfx/image/image_skia.h"
27 #include "ui/gfx/paint_vector_icon.h"
28 #include "ui/gfx/text_constants.h"
29 #include "ui/native_theme/native_theme.h"
30 #include "ui/views/accessibility/view_accessibility.h"
31 #include "ui/views/background.h"
32 #include "ui/views/border.h"
33 #include "ui/views/bubble/bubble_frame_view.h"
34 #include "ui/views/controls/image_view.h"
35 #include "ui/views/controls/textfield/textfield.h"
36 #include "ui/views/controls/throbber.h"
37 #include "ui/views/layout/box_layout.h"
38 #include "ui/views/layout/fill_layout.h"
39 #include "ui/views/layout/grid_layout.h"
40 
41 namespace enterprise_connectors {
42 
43 namespace {
44 
45 constexpr base::TimeDelta kResizeAnimationDuration =
46     base::TimeDelta::FromMilliseconds(100);
47 
48 constexpr int kSideImageSize = 24;
49 constexpr int kLineHeight = 20;
50 
51 constexpr gfx::Insets kSideImageInsets = gfx::Insets(8, 8, 8, 8);
52 constexpr gfx::Insets kMessageAndIconRowInsets = gfx::Insets(0, 32, 0, 48);
53 constexpr int kSideIconBetweenChildSpacing = 16;
54 
55 // These time values are non-const in order to be overridden in test so they
56 // complete faster.
57 base::TimeDelta minimum_pending_dialog_time_ = base::TimeDelta::FromSeconds(2);
58 base::TimeDelta success_dialog_timeout_ = base::TimeDelta::FromSeconds(1);
59 
60 // A simple background class to show a colored circle behind the side icon once
61 // the scanning is done.
62 class CircleBackground : public views::Background {
63  public:
CircleBackground(SkColor color)64   explicit CircleBackground(SkColor color) { SetNativeControlColor(color); }
65   ~CircleBackground() override = default;
66 
Paint(gfx::Canvas * canvas,views::View * view) const67   void Paint(gfx::Canvas* canvas, views::View* view) const override {
68     int radius = view->bounds().width() / 2;
69     gfx::PointF center(radius, radius);
70     cc::PaintFlags flags;
71     flags.setAntiAlias(true);
72     flags.setStyle(cc::PaintFlags::kFill_Style);
73     flags.setColor(get_color());
74     canvas->DrawCircle(center, radius, flags);
75   }
76 };
77 
GetBackgroundColor(const views::Widget * widget)78 SkColor GetBackgroundColor(const views::Widget* widget) {
79   return widget->GetNativeTheme()->GetSystemColor(
80       ui::NativeTheme::kColorId_DialogBackground);
81 }
82 
83 ContentAnalysisDialog::TestObserver* observer_for_testing = nullptr;
84 
85 }  // namespace
86 
87 // View classes used to override OnThemeChanged and update the sub-views to the
88 // new theme.
89 
90 class DeepScanningBaseView {
91  public:
DeepScanningBaseView(ContentAnalysisDialog * dialog)92   explicit DeepScanningBaseView(ContentAnalysisDialog* dialog)
93       : dialog_(dialog) {}
dialog()94   ContentAnalysisDialog* dialog() { return dialog_; }
95 
96  protected:
97   ContentAnalysisDialog* dialog_;
98 };
99 
100 class DeepScanningTopImageView : public DeepScanningBaseView,
101                                  public views::ImageView {
102  public:
103   using DeepScanningBaseView::DeepScanningBaseView;
104 
Update()105   void Update() { SetImage(dialog()->GetTopImage()); }
106 
107  protected:
OnThemeChanged()108   void OnThemeChanged() override {
109     views::ImageView::OnThemeChanged();
110     Update();
111   }
112 };
113 
114 class DeepScanningSideIconImageView : public DeepScanningBaseView,
115                                       public views::ImageView {
116  public:
117   using DeepScanningBaseView::DeepScanningBaseView;
118 
Update()119   void Update() {
120     SetImage(gfx::CreateVectorIcon(vector_icons::kBusinessIcon, kSideImageSize,
121                                    dialog()->GetSideImageLogoColor()));
122     if (dialog()->is_result()) {
123       SetBackground(std::make_unique<CircleBackground>(
124           dialog()->GetSideImageBackgroundColor()));
125     }
126   }
127 
128  protected:
OnThemeChanged()129   void OnThemeChanged() override {
130     views::ImageView::OnThemeChanged();
131     Update();
132   }
133 };
134 
135 class DeepScanningSideIconSpinnerView : public DeepScanningBaseView,
136                                         public views::Throbber {
137  public:
138   using DeepScanningBaseView::DeepScanningBaseView;
139 
Update()140   void Update() {
141     if (dialog()->is_result()) {
142       parent()->RemoveChildView(this);
143       delete this;
144     }
145   }
146 
147  protected:
OnThemeChanged()148   void OnThemeChanged() override {
149     views::Throbber::OnThemeChanged();
150     Update();
151   }
152 };
153 
154 class DeepScanningMessageView : public DeepScanningBaseView,
155                                 public views::Label {
156  public:
157   using DeepScanningBaseView::DeepScanningBaseView;
158 
Update()159   void Update() {
160     if (dialog()->is_failure() || dialog()->is_warning())
161       SetEnabledColor(dialog()->GetSideImageBackgroundColor());
162   }
163 
164  protected:
OnThemeChanged()165   void OnThemeChanged() override {
166     views::Label::OnThemeChanged();
167     Update();
168   }
169 };
170 
171 // static
GetMinimumPendingDialogTime()172 base::TimeDelta ContentAnalysisDialog::GetMinimumPendingDialogTime() {
173   return minimum_pending_dialog_time_;
174 }
175 
176 // static
GetSuccessDialogTimeout()177 base::TimeDelta ContentAnalysisDialog::GetSuccessDialogTimeout() {
178   return success_dialog_timeout_;
179 }
180 
ContentAnalysisDialog(std::unique_ptr<ContentAnalysisDelegate> delegate,content::WebContents * web_contents,safe_browsing::DeepScanAccessPoint access_point,int files_count)181 ContentAnalysisDialog::ContentAnalysisDialog(
182     std::unique_ptr<ContentAnalysisDelegate> delegate,
183     content::WebContents* web_contents,
184     safe_browsing::DeepScanAccessPoint access_point,
185     int files_count)
186     : content::WebContentsObserver(web_contents),
187       delegate_(std::move(delegate)),
188       web_contents_(web_contents),
189       access_point_(std::move(access_point)),
190       files_count_(files_count) {
191   SetOwnedByWidget(true);
192 
193   if (observer_for_testing)
194     observer_for_testing->ConstructorCalled(this, base::TimeTicks::Now());
195 
196   Show();
197 }
198 
GetWindowTitle() const199 base::string16 ContentAnalysisDialog::GetWindowTitle() const {
200   return base::string16();
201 }
202 
AcceptButtonCallback()203 void ContentAnalysisDialog::AcceptButtonCallback() {
204   DCHECK(delegate_);
205   DCHECK(is_warning());
206   delegate_->BypassWarnings();
207 }
208 
CancelButtonCallback()209 void ContentAnalysisDialog::CancelButtonCallback() {
210   if (delegate_)
211     delegate_->Cancel(is_warning());
212 }
213 
ShouldShowCloseButton() const214 bool ContentAnalysisDialog::ShouldShowCloseButton() const {
215   return false;
216 }
217 
GetContentsView()218 views::View* ContentAnalysisDialog::GetContentsView() {
219   if (!contents_view_) {
220     contents_view_ = new views::View();  // Owned by caller.
221 
222     // Create layout
223     views::GridLayout* layout =
224         contents_view_->SetLayoutManager(std::make_unique<views::GridLayout>());
225     views::ColumnSet* columns = layout->AddColumnSet(0);
226     columns->AddColumn(
227         /*h_align=*/views::GridLayout::FILL,
228         /*v_align=*/views::GridLayout::FILL,
229         /*resize_percent=*/1.0,
230         /*size_type=*/views::GridLayout::ColumnSize::kUsePreferred,
231         /*fixed_width=*/0,
232         /*min_width=*/0);
233 
234     // Add the top image.
235     layout->StartRow(views::GridLayout::kFixedSize, 0);
236     image_ = layout->AddView(std::make_unique<DeepScanningTopImageView>(this));
237 
238     // Add padding to distance the top image from the icon and message.
239     layout->AddPaddingRow(views::GridLayout::kFixedSize, 16);
240 
241     // Add the side icon and message row.
242     layout->StartRow(views::GridLayout::kFixedSize, 0);
243     auto icon_and_message_row = std::make_unique<views::View>();
244     auto* row_layout = icon_and_message_row->SetLayoutManager(
245         std::make_unique<views::BoxLayout>(
246             views::BoxLayout::Orientation::kHorizontal,
247             kMessageAndIconRowInsets, kSideIconBetweenChildSpacing));
248     row_layout->set_main_axis_alignment(
249         views::BoxLayout::MainAxisAlignment::kStart);
250     row_layout->set_cross_axis_alignment(
251         views::BoxLayout::CrossAxisAlignment::kCenter);
252 
253     // Add the side icon.
254     icon_and_message_row->AddChildView(CreateSideIcon());
255 
256     // Add the message.
257     auto label = std::make_unique<DeepScanningMessageView>(this);
258     label->SetText(GetDialogMessage());
259     label->SetLineHeight(kLineHeight);
260     label->SetMultiLine(true);
261     label->SetVerticalAlignment(gfx::ALIGN_MIDDLE);
262     label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
263     message_ = icon_and_message_row->AddChildView(std::move(label));
264 
265     layout->AddView(std::move(icon_and_message_row));
266 
267     // Add padding to distance the message from the button(s).
268     layout->AddPaddingRow(views::GridLayout::kFixedSize, 10);
269   }
270 
271   return contents_view_;
272 }
273 
GetWidget()274 views::Widget* ContentAnalysisDialog::GetWidget() {
275   return contents_view_->GetWidget();
276 }
277 
GetWidget() const278 const views::Widget* ContentAnalysisDialog::GetWidget() const {
279   return contents_view_->GetWidget();
280 }
281 
GetModalType() const282 ui::ModalType ContentAnalysisDialog::GetModalType() const {
283   return ui::MODAL_TYPE_CHILD;
284 }
285 
WebContentsDestroyed()286 void ContentAnalysisDialog::WebContentsDestroyed() {
287   // If |web_contents_| is destroyed, then the scan results don't matter so the
288   // delegate can be destroyed as well.
289   delegate_.reset(nullptr);
290   CancelDialog();
291 }
292 
ShowResult(ContentAnalysisDelegate::FinalResult result)293 void ContentAnalysisDialog::ShowResult(
294     ContentAnalysisDelegate::FinalResult result) {
295   DCHECK(is_pending());
296   final_result_ = result;
297   switch (final_result_) {
298     case ContentAnalysisDelegate::FinalResult::ENCRYPTED_FILES:
299     case ContentAnalysisDelegate::FinalResult::LARGE_FILES:
300     case ContentAnalysisDelegate::FinalResult::FAILURE:
301       dialog_status_ = DeepScanningDialogStatus::FAILURE;
302       break;
303     case ContentAnalysisDelegate::FinalResult::SUCCESS:
304       dialog_status_ = DeepScanningDialogStatus::SUCCESS;
305       break;
306     case ContentAnalysisDelegate::FinalResult::WARNING:
307       dialog_status_ = DeepScanningDialogStatus::WARNING;
308       break;
309   }
310 
311   // Do nothing if the pending dialog wasn't shown, the delayed |Show| callback
312   // will show the negative result later if that's the verdict.
313   if (!shown_) {
314     // Cleanup if the pending dialog wasn't shown and the verdict is safe.
315     if (is_success())
316       delete this;
317     return;
318   }
319 
320   // Update the pending dialog only after it has been shown for a minimum amount
321   // of time.
322   base::TimeDelta time_shown = base::TimeTicks::Now() - first_shown_timestamp_;
323   if (time_shown >= GetMinimumPendingDialogTime()) {
324     UpdateDialog();
325   } else {
326     content::GetUIThreadTaskRunner({})->PostDelayedTask(
327         FROM_HERE,
328         base::BindOnce(&ContentAnalysisDialog::UpdateDialog,
329                        weak_ptr_factory_.GetWeakPtr()),
330         GetMinimumPendingDialogTime() - time_shown);
331   }
332 }
333 
~ContentAnalysisDialog()334 ContentAnalysisDialog::~ContentAnalysisDialog() {
335   if (observer_for_testing)
336     observer_for_testing->DestructorCalled(this);
337 }
338 
UpdateDialog()339 void ContentAnalysisDialog::UpdateDialog() {
340   DCHECK(shown_);
341   views::Widget* widget = GetWidget();
342   DCHECK(widget);
343   DCHECK(is_result());
344 
345   // Update the style of the dialog to reflect the new state.
346   message_->Update();
347   image_->Update();
348   side_icon_image_->Update();
349   side_icon_spinner_->Update();
350   side_icon_spinner_ = nullptr;
351 
352   // Update the buttons.
353   SetupButtons();
354 
355   // Update the message's text, and send an alert for screen readers since the
356   // text changed.
357   base::string16 new_message = GetDialogMessage();
358   message_->SetText(new_message);
359   message_->GetViewAccessibility().AnnounceText(std::move(new_message));
360 
361   // Resize the dialog's height. This is needed since the text might take more
362   // lines after changing.
363   int text_height = message_->GetRequiredLines() * message_->GetLineHeight();
364   int row_height = message_->parent()->height();
365   int height_to_add = std::max(text_height - row_height, 0);
366   if (height_to_add > 0)
367     Resize(height_to_add);
368 
369   // Update the dialog.
370   DialogDelegate::DialogModelChanged();
371   widget->ScheduleLayout();
372 
373   // Schedule the dialog to close itself in the success case.
374   if (is_success()) {
375     content::GetUIThreadTaskRunner({})->PostDelayedTask(
376         FROM_HERE,
377         base::BindOnce(&DialogDelegate::CancelDialog,
378                        weak_ptr_factory_.GetWeakPtr()),
379         GetSuccessDialogTimeout());
380   }
381 
382   if (observer_for_testing)
383     observer_for_testing->DialogUpdated(this, final_result_);
384 
385   // Cancel the dialog as it is updated in tests in the failure dialog case.
386   // This is necessary to terminate tests that end when the dialog is closed.
387   if (observer_for_testing && is_failure())
388     CancelDialog();
389 }
390 
Resize(int height_to_add)391 void ContentAnalysisDialog::Resize(int height_to_add) {
392   // Only resize if the dialog is updated to show a result.
393   DCHECK(is_result());
394   views::Widget* widget = GetWidget();
395   DCHECK(widget);
396 
397   gfx::Rect dialog_rect = widget->GetContentsView()->GetContentsBounds();
398   int new_height = dialog_rect.height();
399 
400   // Remove the button row's height if it's removed in the success case.
401   if (is_success()) {
402     DCHECK(contents_view_->parent());
403     DCHECK_EQ(contents_view_->parent()->children().size(), 2ul);
404     DCHECK_EQ(contents_view_->parent()->children()[0], contents_view_);
405 
406     views::View* button_row_view = contents_view_->parent()->children()[1];
407     new_height -= button_row_view->GetContentsBounds().height();
408   }
409 
410   // Apply the message lines delta.
411   new_height += height_to_add;
412   dialog_rect.set_height(new_height);
413 
414   // Setup the animation.
415   bounds_animator_ =
416       std::make_unique<views::BoundsAnimator>(widget->GetRootView());
417   bounds_animator_->SetAnimationDuration(kResizeAnimationDuration);
418 
419   DCHECK(widget->GetRootView());
420   DCHECK_EQ(widget->GetRootView()->children().size(), 1u);
421   views::View* view_to_resize = widget->GetRootView()->children()[0];
422 
423   // Start the animation.
424   bounds_animator_->AnimateViewTo(view_to_resize, dialog_rect);
425 
426   // Change the widget's size.
427   gfx::Size new_size = view_to_resize->size();
428   new_size.set_height(new_height);
429   widget->SetSize(new_size);
430 }
431 
SetupButtons()432 void ContentAnalysisDialog::SetupButtons() {
433   // TODO(domfc): Add "Learn more" button on scan failure.
434   if (is_warning()) {
435     // Include the Ok and Cancel buttons if there is a bypassable warning.
436     DialogDelegate::SetButtons(ui::DIALOG_BUTTON_CANCEL | ui::DIALOG_BUTTON_OK);
437     DialogDelegate::SetDefaultButton(ui::DIALOG_BUTTON_CANCEL);
438 
439     DialogDelegate::SetButtonLabel(ui::DIALOG_BUTTON_CANCEL,
440                                    GetCancelButtonText());
441     DialogDelegate::SetCancelCallback(
442         base::BindOnce(&ContentAnalysisDialog::CancelButtonCallback,
443                        weak_ptr_factory_.GetWeakPtr()));
444 
445     DialogDelegate::SetButtonLabel(ui::DIALOG_BUTTON_OK,
446                                    GetBypassWarningButtonText());
447     DialogDelegate::SetAcceptCallback(
448         base::BindOnce(&ContentAnalysisDialog::AcceptButtonCallback,
449                        weak_ptr_factory_.GetWeakPtr()));
450   } else if (is_failure() || is_pending()) {
451     // Include the Cancel button when the scan is pending or failing.
452     DialogDelegate::SetButtons(ui::DIALOG_BUTTON_CANCEL);
453     DialogDelegate::SetDefaultButton(ui::DIALOG_BUTTON_NONE);
454 
455     DialogDelegate::SetButtonLabel(ui::DIALOG_BUTTON_CANCEL,
456                                    GetCancelButtonText());
457     DialogDelegate::SetCancelCallback(
458         base::BindOnce(&ContentAnalysisDialog::CancelButtonCallback,
459                        weak_ptr_factory_.GetWeakPtr()));
460   } else {
461     // Include no buttons otherwise.
462     DialogDelegate::SetButtons(ui::DIALOG_BUTTON_NONE);
463   }
464 }
465 
GetDialogMessage() const466 base::string16 ContentAnalysisDialog::GetDialogMessage() const {
467   switch (dialog_status_) {
468     case DeepScanningDialogStatus::PENDING:
469       return GetPendingMessage();
470     case DeepScanningDialogStatus::FAILURE:
471       return GetFailureMessage();
472     case DeepScanningDialogStatus::SUCCESS:
473       return GetSuccessMessage();
474     case DeepScanningDialogStatus::WARNING:
475       return GetWarningMessage();
476   }
477 }
478 
GetCancelButtonText() const479 base::string16 ContentAnalysisDialog::GetCancelButtonText() const {
480   int text_id;
481   switch (dialog_status_) {
482     case DeepScanningDialogStatus::SUCCESS:
483       NOTREACHED();
484       FALLTHROUGH;
485     case DeepScanningDialogStatus::PENDING:
486       text_id = IDS_DEEP_SCANNING_DIALOG_CANCEL_UPLOAD_BUTTON;
487       break;
488     case DeepScanningDialogStatus::FAILURE:
489       text_id = IDS_CLOSE;
490       break;
491     case DeepScanningDialogStatus::WARNING:
492       text_id = IDS_DEEP_SCANNING_DIALOG_CANCEL_WARNING_BUTTON;
493       break;
494   }
495   return l10n_util::GetStringUTF16(text_id);
496 }
497 
GetBypassWarningButtonText() const498 base::string16 ContentAnalysisDialog::GetBypassWarningButtonText() const {
499   DCHECK(is_warning());
500   return l10n_util::GetStringUTF16(IDS_DEEP_SCANNING_DIALOG_PROCEED_BUTTON);
501 }
502 
Show()503 void ContentAnalysisDialog::Show() {
504   DCHECK(!shown_);
505 
506   // The only state that cannot be shown immediately is SUCCESS, the dialog
507   // doesn't appear in that case.
508   DCHECK(!is_success());
509 
510   shown_ = true;
511   first_shown_timestamp_ = base::TimeTicks::Now();
512 
513   SetupButtons();
514 
515   constrained_window::ShowWebModalDialogViews(this, web_contents_);
516 
517   if (observer_for_testing)
518     observer_for_testing->ViewsFirstShown(this, first_shown_timestamp_);
519 
520   // Cancel the dialog as it is shown in tests if the failure dialog is shown
521   // immediately.
522   if (observer_for_testing && is_failure())
523     CancelDialog();
524 }
525 
CreateSideIcon()526 std::unique_ptr<views::View> ContentAnalysisDialog::CreateSideIcon() {
527   // The side icon is created either:
528   // - When the pending dialog is shown
529   // - When the response was fast enough that the failure dialog is shown first
530   DCHECK(!is_success());
531 
532   // The icon left of the text has the appearance of a blue "Enterprise" logo
533   // with a spinner when the scan is pending.
534   auto icon = std::make_unique<views::View>();
535   icon->SetLayoutManager(std::make_unique<views::FillLayout>());
536 
537   auto side_image = std::make_unique<DeepScanningSideIconImageView>(this);
538   side_image->SetImage(gfx::CreateVectorIcon(
539       gfx::IconDescription(vector_icons::kBusinessIcon, kSideImageSize)));
540   side_image->SetBorder(views::CreateEmptyBorder(kSideImageInsets));
541   side_icon_image_ = icon->AddChildView(std::move(side_image));
542 
543   // Add a spinner if the scan result is pending.
544   if (is_pending()) {
545     auto spinner = std::make_unique<DeepScanningSideIconSpinnerView>(this);
546     spinner->Start();
547     side_icon_spinner_ = icon->AddChildView(std::move(spinner));
548   }
549 
550   return icon;
551 }
552 
GetSideImageBackgroundColor() const553 SkColor ContentAnalysisDialog::GetSideImageBackgroundColor() const {
554   DCHECK(is_result());
555   const views::Widget* widget = GetWidget();
556   DCHECK(widget);
557   ui::NativeTheme::ColorId color_id =
558       is_success() ? ui::NativeTheme::kColorId_AlertSeverityLow
559                    : ui::NativeTheme::kColorId_AlertSeverityHigh;
560   return widget->GetNativeTheme()->GetSystemColor(color_id);
561 }
562 
GetPasteImageId(bool use_dark) const563 int ContentAnalysisDialog::GetPasteImageId(bool use_dark) const {
564   if (is_pending())
565     return use_dark ? IDR_PASTE_SCANNING_DARK : IDR_PASTE_SCANNING;
566   if (is_success())
567     return use_dark ? IDR_PASTE_SUCCESS_DARK : IDR_PASTE_SUCCESS;
568   return use_dark ? IDR_PASTE_VIOLATION_DARK : IDR_PASTE_VIOLATION;
569 }
570 
GetUploadImageId(bool use_dark) const571 int ContentAnalysisDialog::GetUploadImageId(bool use_dark) const {
572   if (is_pending())
573     return use_dark ? IDR_UPLOAD_SCANNING_DARK : IDR_UPLOAD_SCANNING;
574   if (is_success())
575     return use_dark ? IDR_UPLOAD_SUCCESS_DARK : IDR_UPLOAD_SUCCESS;
576   return use_dark ? IDR_UPLOAD_VIOLATION_DARK : IDR_UPLOAD_VIOLATION;
577 }
578 
GetPendingMessage() const579 base::string16 ContentAnalysisDialog::GetPendingMessage() const {
580   DCHECK(is_pending());
581   return l10n_util::GetPluralStringFUTF16(
582       IDS_DEEP_SCANNING_DIALOG_UPLOAD_PENDING_MESSAGE, files_count_);
583 }
584 
GetFailureMessage() const585 base::string16 ContentAnalysisDialog::GetFailureMessage() const {
586   DCHECK(is_failure());
587 
588   if (final_result_ == ContentAnalysisDelegate::FinalResult::LARGE_FILES) {
589     return l10n_util::GetPluralStringFUTF16(
590         IDS_DEEP_SCANNING_DIALOG_LARGE_FILE_FAILURE_MESSAGE, files_count_);
591   }
592 
593   if (final_result_ == ContentAnalysisDelegate::FinalResult::ENCRYPTED_FILES) {
594     return l10n_util::GetPluralStringFUTF16(
595         IDS_DEEP_SCANNING_DIALOG_ENCRYPTED_FILE_FAILURE_MESSAGE, files_count_);
596   }
597 
598   return l10n_util::GetPluralStringFUTF16(
599       IDS_DEEP_SCANNING_DIALOG_UPLOAD_FAILURE_MESSAGE, files_count_);
600 }
601 
GetWarningMessage() const602 base::string16 ContentAnalysisDialog::GetWarningMessage() const {
603   DCHECK(is_warning());
604   return l10n_util::GetPluralStringFUTF16(
605       IDS_DEEP_SCANNING_DIALOG_UPLOAD_WARNING_MESSAGE, files_count_);
606 }
607 
GetSuccessMessage() const608 base::string16 ContentAnalysisDialog::GetSuccessMessage() const {
609   DCHECK(is_success());
610   return l10n_util::GetPluralStringFUTF16(
611       IDS_DEEP_SCANNING_DIALOG_SUCCESS_MESSAGE, files_count_);
612 }
613 
GetTopImage() const614 const gfx::ImageSkia* ContentAnalysisDialog::GetTopImage() const {
615   const bool use_dark = color_utils::IsDark(GetBackgroundColor(GetWidget()));
616   const bool treat_as_text_paste =
617       access_point_ == safe_browsing::DeepScanAccessPoint::PASTE ||
618       (access_point_ == safe_browsing::DeepScanAccessPoint::DRAG_AND_DROP &&
619        files_count_ == 0);
620 
621   int image_id = treat_as_text_paste ? GetPasteImageId(use_dark)
622                                      : GetUploadImageId(use_dark);
623 
624   return ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(image_id);
625 }
626 
GetSideImageLogoColor() const627 SkColor ContentAnalysisDialog::GetSideImageLogoColor() const {
628   const views::Widget* widget = GetWidget();
629   DCHECK(widget);
630   switch (dialog_status_) {
631     case DeepScanningDialogStatus::PENDING:
632       // Match the spinner in the pending state.
633       return widget->GetNativeTheme()->GetSystemColor(
634           ui::NativeTheme::kColorId_ThrobberSpinningColor);
635     case DeepScanningDialogStatus::SUCCESS:
636     case DeepScanningDialogStatus::FAILURE:
637     case DeepScanningDialogStatus::WARNING:
638       // In a result state the background will have the result's color, so the
639       // logo should have the same color as the background.
640       return GetBackgroundColor(widget);
641   }
642 }
643 
644 // static
SetMinimumPendingDialogTimeForTesting(base::TimeDelta delta)645 void ContentAnalysisDialog::SetMinimumPendingDialogTimeForTesting(
646     base::TimeDelta delta) {
647   minimum_pending_dialog_time_ = delta;
648 }
649 
650 // static
SetSuccessDialogTimeoutForTesting(base::TimeDelta delta)651 void ContentAnalysisDialog::SetSuccessDialogTimeoutForTesting(
652     base::TimeDelta delta) {
653   success_dialog_timeout_ = delta;
654 }
655 
656 // static
SetObserverForTesting(TestObserver * observer)657 void ContentAnalysisDialog::SetObserverForTesting(TestObserver* observer) {
658   observer_for_testing = observer;
659 }
660 
GetTopImageForTesting() const661 views::ImageView* ContentAnalysisDialog::GetTopImageForTesting() const {
662   return image_;
663 }
664 
GetSideIconSpinnerForTesting() const665 views::Throbber* ContentAnalysisDialog::GetSideIconSpinnerForTesting() const {
666   return side_icon_spinner_;
667 }
668 
GetMessageForTesting() const669 views::Label* ContentAnalysisDialog::GetMessageForTesting() const {
670   return message_;
671 }
672 
673 }  // namespace enterprise_connectors
674