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/chromeos/crostini/crostini_util.h"
6 
7 #include <utility>
8 
9 #include "base/bind.h"
10 #include "base/callback.h"
11 #include "base/callback_helpers.h"
12 #include "base/feature_list.h"
13 #include "base/files/file_path.h"
14 #include "base/metrics/histogram_functions.h"
15 #include "base/no_destructor.h"
16 #include "base/strings/strcat.h"
17 #include "base/task/post_task.h"
18 #include "base/timer/timer.h"
19 #include "chrome/browser/chromeos/crostini/crostini_features.h"
20 #include "chrome/browser/chromeos/crostini/crostini_installer.h"
21 #include "chrome/browser/chromeos/crostini/crostini_manager.h"
22 #include "chrome/browser/chromeos/crostini/crostini_mime_types_service.h"
23 #include "chrome/browser/chromeos/crostini/crostini_mime_types_service_factory.h"
24 #include "chrome/browser/chromeos/crostini/crostini_pref_names.h"
25 #include "chrome/browser/chromeos/crostini/crostini_terminal.h"
26 #include "chrome/browser/chromeos/file_manager/path_util.h"
27 #include "chrome/browser/chromeos/guest_os/guest_os_registry_service.h"
28 #include "chrome/browser/chromeos/guest_os/guest_os_registry_service_factory.h"
29 #include "chrome/browser/chromeos/guest_os/guest_os_share_path.h"
30 #include "chrome/browser/chromeos/profiles/profile_helper.h"
31 #include "chrome/browser/chromeos/virtual_machines/virtual_machines_util.h"
32 #include "chrome/browser/profiles/profile.h"
33 #include "chrome/browser/ui/ash/launcher/app_service/app_service_app_window_crostini_tracker.h"
34 #include "chrome/browser/ui/ash/launcher/app_service/app_service_app_window_launcher_controller.h"
35 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
36 #include "chrome/browser/ui/ash/launcher/shelf_spinner_controller.h"
37 #include "chrome/browser/ui/ash/launcher/shelf_spinner_item_controller.h"
38 #include "chrome/browser/ui/browser.h"
39 #include "chrome/browser/ui/browser_window.h"
40 #include "chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_dialog.h"
41 #include "chrome/common/chrome_features.h"
42 #include "chrome/grit/generated_resources.h"
43 #include "chromeos/constants/chromeos_features.h"
44 #include "components/prefs/pref_service.h"
45 #include "components/user_manager/user.h"
46 #include "google_apis/gaia/gaia_auth_util.h"
47 #include "ui/base/l10n/l10n_util.h"
48 #include "ui/base/l10n/time_format.h"
49 
50 namespace crostini {
51 
52 // We use an arbitrary well-formed extension id for the Terminal app, this
53 // is equal to GenerateId("Terminal").
54 const char kCrostiniDeletedTerminalId[] = "oajcgpnkmhaalajejhlfpacbiokdnnfe";
55 // web_app::GenerateAppIdFromURL(
56 //     GURL("chrome-untrusted://terminal/html/terminal.html"))
57 const char kCrostiniTerminalSystemAppId[] = "fhicihalidkgcimdmhpohldehjmcabcf";
58 
59 const char kCrostiniDefaultVmName[] = "termina";
60 const char kCrostiniDefaultContainerName[] = "penguin";
61 const char kCrostiniDefaultUsername[] = "emperor";
62 // In order to be compatible with sync folder id must match standard.
63 // Generated using crx_file::id_util::GenerateId("LinuxAppsFolder")
64 const char kCrostiniFolderId[] = "ddolnhmblagmcagkedkbfejapapdimlk";
65 const char kCrostiniDefaultImageServerUrl[] =
66     "https://storage.googleapis.com/cros-containers/%d";
67 const char kCrostiniStretchImageAlias[] = "debian/stretch";
68 const char kCrostiniBusterImageAlias[] = "debian/buster";
69 const char kCrostiniDlcName[] = "termina-dlc";
70 
71 const base::FilePath::CharType kHomeDirectory[] = FILE_PATH_LITERAL("/home");
72 
73 namespace {
74 
75 constexpr char kCrostiniAppLaunchHistogram[] = "Crostini.AppLaunch";
76 constexpr char kCrostiniAppLaunchResultHistogram[] = "Crostini.AppLaunchResult";
77 constexpr char kCrostiniAppNamePrefix[] = "_crostini_";
78 constexpr int64_t kDelayBeforeSpinnerMs = 400;
79 
80 // These values are persisted to logs. Entries should not be renumbered and
81 // numeric values should never be reused.
82 enum class CrostiniAppLaunchAppType {
83   // An app which isn't in the CrostiniAppRegistry. This shouldn't happen.
84   kUnknownApp = 0,
85 
86   // The main terminal app.
87   kTerminal = 1,
88 
89   // An app for which there is something in the CrostiniAppRegistry.
90   kRegisteredApp = 2,
91 
92   kCount
93 };
94 
RecordAppLaunchHistogram(CrostiniAppLaunchAppType app_type)95 void RecordAppLaunchHistogram(CrostiniAppLaunchAppType app_type) {
96   base::UmaHistogramEnumeration(kCrostiniAppLaunchHistogram, app_type,
97                                 CrostiniAppLaunchAppType::kCount);
98 }
99 
RecordAppLaunchResultHistogram(crostini::CrostiniResult reason)100 void RecordAppLaunchResultHistogram(crostini::CrostiniResult reason) {
101   base::UmaHistogramEnumeration(kCrostiniAppLaunchResultHistogram, reason);
102 }
103 
OnApplicationLaunched(const std::string & app_id,crostini::CrostiniSuccessCallback callback,const crostini::CrostiniResult failure_result,bool success,const std::string & failure_reason)104 void OnApplicationLaunched(const std::string& app_id,
105                            crostini::CrostiniSuccessCallback callback,
106                            const crostini::CrostiniResult failure_result,
107                            bool success,
108                            const std::string& failure_reason) {
109   // Remove the spinner. Controller doesn't exist in tests.
110   // TODO(timloh): Consider also displaying a notification for failure.
111   if (auto* chrome_controller = ChromeLauncherController::instance()) {
112     chrome_controller->GetShelfSpinnerController()->CloseSpinner(app_id);
113   }
114   RecordAppLaunchResultHistogram(success ? crostini::CrostiniResult::SUCCESS
115                                          : failure_result);
116   std::move(callback).Run(success, failure_reason);
117 }
118 
OnLaunchFailed(const std::string & app_id,crostini::CrostiniSuccessCallback callback,const std::string & failure_reason,crostini::CrostiniResult result=crostini::CrostiniResult::UNKNOWN_ERROR)119 void OnLaunchFailed(
120     const std::string& app_id,
121     crostini::CrostiniSuccessCallback callback,
122     const std::string& failure_reason,
123     crostini::CrostiniResult result = crostini::CrostiniResult::UNKNOWN_ERROR) {
124   OnApplicationLaunched(app_id, std::move(callback), result, false,
125                         failure_reason);
126 }
127 
OnSharePathForLaunchApplication(Profile * profile,const std::string & app_id,guest_os::GuestOsRegistryService::Registration registration,int64_t display_id,const std::vector<std::string> & args,crostini::CrostiniSuccessCallback callback,bool success,const std::string & failure_reason)128 void OnSharePathForLaunchApplication(
129     Profile* profile,
130     const std::string& app_id,
131     guest_os::GuestOsRegistryService::Registration registration,
132     int64_t display_id,
133     const std::vector<std::string>& args,
134     crostini::CrostiniSuccessCallback callback,
135     bool success,
136     const std::string& failure_reason) {
137   if (!success) {
138     return OnLaunchFailed(
139         app_id, std::move(callback),
140         "failed to share paths to launch " + app_id + ":" + failure_reason);
141   }
142   const crostini::ContainerId container_id(registration.VmName(),
143                                            registration.ContainerName());
144   if (app_id == kCrostiniTerminalSystemAppId) {
145     // Use first file as 'cwd'.
146     std::string cwd = !args.empty() ? args[0] : "";
147     if (!LaunchTerminal(profile, display_id, container_id, cwd)) {
148       return OnLaunchFailed(app_id, std::move(callback),
149                             "failed to launch terminal");
150     }
151     return OnApplicationLaunched(app_id, std::move(callback),
152                                  crostini::CrostiniResult::SUCCESS, true, "");
153   }
154   crostini::CrostiniManager::GetForProfile(profile)->LaunchContainerApplication(
155       container_id, registration.DesktopFileId(), args, registration.IsScaled(),
156       base::BindOnce(OnApplicationLaunched, app_id, std::move(callback),
157                      crostini::CrostiniResult::UNKNOWN_ERROR));
158 }
159 
LaunchApplication(Profile * profile,const std::string & app_id,guest_os::GuestOsRegistryService::Registration registration,int64_t display_id,const std::vector<LaunchArg> & args,crostini::CrostiniSuccessCallback callback)160 void LaunchApplication(
161     Profile* profile,
162     const std::string& app_id,
163     guest_os::GuestOsRegistryService::Registration registration,
164     int64_t display_id,
165     const std::vector<LaunchArg>& args,
166     crostini::CrostiniSuccessCallback callback) {
167   ChromeLauncherController* chrome_launcher_controller =
168       ChromeLauncherController::instance();
169   DCHECK(chrome_launcher_controller);
170 
171   AppServiceAppWindowLauncherController* app_service_controller =
172       chrome_launcher_controller->app_service_app_window_controller();
173   DCHECK(app_service_controller);
174   app_service_controller->app_service_crostini_tracker()->OnAppLaunchRequested(
175       app_id, display_id);
176 
177   // Share any paths not in crostini.  The user will see the spinner while this
178   // is happening.
179   std::vector<base::FilePath> paths_to_share;
180   std::vector<std::string> launch_args;
181   launch_args.reserve(args.size());
182   for (const auto& arg : args) {
183     if (absl::holds_alternative<std::string>(arg)) {
184       launch_args.push_back(absl::get<std::string>(arg));
185       continue;
186     }
187     const storage::FileSystemURL& url = absl::get<storage::FileSystemURL>(arg);
188     base::FilePath path;
189     if (!file_manager::util::ConvertFileSystemURLToPathInsideCrostini(
190             profile, url, &path)) {
191       return OnLaunchFailed(
192           app_id, std::move(callback),
193           "Cannot share file with crostini: " + url.DebugString());
194     }
195     if (url.mount_filesystem_id() !=
196         file_manager::util::GetCrostiniMountPointName(profile)) {
197       paths_to_share.push_back(url.path());
198     }
199     launch_args.push_back(path.value());
200   }
201 
202   if (paths_to_share.empty()) {
203     OnSharePathForLaunchApplication(profile, app_id, std::move(registration),
204                                     display_id, std::move(launch_args),
205                                     std::move(callback), true, "");
206   } else {
207     guest_os::GuestOsSharePath::GetForProfile(profile)->SharePaths(
208         registration.VmName(), std::move(paths_to_share), /*persist=*/false,
209         base::BindOnce(OnSharePathForLaunchApplication, profile, app_id,
210                        std::move(registration), display_id,
211                        std::move(launch_args), std::move(callback)));
212   }
213 }
214 
215 }  // namespace
216 
ContainerId(std::string vm_name,std::string container_name)217 ContainerId::ContainerId(std::string vm_name,
218                          std::string container_name) noexcept
219     : vm_name(std::move(vm_name)), container_name(std::move(container_name)) {}
220 
operator <(const ContainerId & lhs,const ContainerId & rhs)221 bool operator<(const ContainerId& lhs, const ContainerId& rhs) noexcept {
222   const auto result = lhs.vm_name.compare(rhs.vm_name);
223   return result < 0 || (result == 0 && lhs.container_name < rhs.container_name);
224 }
225 
operator ==(const ContainerId & lhs,const ContainerId & rhs)226 bool operator==(const ContainerId& lhs, const ContainerId& rhs) noexcept {
227   return lhs.vm_name == rhs.vm_name && lhs.container_name == rhs.container_name;
228 }
229 
operator <<(std::ostream & ostream,const ContainerId & container_id)230 std::ostream& operator<<(std::ostream& ostream,
231                          const ContainerId& container_id) {
232   return ostream << "(vm: \"" << container_id.vm_name << "\" container: \""
233                  << container_id.container_name << "\")";
234 }
235 
GetDefault()236 ContainerId ContainerId::GetDefault() {
237   return ContainerId(kCrostiniDefaultVmName, kCrostiniDefaultContainerName);
238 }
239 
IsUninstallable(Profile * profile,const std::string & app_id)240 bool IsUninstallable(Profile* profile, const std::string& app_id) {
241   if (!CrostiniFeatures::Get()->IsEnabled(profile) ||
242       app_id == kCrostiniTerminalSystemAppId) {
243     return false;
244   }
245   auto* registry_service =
246       guest_os::GuestOsRegistryServiceFactory::GetForProfile(profile);
247   base::Optional<guest_os::GuestOsRegistryService::Registration> registration =
248       registry_service->GetRegistration(app_id);
249   if (registration)
250     return registration->CanUninstall();
251   return false;
252 }
253 
IsCrostiniRunning(Profile * profile)254 bool IsCrostiniRunning(Profile* profile) {
255   return crostini::CrostiniManager::GetForProfile(profile)->IsVmRunning(
256       kCrostiniDefaultVmName);
257 }
258 
ShouldConfigureDefaultContainer(Profile * profile)259 bool ShouldConfigureDefaultContainer(Profile* profile) {
260   const base::FilePath ansible_playbook_file_path =
261       profile->GetPrefs()->GetFilePath(prefs::kCrostiniAnsiblePlaybookFilePath);
262   bool default_container_configured = profile->GetPrefs()->GetBoolean(
263       prefs::kCrostiniDefaultContainerConfigured);
264   return base::FeatureList::IsEnabled(
265              features::kCrostiniAnsibleInfrastructure) &&
266          !default_container_configured && !ansible_playbook_file_path.empty();
267 }
268 
ShouldAllowContainerUpgrade(Profile * profile)269 bool ShouldAllowContainerUpgrade(Profile* profile) {
270   return CrostiniFeatures::Get()->IsContainerUpgradeUIAllowed(profile) &&
271          crostini::CrostiniManager::GetForProfile(profile)
272              ->IsContainerUpgradeable(ContainerId(
273                  kCrostiniDefaultVmName, kCrostiniDefaultContainerName));
274 }
275 
AddSpinner(crostini::CrostiniManager::RestartId restart_id,const std::string & app_id,Profile * profile)276 void AddSpinner(crostini::CrostiniManager::RestartId restart_id,
277                 const std::string& app_id,
278                 Profile* profile) {
279   ChromeLauncherController* chrome_controller =
280       ChromeLauncherController::instance();
281   if (chrome_controller &&
282       crostini::CrostiniManager::GetForProfile(profile)->IsRestartPending(
283           restart_id)) {
284     chrome_controller->GetShelfSpinnerController()->AddSpinnerToShelf(
285         app_id, std::make_unique<ShelfSpinnerItemController>(app_id));
286   }
287 }
288 
MaybeShowCrostiniDialogBeforeLaunch(Profile * profile,CrostiniResult result)289 bool MaybeShowCrostiniDialogBeforeLaunch(Profile* profile,
290                                          CrostiniResult result) {
291   if (result == CrostiniResult::OFFLINE_WHEN_UPGRADE_REQUIRED ||
292       result == CrostiniResult::LOAD_COMPONENT_FAILED) {
293     ShowCrostiniUpdateComponentView(profile, CrostiniUISurface::kAppList);
294     VLOG(1) << "Update Component dialog";
295     return true;
296   }
297   return false;
298 }
299 
LaunchCrostiniAppImpl(Profile * profile,const std::string & app_id,guest_os::GuestOsRegistryService::Registration registration,int64_t display_id,const std::vector<LaunchArg> & args,CrostiniSuccessCallback callback)300 void LaunchCrostiniAppImpl(
301     Profile* profile,
302     const std::string& app_id,
303     guest_os::GuestOsRegistryService::Registration registration,
304     int64_t display_id,
305     const std::vector<LaunchArg>& args,
306     CrostiniSuccessCallback callback) {
307   auto* crostini_manager = crostini::CrostiniManager::GetForProfile(profile);
308   auto* registry_service =
309       guest_os::GuestOsRegistryServiceFactory::GetForProfile(profile);
310   // Store these as we move |registration| into LaunchContainerApplication().
311   const ContainerId container_id(registration.VmName(),
312                                  registration.ContainerName());
313 
314   if (app_id == kCrostiniTerminalSystemAppId) {
315     // If terminal is launched with a 'cwd' file, we may need to launch the VM
316     // and share the path before launching terminal.
317     bool requires_share = false;
318     base::FilePath cwd;
319     if (!args.empty() &&
320         absl::holds_alternative<storage::FileSystemURL>(args[0])) {
321       const storage::FileSystemURL& url =
322           absl::get<storage::FileSystemURL>(args[0]);
323       if (url.mount_filesystem_id() !=
324           file_manager::util::GetCrostiniMountPointName(profile)) {
325         requires_share = true;
326       } else {
327         file_manager::util::ConvertFileSystemURLToPathInsideCrostini(profile,
328                                                                      url, &cwd);
329       }
330     }
331 
332     if (!requires_share) {
333       RecordAppLaunchHistogram(CrostiniAppLaunchAppType::kTerminal);
334       if (!LaunchTerminal(profile, display_id, container_id, cwd.value())) {
335         RecordAppLaunchResultHistogram(crostini::CrostiniResult::UNKNOWN_ERROR);
336         return std::move(callback).Run(false, "failed to launch terminal");
337       }
338       RecordAppLaunchResultHistogram(crostini::CrostiniResult::SUCCESS);
339       return std::move(callback).Run(true, "");
340     }
341   }
342 
343   RecordAppLaunchHistogram(CrostiniAppLaunchAppType::kRegisteredApp);
344 
345   // Update the last launched time and Termina version.
346   registry_service->AppLaunched(app_id);
347   crostini_manager->UpdateLaunchMetricsForEnterpriseReporting();
348 
349   auto restart_id = crostini_manager->RestartCrostini(
350       container_id,
351       base::BindOnce(
352           [](Profile* profile, const std::string& app_id,
353              guest_os::GuestOsRegistryService::Registration registration,
354              int64_t display_id, const std::vector<LaunchArg> args,
355              crostini::CrostiniSuccessCallback callback,
356              crostini::CrostiniResult result) {
357             if (result != crostini::CrostiniResult::SUCCESS) {
358               OnLaunchFailed(app_id, std::move(callback),
359                              base::StringPrintf(
360                                  "crostini restart to launch app %s failed: %d",
361                                  app_id.c_str(), result),
362                              result);
363               if (crostini::MaybeShowCrostiniDialogBeforeLaunch(profile,
364                                                                 result)) {
365                 VLOG(1) << "Crostini restart blocked by dialog";
366               }
367               return;
368             }
369 
370             LaunchApplication(profile, app_id, std::move(registration),
371                               display_id, args, std::move(callback));
372           },
373           profile, app_id, std::move(registration), display_id, args,
374           std::move(callback)));
375 
376   base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
377       FROM_HERE, base::BindOnce(&AddSpinner, restart_id, app_id, profile),
378       base::TimeDelta::FromMilliseconds(kDelayBeforeSpinnerMs));
379 }
380 
LaunchCrostiniApp(Profile * profile,const std::string & app_id,int64_t display_id,const std::vector<LaunchArg> & args,CrostiniSuccessCallback callback)381 void LaunchCrostiniApp(Profile* profile,
382                        const std::string& app_id,
383                        int64_t display_id,
384                        const std::vector<LaunchArg>& args,
385                        CrostiniSuccessCallback callback) {
386   // Policies can change under us, and crostini may now be forbidden.
387   if (!CrostiniFeatures::Get()->IsUIAllowed(profile)) {
388     return std::move(callback).Run(false, "Crostini UI not allowed");
389   }
390   auto* crostini_manager = crostini::CrostiniManager::GetForProfile(profile);
391 
392   // At this point, we know that Crostini UI is allowed.
393   if (app_id == kCrostiniTerminalSystemAppId &&
394       !CrostiniFeatures::Get()->IsEnabled(profile)) {
395     crostini::CrostiniInstaller::GetForProfile(profile)->ShowDialog(
396         CrostiniUISurface::kAppList);
397     return std::move(callback).Run(false, "Crostini not installed");
398   }
399 
400   auto* registry_service =
401       guest_os::GuestOsRegistryServiceFactory::GetForProfile(profile);
402   base::Optional<guest_os::GuestOsRegistryService::Registration> registration =
403       registry_service->GetRegistration(app_id);
404   if (!registration) {
405     RecordAppLaunchHistogram(CrostiniAppLaunchAppType::kUnknownApp);
406     return std::move(callback).Run(
407         false, "LaunchCrostiniApp called with an unknown app_id: " + app_id);
408   }
409 
410   if (crostini_manager->IsUncleanStartup()) {
411     // Prompt for user-restart.
412     return ShowCrostiniRecoveryView(
413         profile, crostini::CrostiniUISurface::kAppList, app_id, display_id,
414         args, std::move(callback));
415   }
416 
417   if (crostini_manager->GetCrostiniDialogStatus(DialogType::UPGRADER)) {
418     // Reshow the existing dialog.
419     chromeos::CrostiniUpgraderDialog::Reshow();
420     VLOG(1) << "Reshowing upgrade dialog";
421     std::move(callback).Run(
422         false, "LaunchCrostiniApp called while upgrade dialog showing");
423     return;
424   }
425   LaunchCrostiniAppImpl(profile, app_id, std::move(*registration), display_id,
426                         args, std::move(callback));
427 }
428 
CryptohomeIdForProfile(Profile * profile)429 std::string CryptohomeIdForProfile(Profile* profile) {
430   std::string id = chromeos::ProfileHelper::GetUserIdHashFromProfile(profile);
431   // Empty id means we're running in a test.
432   return id.empty() ? "test" : id;
433 }
434 
DefaultContainerUserNameForProfile(Profile * profile)435 std::string DefaultContainerUserNameForProfile(Profile* profile) {
436   const user_manager::User* user =
437       chromeos::ProfileHelper::Get()->GetUserByProfile(profile);
438   if (!user) {
439     return kCrostiniDefaultUsername;
440   }
441   std::string username = user->GetAccountName(/*use_display_email=*/false);
442 
443   // For gmail accounts, dots are already stripped away in the canonical
444   // username. But for other accounts (e.g. managedchrome), we need to do this
445   // manually.
446   std::string::size_type index;
447   while ((index = username.find('.')) != std::string::npos) {
448     username.erase(index, 1);
449   }
450 
451   return username;
452 }
453 
ContainerChromeOSBaseDirectory()454 base::FilePath ContainerChromeOSBaseDirectory() {
455   return base::FilePath("/mnt/chromeos");
456 }
457 
AppNameFromCrostiniAppId(const std::string & id)458 std::string AppNameFromCrostiniAppId(const std::string& id) {
459   return kCrostiniAppNamePrefix + id;
460 }
461 
CrostiniAppIdFromAppName(const std::string & app_name)462 base::Optional<std::string> CrostiniAppIdFromAppName(
463     const std::string& app_name) {
464   if (!base::StartsWith(app_name, kCrostiniAppNamePrefix,
465                         base::CompareCase::SENSITIVE)) {
466     return base::nullopt;
467   }
468   return app_name.substr(strlen(kCrostiniAppNamePrefix));
469 }
470 
AddNewLxdContainerToPrefs(Profile * profile,const ContainerId & container_id)471 void AddNewLxdContainerToPrefs(Profile* profile,
472                                const ContainerId& container_id) {
473   auto* pref_service = profile->GetPrefs();
474 
475   base::Value new_container(base::Value::Type::DICTIONARY);
476   new_container.SetKey(prefs::kVmKey, base::Value(container_id.vm_name));
477   new_container.SetKey(prefs::kContainerKey,
478                        base::Value(container_id.container_name));
479   new_container.SetIntKey(prefs::kContainerOsVersionKey,
480                           static_cast<int>(ContainerOsVersion::kUnknown));
481 
482   ListPrefUpdate updater(pref_service, crostini::prefs::kCrostiniContainers);
483   updater->Append(std::move(new_container));
484 }
485 
486 namespace {
487 
MatchContainerDict(const base::Value & dict,const ContainerId & container_id)488 bool MatchContainerDict(const base::Value& dict,
489                         const ContainerId& container_id) {
490   const std::string* vm_name = dict.FindStringKey(prefs::kVmKey);
491   const std::string* container_name = dict.FindStringKey(prefs::kContainerKey);
492   return (vm_name && *vm_name == container_id.vm_name) &&
493          (container_name && *container_name == container_id.container_name);
494 }
495 
496 }  // namespace
497 
RemoveLxdContainerFromPrefs(Profile * profile,const ContainerId & container_id)498 void RemoveLxdContainerFromPrefs(Profile* profile,
499                                  const ContainerId& container_id) {
500   auto* pref_service = profile->GetPrefs();
501   ListPrefUpdate updater(pref_service, crostini::prefs::kCrostiniContainers);
502   updater->EraseListIter(
503       std::find_if(updater->GetList().begin(), updater->GetList().end(),
504                    [&](const auto& dict) {
505                      return MatchContainerDict(dict, container_id);
506                    }));
507 
508   guest_os::GuestOsRegistryServiceFactory::GetForProfile(profile)
509       ->ClearApplicationList(guest_os::GuestOsRegistryService::VmType::
510                                  ApplicationList_VmType_TERMINA,
511                              container_id.vm_name, container_id.container_name);
512   CrostiniMimeTypesServiceFactory::GetForProfile(profile)->ClearMimeTypes(
513       container_id.vm_name, container_id.container_name);
514 }
515 
GetContainerPrefValue(Profile * profile,const ContainerId & container_id,const std::string & key)516 const base::Value* GetContainerPrefValue(Profile* profile,
517                                          const ContainerId& container_id,
518                                          const std::string& key) {
519   const base::ListValue* containers =
520       profile->GetPrefs()->GetList(crostini::prefs::kCrostiniContainers);
521   if (!containers) {
522     return nullptr;
523   }
524   auto it = std::find_if(
525       containers->begin(), containers->end(),
526       [&](const auto& dict) { return MatchContainerDict(dict, container_id); });
527   if (it == containers->end()) {
528     return nullptr;
529   }
530   return it->FindKey(key);
531 }
532 
UpdateContainerPref(Profile * profile,const ContainerId & container_id,const std::string & key,base::Value value)533 void UpdateContainerPref(Profile* profile,
534                          const ContainerId& container_id,
535                          const std::string& key,
536                          base::Value value) {
537   ListPrefUpdate updater(profile->GetPrefs(),
538                          crostini::prefs::kCrostiniContainers);
539   auto it = std::find_if(
540       updater->GetList().begin(), updater->GetList().end(),
541       [&](const auto& dict) { return MatchContainerDict(dict, container_id); });
542   if (it != updater->GetList().end()) {
543     it->SetKey(key, std::move(value));
544   }
545 }
546 
GetTimeRemainingMessage(base::TimeTicks start,int percent)547 base::string16 GetTimeRemainingMessage(base::TimeTicks start, int percent) {
548   // Only estimate once we've spent at least 3 seconds OR gotten 10% of the way
549   // through.
550   constexpr base::TimeDelta kMinTimeForEstimate =
551       base::TimeDelta::FromSeconds(3);
552   constexpr base::TimeDelta kTimeDeltaZero = base::TimeDelta::FromSeconds(0);
553   constexpr int kMinPercentForEstimate = 10;
554   base::TimeDelta elapsed = base::TimeTicks::Now() - start;
555   if ((elapsed >= kMinTimeForEstimate && percent > 0) ||
556       (percent >= kMinPercentForEstimate && elapsed > kTimeDeltaZero)) {
557     base::TimeDelta total_time_expected = (elapsed * 100) / percent;
558     base::TimeDelta time_remaining = total_time_expected - elapsed;
559     return ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_REMAINING,
560                                   ui::TimeFormat::LENGTH_SHORT, time_remaining);
561   } else {
562     return l10n_util::GetStringUTF16(
563         IDS_CROSTINI_NOTIFICATION_OPERATION_STARTING);
564   }
565 }
566 
567 
DefaultContainerId()568 const ContainerId& DefaultContainerId() {
569   static const base::NoDestructor<ContainerId> container_id(
570       kCrostiniDefaultVmName, kCrostiniDefaultContainerName);
571   return *container_id;
572 }
573 }  // namespace crostini
574