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