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/crostini/crostini_uninstaller_view.h"
6
7 #include "base/bind.h"
8 #include "base/metrics/histogram_functions.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/chromeos/crostini/crostini_features.h"
11 #include "chrome/browser/chromeos/crostini/crostini_manager.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/ui/browser_dialogs.h"
14 #include "chrome/browser/ui/views/chrome_layout_provider.h"
15 #include "chrome/grit/generated_resources.h"
16 #include "ui/base/l10n/l10n_util.h"
17 #include "ui/base/resource/resource_bundle.h"
18 #include "ui/chromeos/devicetype_utils.h"
19 #include "ui/strings/grit/ui_strings.h"
20 #include "ui/views/border.h"
21 #include "ui/views/controls/label.h"
22 #include "ui/views/controls/progress_bar.h"
23 #include "ui/views/layout/box_layout.h"
24 #include "ui/views/layout/layout_provider.h"
25
26 namespace {
27
28 CrostiniUninstallerView* g_crostini_uninstaller_view = nullptr;
29
30 constexpr char kCrostiniUninstallResultHistogram[] = "Crostini.UninstallResult";
31 constexpr char kCrostiniUninstallSourceHistogram[] = "Crostini.UninstallSource";
32
33 } // namespace
34
ShowCrostiniUninstallerView(Profile * profile,crostini::CrostiniUISurface ui_surface)35 void crostini::ShowCrostiniUninstallerView(
36 Profile* profile,
37 crostini::CrostiniUISurface ui_surface) {
38 base::UmaHistogramEnumeration(kCrostiniUninstallSourceHistogram, ui_surface,
39 crostini::CrostiniUISurface::kCount);
40 return CrostiniUninstallerView::Show(profile);
41 }
42
Show(Profile * profile)43 void CrostiniUninstallerView::Show(Profile* profile) {
44 DCHECK(crostini::CrostiniFeatures::Get()->IsUIAllowed(profile));
45 if (!g_crostini_uninstaller_view) {
46 g_crostini_uninstaller_view = new CrostiniUninstallerView(profile);
47 views::DialogDelegate::CreateDialogWidget(g_crostini_uninstaller_view,
48 nullptr, nullptr);
49 }
50 g_crostini_uninstaller_view->GetWidget()->Show();
51 }
52
Accept()53 bool CrostiniUninstallerView::Accept() {
54 state_ = State::UNINSTALLING;
55 SetButtons(ui::DIALOG_BUTTON_NONE);
56 message_label_->SetText(
57 l10n_util::GetStringUTF16(IDS_CROSTINI_UNINSTALLER_UNINSTALLING_MESSAGE));
58
59 // Kick off the Crostini Remove sequence.
60 crostini::CrostiniManager::GetForProfile(profile_)->RemoveCrostini(
61 crostini::kCrostiniDefaultVmName,
62 base::BindOnce(&CrostiniUninstallerView::UninstallCrostiniFinished,
63 weak_ptr_factory_.GetWeakPtr()));
64
65 progress_bar_ = new views::ProgressBar();
66 AddChildView(progress_bar_);
67 // Setting value to -1 makes the progress bar play the
68 // "indeterminate animation".
69 progress_bar_->SetValue(-1);
70 DialogModelChanged();
71 GetWidget()->UpdateWindowTitle();
72 GetWidget()->SetSize(GetWidget()->non_client_view()->GetPreferredSize());
73 return false; // Should not close the dialog
74 }
75
Cancel()76 bool CrostiniUninstallerView::Cancel() {
77 RecordUninstallResultHistogram(UninstallResult::kCancelled);
78 return true; // Should close the dialog
79 }
80
81 // static
GetActiveViewForTesting()82 CrostiniUninstallerView* CrostiniUninstallerView::GetActiveViewForTesting() {
83 return g_crostini_uninstaller_view;
84 }
85
CrostiniUninstallerView(Profile * profile)86 CrostiniUninstallerView::CrostiniUninstallerView(Profile* profile)
87 : profile_(profile) {
88 SetShowCloseButton(false);
89 SetTitle(IDS_CROSTINI_UNINSTALLER_TITLE);
90 SetButtonLabel(
91 ui::DIALOG_BUTTON_OK,
92 l10n_util::GetStringUTF16(IDS_CROSTINI_UNINSTALLER_UNINSTALL_BUTTON));
93 set_fixed_width(ChromeLayoutProvider::Get()->GetDistanceMetric(
94 DISTANCE_STANDALONE_BUBBLE_PREFERRED_WIDTH));
95 views::LayoutProvider* provider = views::LayoutProvider::Get();
96 SetLayoutManager(std::make_unique<views::BoxLayout>(
97 views::BoxLayout::Orientation::kVertical,
98 provider->GetInsetsMetric(views::InsetsMetric::INSETS_DIALOG),
99 provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL)));
100
101 const base::string16 device_type = ui::GetChromeOSDeviceName();
102 const base::string16 message =
103 l10n_util::GetStringFUTF16(IDS_CROSTINI_UNINSTALLER_BODY, device_type);
104 message_label_ = new views::Label(message);
105 message_label_->SetMultiLine(true);
106 message_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
107 AddChildView(message_label_);
108
109 chrome::RecordDialogCreation(chrome::DialogIdentifier::CROSTINI_UNINSTALLER);
110 }
111
~CrostiniUninstallerView()112 CrostiniUninstallerView::~CrostiniUninstallerView() {
113 g_crostini_uninstaller_view = nullptr;
114 }
115
HandleError(const base::string16 & error_message)116 void CrostiniUninstallerView::HandleError(const base::string16& error_message) {
117 state_ = State::ERROR;
118 SetButtons(ui::DIALOG_BUTTON_CANCEL);
119 message_label_->SetVisible(true);
120 message_label_->SetText(error_message);
121 progress_bar_->SetVisible(false);
122 DialogModelChanged();
123 GetWidget()->UpdateWindowTitle();
124 GetWidget()->SetSize(GetWidget()->non_client_view()->GetPreferredSize());
125 RecordUninstallResultHistogram(UninstallResult::kError);
126 }
127
UninstallCrostiniFinished(crostini::CrostiniResult result)128 void CrostiniUninstallerView::UninstallCrostiniFinished(
129 crostini::CrostiniResult result) {
130 if (result != crostini::CrostiniResult::SUCCESS) {
131 HandleError(l10n_util::GetStringUTF16(IDS_CROSTINI_UNINSTALLER_ERROR));
132 return;
133 } else {
134 RecordUninstallResultHistogram(UninstallResult::kSuccess);
135 }
136 GetWidget()->Close();
137 }
138
RecordUninstallResultHistogram(UninstallResult result)139 void CrostiniUninstallerView::RecordUninstallResultHistogram(
140 UninstallResult result) {
141 // Prevent multiple results being logged for a given uninstall flow. This
142 // happens because Cancel is always called, either due to the user cancelling
143 // or the dialog being closed. The simplest way to prevent metrics being
144 // erroneously logged for user cancellation is to only record the first
145 // metric.
146 if (has_logged_result_)
147 return;
148
149 base::UmaHistogramEnumeration(kCrostiniUninstallResultHistogram, result,
150 UninstallResult::kCount);
151 has_logged_result_ = true;
152 }
153