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