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