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/web_applications/system_web_app_manager.h"
6 
7 #include <algorithm>
8 #include <string>
9 #include <tuple>
10 #include <utility>
11 #include <vector>
12 
13 #include "base/bind.h"
14 #include "base/command_line.h"
15 #include "base/metrics/histogram_functions.h"
16 #include "base/run_loop.h"
17 #include "base/stl_util.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/version.h"
20 #include "chrome/browser/browser_process.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/web_applications/components/app_registrar.h"
23 #include "chrome/browser/web_applications/components/app_registry_controller.h"
24 #include "chrome/browser/web_applications/components/external_install_options.h"
25 #include "chrome/browser/web_applications/components/os_integration_manager.h"
26 #include "chrome/browser/web_applications/components/web_app_constants.h"
27 #include "chrome/browser/web_applications/components/web_app_install_utils.h"
28 #include "chrome/browser/web_applications/components/web_app_ui_manager.h"
29 #include "chrome/browser/web_applications/components/web_app_utils.h"
30 #include "chrome/browser/web_applications/components/web_application_info.h"
31 #include "chrome/common/chrome_features.h"
32 #include "chrome/common/pref_names.h"
33 #include "chrome/common/webui_url_constants.h"
34 #include "chrome/grit/generated_resources.h"
35 #include "components/pref_registry/pref_registry_syncable.h"
36 #include "components/prefs/pref_service.h"
37 #include "components/user_manager/user_manager.h"
38 #include "components/version_info/version_info.h"
39 #include "content/public/browser/navigation_handle.h"
40 #include "content/public/browser/url_data_source.h"
41 #include "content/public/common/content_switches.h"
42 #include "content/public/common/url_constants.h"
43 #include "ui/base/l10n/l10n_util.h"
44 
45 #if defined(OS_CHROMEOS)
46 #include "ash/public/cpp/app_list/internal_app_id_constants.h"
47 #include "base/values.h"
48 #include "chrome/browser/chromeos/policy/system_features_disable_list_policy_handler.h"
49 #include "chrome/browser/chromeos/web_applications/camera_system_web_app_info.h"
50 #include "chrome/browser/chromeos/web_applications/connectivity_diagnostics_system_web_app_info.h"
51 #include "chrome/browser/chromeos/web_applications/default_web_app_ids.h"
52 #include "chrome/browser/chromeos/web_applications/diagnostics_system_web_app_info.h"
53 #include "chrome/browser/chromeos/web_applications/help_app_web_app_info.h"
54 #include "chrome/browser/chromeos/web_applications/media_web_app_info.h"
55 #include "chrome/browser/chromeos/web_applications/os_settings_web_app_info.h"
56 #include "chrome/browser/chromeos/web_applications/print_management_web_app_info.h"
57 #include "chrome/browser/chromeos/web_applications/scanning_system_web_app_info.h"
58 #include "chrome/browser/chromeos/web_applications/terminal_source.h"
59 #include "chrome/browser/chromeos/web_applications/terminal_system_web_app_info.h"
60 #include "chromeos/components/camera_app_ui/url_constants.h"
61 #include "chromeos/components/connectivity_diagnostics/url_constants.h"
62 #include "chromeos/components/help_app_ui/url_constants.h"
63 #include "chromeos/components/media_app_ui/url_constants.h"
64 #include "chromeos/constants/chromeos_features.h"
65 #include "chromeos/constants/chromeos_pref_names.h"
66 #include "chromeos/strings/grit/chromeos_strings.h"
67 #include "components/policy/core/common/policy_pref_names.h"
68 #include "extensions/common/constants.h"
69 
70 #if !defined(OFFICIAL_BUILD)
71 #include "chrome/browser/chromeos/web_applications/file_manager_web_app_info.h"
72 #include "chrome/browser/chromeos/web_applications/sample_system_web_app_info.h"
73 #include "chrome/browser/chromeos/web_applications/telemetry_extension_web_app_info.h"
74 #endif  // !defined(OFFICIAL_BUILD)
75 
76 #endif  // defined(OS_CHROMEOS)
77 
78 namespace web_app {
79 
80 namespace {
81 
82 // Copy the origin trial name from runtime_enabled_features.json5, to avoid
83 // complex dependencies.
84 const char kFileHandlingOriginTrial[] = "FileHandling";
85 
86 // Number of attempts to install a given version & locale of the SWAs before
87 // bailing out.
88 const int kInstallFailureAttempts = 3;
89 
90 // Use #if defined to avoid compiler error on unused function.
91 #if defined(OS_CHROMEOS)
92 
93 // A convenience method to create OriginTrialsMap. Note, we only support simple
94 // cases for chrome:// and chrome-untrusted:// URLs. We don't support complex
95 // cases such as about:blank (which inherits origins from the embedding frame).
GetOrigin(const char * url)96 url::Origin GetOrigin(const char* url) {
97   GURL gurl = GURL(url);
98   DCHECK(gurl.is_valid());
99 
100   url::Origin origin = url::Origin::Create(gurl);
101   DCHECK(!origin.opaque());
102 
103   return origin;
104 }
105 #endif  // OS_CHROMEOS
106 
CreateSystemWebApps(Profile * profile)107 base::flat_map<SystemAppType, SystemAppInfo> CreateSystemWebApps(
108     Profile* profile) {
109   base::flat_map<SystemAppType, SystemAppInfo> infos;
110 // TODO(calamity): Split this into per-platform functions.
111 #if defined(OS_CHROMEOS)
112   // SystemAppInfo's |name| field should be defined. These names are persisted
113   // to logs and should not be renamed.
114   // If new names are added, update tool/metrics/histograms/histograms.xml:
115   // "SystemWebAppName"
116   if (SystemWebAppManager::IsAppEnabled(SystemAppType::CAMERA)) {
117     infos.emplace(
118         SystemAppType::CAMERA,
119         SystemAppInfo(
120             "Camera", GURL("chrome://camera-app/views/main.html"),
121             base::BindRepeating(&CreateWebAppInfoForCameraSystemWebApp)));
122     if (!profile->GetPrefs()->GetBoolean(
123             chromeos::prefs::kHasCameraAppMigratedToSWA)) {
124       infos.at(SystemAppType::CAMERA).uninstall_and_replace = {
125           extension_misc::kCameraAppId};
126     }
127     // We need "FileHandling" to use File Handling API to set launch directory.
128     // And we need "NativeFileSystem2" to use Native File System API.
129     infos.at(SystemAppType::CAMERA).enabled_origin_trials =
130         OriginTrialsMap({{GetOrigin("chrome://camera-app"),
131                           {"FileHandling", "NativeFileSystem2"}}});
132     infos.at(SystemAppType::CAMERA).capture_navigations = true;
133   }
134 
135   if (SystemWebAppManager::IsAppEnabled(SystemAppType::DIAGNOSTICS)) {
136     infos.emplace(
137         SystemAppType::DIAGNOSTICS,
138         SystemAppInfo(
139             "Diagnostics", GURL("chrome://diagnostics"),
140             base::BindRepeating(&CreateWebAppInfoForDiagnosticsSystemWebApp)));
141   }
142 
143   infos.emplace(SystemAppType::SETTINGS,
144                 SystemAppInfo("OSSettings", GURL(chrome::kChromeUISettingsURL),
145                               base::BindRepeating(
146                                   &CreateWebAppInfoForOSSettingsSystemWebApp)));
147   infos.at(SystemAppType::SETTINGS).uninstall_and_replace = {
148       chromeos::default_web_apps::kSettingsAppId, ash::kInternalAppIdSettings};
149   // Large enough to see the heading text "Settings" in the top-left.
150   infos.at(SystemAppType::SETTINGS).minimum_window_size = {300, 100};
151 
152   if (SystemWebAppManager::IsAppEnabled(SystemAppType::TERMINAL)) {
153     infos.emplace(
154         SystemAppType::TERMINAL,
155         SystemAppInfo(
156             "Terminal", GURL(chrome::kChromeUIUntrustedTerminalURL),
157             base::BindRepeating(&CreateWebAppInfoForTerminalSystemWebApp)));
158     infos.at(SystemAppType::TERMINAL).single_window = false;
159   }
160 
161   if (SystemWebAppManager::IsAppEnabled(SystemAppType::HELP)) {
162     infos.emplace(
163         SystemAppType::HELP,
164         SystemAppInfo("Help", GURL("chrome://help-app/pwa.html"),
165                       base::BindRepeating(&CreateWebAppInfoForHelpWebApp)));
166     infos.at(SystemAppType::HELP).additional_search_terms = {
167         IDS_GENIUS_APP_NAME, IDS_HELP_APP_PERKS, IDS_HELP_APP_OFFERS};
168     infos.at(SystemAppType::HELP).minimum_window_size = {600, 320};
169     infos.at(SystemAppType::HELP).capture_navigations = true;
170   }
171 
172   if (SystemWebAppManager::IsAppEnabled(SystemAppType::MEDIA)) {
173     infos.emplace(
174         SystemAppType::MEDIA,
175         SystemAppInfo("Media", GURL("chrome://media-app/pwa.html"),
176                       base::BindRepeating(&CreateWebAppInfoForMediaWebApp)));
177     infos.at(SystemAppType::MEDIA).include_launch_directory = true;
178     infos.at(SystemAppType::MEDIA).show_in_launcher = false;
179     infos.at(SystemAppType::MEDIA).show_in_search = false;
180     infos.at(SystemAppType::MEDIA).enabled_origin_trials =
181         OriginTrialsMap({{GetOrigin("chrome://media-app"),
182                           {"FileHandling", "NativeFileSystem2"}}});
183   }
184 
185   if (SystemWebAppManager::IsAppEnabled(SystemAppType::PRINT_MANAGEMENT)) {
186     infos.emplace(
187         SystemAppType::PRINT_MANAGEMENT,
188         SystemAppInfo(
189             "PrintManagement", GURL("chrome://print-management/pwa.html"),
190             base::BindRepeating(&CreateWebAppInfoForPrintManagementApp)));
191     infos.at(SystemAppType::PRINT_MANAGEMENT).show_in_launcher = false;
192     infos.at(SystemAppType::PRINT_MANAGEMENT).minimum_window_size = {600, 320};
193   }
194 
195   if (SystemWebAppManager::IsAppEnabled(SystemAppType::SCANNING)) {
196     infos.emplace(SystemAppType::SCANNING,
197                   SystemAppInfo("Scanning", GURL("chrome://scanning"),
198                                 base::BindRepeating(
199                                     &CreateWebAppInfoForScanningSystemWebApp)));
200   }
201 
202   if (SystemWebAppManager::IsAppEnabled(
203           SystemAppType::CONNECTIVITY_DIAGNOSTICS)) {
204     infos.emplace(
205         SystemAppType::CONNECTIVITY_DIAGNOSTICS,
206         SystemAppInfo(
207             "ConnectivityDiagnostics",
208             GURL(chromeos::kChromeUIConnectivityDiagnosticsUrl),
209             base::BindRepeating(
210                 &CreateWebAppInfoForConnectivityDiagnosticsSystemWebApp)));
211   }
212 
213 #if !defined(OFFICIAL_BUILD)
214   if (SystemWebAppManager::IsAppEnabled(SystemAppType::TELEMETRY)) {
215     infos.emplace(
216         SystemAppType::TELEMETRY,
217         SystemAppInfo(
218             "Telemetry", GURL("chrome://telemetry-extension"),
219             base::BindRepeating(&CreateWebAppInfoForTelemetryExtension)));
220   }
221 
222   if (SystemWebAppManager::IsAppEnabled(SystemAppType::FILE_MANAGER)) {
223     infos.emplace(
224         SystemAppType::FILE_MANAGER,
225         SystemAppInfo("File Manager", GURL("chrome://file-manager"),
226                       base::BindRepeating(&CreateWebAppInfoForFileManager)));
227     infos.at(SystemAppType::FILE_MANAGER).capture_navigations = true;
228     infos.at(SystemAppType::FILE_MANAGER).single_window = false;
229   }
230 
231   infos.emplace(
232       SystemAppType::SAMPLE,
233       SystemAppInfo(
234           "Sample", GURL("chrome://sample-system-web-app/pwa.html"),
235           base::BindRepeating(&CreateWebAppInfoForSampleSystemWebApp)));
236   // Frobulate is the name for Sample Origin Trial API, and has no impact on the
237   // Web App's functionality. Here we use it to demonstrate how to enable origin
238   // trials for a System Web App.
239   infos.at(SystemAppType::SAMPLE).enabled_origin_trials = OriginTrialsMap(
240       {{GetOrigin("chrome://sample-system-web-app"), {"Frobulate"}},
241        {GetOrigin("chrome-untrusted://sample-system-web-app"), {"Frobulate"}}});
242   infos.at(SystemAppType::SAMPLE).capture_navigations = true;
243 #endif  // !defined(OFFICIAL_BUILD)
244 
245 #endif  // OS_CHROMEOS
246 
247   return infos;
248 }
249 
HasSystemWebAppScheme(const GURL & url)250 bool HasSystemWebAppScheme(const GURL& url) {
251   return url.SchemeIs(content::kChromeUIScheme) ||
252          url.SchemeIs(content::kChromeUIUntrustedScheme);
253 }
254 
CreateInstallOptionsForSystemApp(const SystemAppInfo & info,bool force_update,bool is_disabled)255 ExternalInstallOptions CreateInstallOptionsForSystemApp(
256     const SystemAppInfo& info,
257     bool force_update,
258     bool is_disabled) {
259   DCHECK(info.install_url.scheme() == content::kChromeUIScheme ||
260          info.install_url.scheme() == content::kChromeUIUntrustedScheme);
261 
262   ExternalInstallOptions install_options(
263       info.install_url, DisplayMode::kStandalone,
264       ExternalInstallSource::kSystemInstalled);
265   install_options.only_use_app_info_factory = !!info.app_info_factory;
266   install_options.app_info_factory = info.app_info_factory;
267   install_options.add_to_applications_menu = info.show_in_launcher;
268   install_options.add_to_desktop = false;
269   install_options.add_to_quick_launch_bar = false;
270   install_options.add_to_search = info.show_in_search;
271   install_options.add_to_management = false;
272   install_options.is_disabled = is_disabled;
273   install_options.bypass_service_worker_check = true;
274   install_options.force_reinstall = force_update;
275   install_options.uninstall_and_replace = info.uninstall_and_replace;
276 
277   const auto& search_terms = info.additional_search_terms;
278   std::transform(search_terms.begin(), search_terms.end(),
279                  std::back_inserter(install_options.additional_search_terms),
280                  [](int term) { return l10n_util::GetStringUTF8(term); });
281   return install_options;
282 }
283 
GetDisabledSystemWebApps()284 std::set<SystemAppType> GetDisabledSystemWebApps() {
285   std::set<SystemAppType> disabled_system_apps;
286 
287 #if defined(OS_CHROMEOS)
288   PrefService* const local_state = g_browser_process->local_state();
289   if (!local_state)  // Sometimes it's not available in tests.
290     return disabled_system_apps;
291 
292   const base::ListValue* disabled_system_features_pref =
293       local_state->GetList(policy::policy_prefs::kSystemFeaturesDisableList);
294   if (!disabled_system_features_pref)
295     return disabled_system_apps;
296 
297   for (const auto& entry : *disabled_system_features_pref) {
298     switch (entry.GetInt()) {
299       case policy::SystemFeature::CAMERA:
300         disabled_system_apps.insert(SystemAppType::CAMERA);
301         break;
302       case policy::SystemFeature::OS_SETTINGS:
303         disabled_system_apps.insert(SystemAppType::SETTINGS);
304         break;
305       case policy::SystemFeature::SCANNING:
306         disabled_system_apps.insert(SystemAppType::SCANNING);
307         break;
308     }
309   }
310 #endif  // defined(OS_CHROMEOS)
311 
312   return disabled_system_apps;
313 }
314 
315 }  // namespace
316 
SystemAppInfo(const std::string & internal_name,const GURL & install_url)317 SystemAppInfo::SystemAppInfo(const std::string& internal_name,
318                              const GURL& install_url)
319     : internal_name(internal_name), install_url(install_url) {}
320 
SystemAppInfo(const std::string & internal_name,const GURL & install_url,const WebApplicationInfoFactory & app_info_factory)321 SystemAppInfo::SystemAppInfo(const std::string& internal_name,
322                              const GURL& install_url,
323                              const WebApplicationInfoFactory& app_info_factory)
324     : internal_name(internal_name),
325       install_url(install_url),
326       app_info_factory(app_info_factory) {}
327 
328 SystemAppInfo::SystemAppInfo(const SystemAppInfo& other) = default;
329 
330 SystemAppInfo::~SystemAppInfo() = default;
331 
332 // static
333 const char SystemWebAppManager::kInstallResultHistogramName[];
334 const char SystemWebAppManager::kInstallDurationHistogramName[];
335 
336 // static
IsAppEnabled(SystemAppType type)337 bool SystemWebAppManager::IsAppEnabled(SystemAppType type) {
338   if (base::FeatureList::IsEnabled(features::kEnableAllSystemWebApps))
339     return true;
340 
341 #if defined(OS_CHROMEOS)
342   switch (type) {
343     case SystemAppType::SETTINGS:
344       return true;
345     case SystemAppType::CAMERA:
346       return base::FeatureList::IsEnabled(
347                  chromeos::features::kCameraSystemWebApp) &&
348              !user_manager::UserManager::Get()->IsLoggedInAsGuest();
349     case SystemAppType::TERMINAL:
350       return true;
351     case SystemAppType::MEDIA:
352       return base::FeatureList::IsEnabled(chromeos::features::kMediaApp);
353     case SystemAppType::HELP:
354       return true;
355     case SystemAppType::PRINT_MANAGEMENT:
356       return base::FeatureList::IsEnabled(
357           chromeos::features::kPrintJobManagementApp);
358     case SystemAppType::SCANNING:
359       return base::FeatureList::IsEnabled(chromeos::features::kScanningUI);
360     case SystemAppType::DIAGNOSTICS:
361       return base::FeatureList::IsEnabled(chromeos::features::kDiagnosticsApp);
362     case SystemAppType::CONNECTIVITY_DIAGNOSTICS:
363       return base::FeatureList::IsEnabled(
364           chromeos::features::kConnectivityDiagnosticsWebUi);
365 #if !defined(OFFICIAL_BUILD)
366     case SystemAppType::TELEMETRY:
367       return base::FeatureList::IsEnabled(
368           chromeos::features::kTelemetryExtension);
369     case SystemAppType::FILE_MANAGER:
370       return base::FeatureList::IsEnabled(chromeos::features::kFilesSWA);
371     case SystemAppType::SAMPLE:
372       NOTREACHED();
373       return false;
374 #endif  // !defined(OFFICIAL_BUILD)
375   }
376 #else
377   return false;
378 #endif  // OS_CHROMEOS
379 }
380 
SystemWebAppManager(Profile * profile)381 SystemWebAppManager::SystemWebAppManager(Profile* profile)
382     : profile_(profile),
383       on_apps_synchronized_(new base::OneShotEvent()),
384       install_result_per_profile_histogram_name_(
385           std::string(kInstallResultHistogramName) + ".Profiles." +
386           GetProfileCategoryForLogging(profile)),
387       pref_service_(profile_->GetPrefs()) {
388   if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType)) {
389     // Always update in tests.
390     update_policy_ = UpdatePolicy::kAlwaysUpdate;
391 
392     // Populate with real system apps if the test asks for it.
393     if (base::FeatureList::IsEnabled(features::kEnableAllSystemWebApps))
394       system_app_infos_ = CreateSystemWebApps(profile_);
395 
396     return;
397   }
398 
399 #if defined(OFFICIAL_BUILD)
400   // Official builds should trigger updates whenever the version number changes.
401   update_policy_ = UpdatePolicy::kOnVersionChange;
402 #else
403   // Dev builds should update every launch.
404   update_policy_ = UpdatePolicy::kAlwaysUpdate;
405 #endif
406 
407   system_app_infos_ = CreateSystemWebApps(profile_);
408 }
409 
410 SystemWebAppManager::~SystemWebAppManager() = default;
411 
Shutdown()412 void SystemWebAppManager::Shutdown() {
413   shutting_down_ = true;
414 }
415 
SetSubsystems(PendingAppManager * pending_app_manager,AppRegistrar * registrar,AppRegistryController * registry_controller,WebAppUiManager * ui_manager,OsIntegrationManager * os_integration_manager)416 void SystemWebAppManager::SetSubsystems(
417     PendingAppManager* pending_app_manager,
418     AppRegistrar* registrar,
419     AppRegistryController* registry_controller,
420     WebAppUiManager* ui_manager,
421     OsIntegrationManager* os_integration_manager) {
422   pending_app_manager_ = pending_app_manager;
423   registrar_ = registrar;
424   registry_controller_ = registry_controller;
425   ui_manager_ = ui_manager;
426   os_integration_manager_ = os_integration_manager;
427 }
428 
Start()429 void SystemWebAppManager::Start() {
430   const base::TimeTicks install_start_time = base::TimeTicks::Now();
431 
432 #if DCHECK_IS_ON()
433   // Check Origin Trials are defined correctly.
434   for (const auto& type_and_app_info : system_app_infos_) {
435     for (const auto& origin_to_trial_names :
436          type_and_app_info.second.enabled_origin_trials) {
437       // Only allow force enabled origin trials on chrome:// and
438       // chrome-untrusted:// URLs.
439       const auto& scheme = origin_to_trial_names.first.scheme();
440       DCHECK(scheme == content::kChromeUIScheme ||
441              scheme == content::kChromeUIUntrustedScheme);
442     }
443   }
444 
445   // TODO(https://crbug.com/1043843): Find some ways to validate supplied origin
446   // trial names. Ideally, construct them from some static const char*.
447 #endif  // DCHECK_IS_ON()
448 
449 #if defined(OS_CHROMEOS)
450   // Set up terminal data source. Terminal source is needed for install.
451   // TODO(crbug.com/1080384): Move once chrome-untrusted has WebUIControllers.
452   if (SystemWebAppManager::IsAppEnabled(SystemAppType::TERMINAL)) {
453     content::URLDataSource::Add(profile_,
454                                 TerminalSource::ForTerminal(profile_));
455   }
456 #endif  // defined(OS_CHROMEOS)
457 
458   std::vector<ExternalInstallOptions> install_options_list;
459   const bool should_force_install_apps = ShouldForceInstallApps();
460   if (should_force_install_apps) {
461     UpdateLastAttemptedInfo();
462   }
463 
464   const auto disabled_system_apps = GetDisabledSystemWebApps();
465 
466   for (const auto& app : system_app_infos_) {
467     install_options_list.push_back(CreateInstallOptionsForSystemApp(
468         app.second, should_force_install_apps,
469         base::Contains(disabled_system_apps, app.first)));
470   }
471 
472   const bool exceeded_retries = CheckAndIncrementRetryAttempts();
473   if (!exceeded_retries) {
474     pending_app_manager_->SynchronizeInstalledApps(
475         std::move(install_options_list),
476         ExternalInstallSource::kSystemInstalled,
477         base::BindOnce(&SystemWebAppManager::OnAppsSynchronized,
478                        weak_ptr_factory_.GetWeakPtr(),
479                        should_force_install_apps, install_start_time));
480   }
481 #if defined(OS_CHROMEOS)
482   PrefService* const local_state = g_browser_process->local_state();
483   if (local_state) {  // Sometimes it's not available in tests.
484     local_state_pref_change_registrar_.Init(local_state);
485 
486     // Sometimes this function gets called twice in tests.
487     if (!local_state_pref_change_registrar_.IsObserved(
488             policy::policy_prefs::kSystemFeaturesDisableList)) {
489       local_state_pref_change_registrar_.Add(
490           policy::policy_prefs::kSystemFeaturesDisableList,
491           base::Bind(&SystemWebAppManager::OnAppsPolicyChanged,
492                      base::Unretained(this)));
493     }
494   }
495 #endif  // defined(OS_CHROMEOS)
496 }
497 
InstallSystemAppsForTesting()498 void SystemWebAppManager::InstallSystemAppsForTesting() {
499   on_apps_synchronized_.reset(new base::OneShotEvent());
500   system_app_infos_ = CreateSystemWebApps(profile_);
501   Start();
502 
503   // Wait for the System Web Apps to install.
504   base::RunLoop run_loop;
505   on_apps_synchronized().Post(FROM_HERE, run_loop.QuitClosure());
506   run_loop.Run();
507 }
508 
509 const base::flat_map<SystemAppType, SystemAppInfo>&
GetRegisteredSystemAppsForTesting() const510 SystemWebAppManager::GetRegisteredSystemAppsForTesting() const {
511   return system_app_infos_;
512 }
513 
GetAppIdForSystemApp(SystemAppType id) const514 base::Optional<AppId> SystemWebAppManager::GetAppIdForSystemApp(
515     SystemAppType id) const {
516   auto app_url_it = system_app_infos_.find(id);
517 
518   if (app_url_it == system_app_infos_.end())
519     return base::Optional<AppId>();
520 
521   return registrar_->LookupExternalAppId(app_url_it->second.install_url);
522 }
523 
GetSystemAppTypeForAppId(AppId app_id) const524 base::Optional<SystemAppType> SystemWebAppManager::GetSystemAppTypeForAppId(
525     AppId app_id) const {
526   auto it = app_id_to_app_type_.find(app_id);
527   if (it == app_id_to_app_type_.end())
528     return base::nullopt;
529 
530   return it->second;
531 }
532 
GetAppIds() const533 std::vector<AppId> SystemWebAppManager::GetAppIds() const {
534   std::vector<AppId> app_ids;
535   for (const auto& app_id_to_app_type : app_id_to_app_type_) {
536     app_ids.push_back(app_id_to_app_type.first);
537   }
538   return app_ids;
539 }
540 
IsSystemWebApp(const AppId & app_id) const541 bool SystemWebAppManager::IsSystemWebApp(const AppId& app_id) const {
542   return app_id_to_app_type_.contains(app_id);
543 }
544 
IsSingleWindow(SystemAppType type) const545 bool SystemWebAppManager::IsSingleWindow(SystemAppType type) const {
546   auto it = system_app_infos_.find(type);
547   if (it == system_app_infos_.end())
548     return false;
549 
550   return it->second.single_window;
551 }
552 
AppShouldReceiveLaunchDirectory(SystemAppType type) const553 bool SystemWebAppManager::AppShouldReceiveLaunchDirectory(
554     SystemAppType type) const {
555   auto it = system_app_infos_.find(type);
556   if (it == system_app_infos_.end())
557     return false;
558   return it->second.include_launch_directory;
559 }
560 
GetEnabledOriginTrials(const SystemAppType type,const GURL & url)561 const std::vector<std::string>* SystemWebAppManager::GetEnabledOriginTrials(
562     const SystemAppType type,
563     const GURL& url) {
564   const auto& origin_to_origin_trials =
565       system_app_infos_.at(type).enabled_origin_trials;
566   auto iter_trials = origin_to_origin_trials.find(url::Origin::Create(url));
567   if (iter_trials == origin_to_origin_trials.end())
568     return nullptr;
569 
570   return &iter_trials->second;
571 }
572 
AppHasFileHandlingOriginTrial(SystemAppType type)573 bool SystemWebAppManager::AppHasFileHandlingOriginTrial(SystemAppType type) {
574   const auto& info = system_app_infos_.at(type);
575   const std::vector<std::string>* trials =
576       GetEnabledOriginTrials(type, info.install_url);
577   return trials && base::Contains(*trials, kFileHandlingOriginTrial);
578 }
579 
OnReadyToCommitNavigation(const AppId & app_id,content::NavigationHandle * navigation_handle)580 void SystemWebAppManager::OnReadyToCommitNavigation(
581     const AppId& app_id,
582     content::NavigationHandle* navigation_handle) {
583   // No need to setup origin trials for intra-document navigation.
584   if (navigation_handle->IsSameDocument())
585     return;
586 
587   const base::Optional<SystemAppType> type = GetSystemAppTypeForAppId(app_id);
588   // This function should only be called when an navigation happens inside a
589   // System App. So the |app_id| should always have a valid associated System
590   // App type.
591   DCHECK(type.has_value());
592 
593   const std::vector<std::string>* trials =
594       GetEnabledOriginTrials(type.value(), navigation_handle->GetURL());
595   if (trials)
596     navigation_handle->ForceEnableOriginTrials(*trials);
597 }
598 
GetAdditionalSearchTerms(SystemAppType type) const599 std::vector<std::string> SystemWebAppManager::GetAdditionalSearchTerms(
600     SystemAppType type) const {
601   auto it = system_app_infos_.find(type);
602   if (it == system_app_infos_.end())
603     return {};
604 
605   const auto& search_terms = it->second.additional_search_terms;
606 
607   std::vector<std::string> search_terms_strings;
608   std::transform(search_terms.begin(), search_terms.end(),
609                  std::back_inserter(search_terms_strings),
610                  [](int term) { return l10n_util::GetStringUTF8(term); });
611   return search_terms_strings;
612 }
613 
ShouldShowInLauncher(SystemAppType type) const614 bool SystemWebAppManager::ShouldShowInLauncher(SystemAppType type) const {
615   auto it = system_app_infos_.find(type);
616   if (it == system_app_infos_.end())
617     return false;
618   return it->second.show_in_launcher;
619 }
620 
ShouldShowInSearch(SystemAppType type) const621 bool SystemWebAppManager::ShouldShowInSearch(SystemAppType type) const {
622   auto it = system_app_infos_.find(type);
623   if (it == system_app_infos_.end())
624     return false;
625   return it->second.show_in_search;
626 }
627 
GetCapturingSystemAppForURL(const GURL & url) const628 base::Optional<SystemAppType> SystemWebAppManager::GetCapturingSystemAppForURL(
629     const GURL& url) const {
630   if (!HasSystemWebAppScheme(url))
631     return base::nullopt;
632 
633   base::Optional<AppId> app_id = registrar_->FindAppWithUrlInScope(url);
634   if (!app_id.has_value())
635     return base::nullopt;
636 
637   base::Optional<SystemAppType> type = GetSystemAppTypeForAppId(app_id.value());
638   if (!type.has_value())
639     return base::nullopt;
640 
641   const auto it = system_app_infos_.find(type);
642   if (it == system_app_infos_.end())
643     return base::nullopt;
644 
645   if (!it->second.capture_navigations)
646     return base::nullopt;
647 
648 #if defined(OS_CHROMEOS)
649   if (type == SystemAppType::CAMERA &&
650       url.spec() != chromeos::kChromeUICameraAppMainURL)
651     return base::nullopt;
652 #endif  // defined(OS_CHROMEOS)
653 
654   return type;
655 }
656 
GetMinimumWindowSize(const AppId & app_id) const657 gfx::Size SystemWebAppManager::GetMinimumWindowSize(const AppId& app_id) const {
658   auto app_type_it = app_id_to_app_type_.find(app_id);
659   if (app_type_it == app_id_to_app_type_.end())
660     return gfx::Size();
661   const SystemAppType& app_type = app_type_it->second;
662   auto app_info_it = system_app_infos_.find(app_type);
663   if (app_info_it == system_app_infos_.end())
664     return gfx::Size();
665   return app_info_it->second.minimum_window_size;
666 }
667 
SetSystemAppsForTesting(base::flat_map<SystemAppType,SystemAppInfo> system_apps)668 void SystemWebAppManager::SetSystemAppsForTesting(
669     base::flat_map<SystemAppType, SystemAppInfo> system_apps) {
670   system_app_infos_ = std::move(system_apps);
671 }
672 
SetUpdatePolicyForTesting(UpdatePolicy policy)673 void SystemWebAppManager::SetUpdatePolicyForTesting(UpdatePolicy policy) {
674   update_policy_ = policy;
675 }
676 
ResetOnAppsSynchronizedForTesting()677 void SystemWebAppManager::ResetOnAppsSynchronizedForTesting() {
678   on_apps_synchronized_ = std::make_unique<base::OneShotEvent>();
679 }
680 
681 // static
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)682 void SystemWebAppManager::RegisterProfilePrefs(
683     user_prefs::PrefRegistrySyncable* registry) {
684   registry->RegisterStringPref(prefs::kSystemWebAppLastUpdateVersion, "");
685   registry->RegisterStringPref(prefs::kSystemWebAppLastInstalledLocale, "");
686   registry->RegisterStringPref(prefs::kSystemWebAppLastAttemptedVersion, "");
687   registry->RegisterStringPref(prefs::kSystemWebAppLastAttemptedLocale, "");
688   registry->RegisterIntegerPref(prefs::kSystemWebAppInstallFailureCount, 0);
689 }
690 
CurrentVersion() const691 const base::Version& SystemWebAppManager::CurrentVersion() const {
692   return version_info::GetVersion();
693 }
694 
CurrentLocale() const695 const std::string& SystemWebAppManager::CurrentLocale() const {
696   return g_browser_process->GetApplicationLocale();
697 }
698 
RecordSystemWebAppInstallDuration(const base::TimeDelta & install_duration) const699 void SystemWebAppManager::RecordSystemWebAppInstallDuration(
700     const base::TimeDelta& install_duration) const {
701   // Install duration should be non-negative. A low resolution clock could
702   // result in a |install_duration| of 0.
703   DCHECK_GE(install_duration.InMilliseconds(), 0);
704 
705   if (!shutting_down_) {
706     base::UmaHistogramMediumTimes(kInstallDurationHistogramName,
707                                   install_duration);
708   }
709 }
710 
RecordSystemWebAppInstallResults(const std::map<GURL,InstallResultCode> & install_results) const711 void SystemWebAppManager::RecordSystemWebAppInstallResults(
712     const std::map<GURL, InstallResultCode>& install_results) const {
713   // Report install result codes. Exclude kSuccessAlreadyInstalled from metrics.
714   // This result means the installation pipeline is a no-op (which happens every
715   // time user logs in, and if there hasn't been a version upgrade). This skews
716   // the install success rate.
717   std::map<GURL, InstallResultCode> results_to_report;
718   std::copy_if(install_results.begin(), install_results.end(),
719                std::inserter(results_to_report, results_to_report.end()),
720                [](const auto& url_and_result) {
721                  return url_and_result.second !=
722                         InstallResultCode::kSuccessAlreadyInstalled;
723                });
724 
725   for (const auto& url_and_result : results_to_report) {
726     // Record aggregate result.
727     base::UmaHistogramEnumeration(
728         kInstallResultHistogramName,
729         shutting_down_
730             ? InstallResultCode::kCancelledOnWebAppProviderShuttingDown
731             : url_and_result.second);
732 
733     // Record per-profile result.
734     base::UmaHistogramEnumeration(
735         install_result_per_profile_histogram_name_,
736         shutting_down_
737             ? InstallResultCode::kCancelledOnWebAppProviderShuttingDown
738             : url_and_result.second);
739   }
740 
741   // Record per-app result.
742   for (const auto& type_and_app_info : system_app_infos_) {
743     const GURL& install_url = type_and_app_info.second.install_url;
744     const auto url_and_result = results_to_report.find(install_url);
745     if (url_and_result != results_to_report.cend()) {
746       const std::string app_histogram_name =
747           std::string(kInstallResultHistogramName) + ".Apps." +
748           type_and_app_info.second.internal_name;
749       base::UmaHistogramEnumeration(
750           app_histogram_name,
751           shutting_down_
752               ? InstallResultCode::kCancelledOnWebAppProviderShuttingDown
753               : url_and_result->second);
754     }
755   }
756 }
757 
OnAppsSynchronized(bool did_force_install_apps,const base::TimeTicks & install_start_time,std::map<GURL,InstallResultCode> install_results,std::map<GURL,bool> uninstall_results)758 void SystemWebAppManager::OnAppsSynchronized(
759     bool did_force_install_apps,
760     const base::TimeTicks& install_start_time,
761     std::map<GURL, InstallResultCode> install_results,
762     std::map<GURL, bool> uninstall_results) {
763   // TODO(crbug.com/1053371): Clean up File Handler install. We install SWA file
764   // handlers here, because the code that registers file handlers for regular
765   // Web Apps, does not run when for apps installed in the background.
766   for (const auto& it : system_app_infos_) {
767     const SystemAppType& type = it.first;
768     base::Optional<AppId> app_id = GetAppIdForSystemApp(type);
769     if (!app_id)
770       continue;
771 
772     if (AppHasFileHandlingOriginTrial(type)) {
773       os_integration_manager_->ForceEnableFileHandlingOriginTrial(
774           app_id.value());
775     } else {
776       os_integration_manager_->DisableForceEnabledFileHandlingOriginTrial(
777           app_id.value());
778     }
779   }
780 
781   const base::TimeDelta install_duration =
782       base::TimeTicks::Now() - install_start_time;
783 
784   // TODO(qjw): Figure out where install_results come from, decide if
785   // installation failures need to be handled
786   pref_service_->SetString(prefs::kSystemWebAppLastUpdateVersion,
787                            CurrentVersion().GetString());
788   pref_service_->SetString(prefs::kSystemWebAppLastInstalledLocale,
789                            CurrentLocale());
790   pref_service_->SetInteger(prefs::kSystemWebAppInstallFailureCount, 0);
791 
792   // Report install duration only if the install pipeline actually installs
793   // all the apps (e.g. on version upgrade).
794   if (did_force_install_apps)
795     RecordSystemWebAppInstallDuration(install_duration);
796 
797   RecordSystemWebAppInstallResults(install_results);
798 
799   // Build the map from installed app id to app type.
800   for (const auto& it : system_app_infos_) {
801     const SystemAppType& app_type = it.first;
802     base::Optional<AppId> app_id =
803         registrar_->LookupExternalAppId(it.second.install_url);
804     if (app_id.has_value())
805       app_id_to_app_type_[app_id.value()] = app_type;
806   }
807 
808   // May be called more than once in tests.
809   if (!on_apps_synchronized_->is_signaled()) {
810     on_apps_synchronized_->Signal();
811     OnAppsPolicyChanged();
812   }
813 
814 #if BUILDFLAG(IS_CHROMEOS_ASH)
815   bool is_camera_app_installed =
816       system_app_infos_.find(SystemAppType::CAMERA) != system_app_infos_.end();
817   profile_->GetPrefs()->SetBoolean(chromeos::prefs::kHasCameraAppMigratedToSWA,
818                                    is_camera_app_installed);
819 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
820 }
821 
ShouldForceInstallApps() const822 bool SystemWebAppManager::ShouldForceInstallApps() const {
823   if (base::FeatureList::IsEnabled(features::kAlwaysReinstallSystemWebApps))
824     return true;
825 
826   if (update_policy_ == UpdatePolicy::kAlwaysUpdate)
827     return true;
828 
829   base::Version current_installed_version(
830       pref_service_->GetString(prefs::kSystemWebAppLastUpdateVersion));
831 
832   const std::string& current_installed_locale(
833       pref_service_->GetString(prefs::kSystemWebAppLastInstalledLocale));
834 
835   // If Chrome version rolls back for some reason, ensure System Web Apps are
836   // always in sync with Chrome version.
837   const bool versionIsDifferent = !current_installed_version.IsValid() ||
838                                   current_installed_version != CurrentVersion();
839 
840   // If system language changes, ensure System Web Apps launcher localization
841   // are in sync with current language.
842   const bool localeIsDifferent = current_installed_locale != CurrentLocale();
843 
844   return versionIsDifferent || localeIsDifferent;
845 }
846 
UpdateLastAttemptedInfo()847 void SystemWebAppManager::UpdateLastAttemptedInfo() {
848   base::Version last_attempted_version(
849       pref_service_->GetString(prefs::kSystemWebAppLastAttemptedVersion));
850 
851   const std::string& last_attempted_locale(
852       pref_service_->GetString(prefs::kSystemWebAppLastAttemptedLocale));
853 
854   const bool is_retry = last_attempted_version.IsValid() &&
855                         last_attempted_version == CurrentVersion() &&
856                         last_attempted_locale == CurrentLocale();
857   if (!is_retry) {
858     pref_service_->SetInteger(prefs::kSystemWebAppInstallFailureCount, 0);
859   }
860 
861   pref_service_->SetString(prefs::kSystemWebAppLastAttemptedVersion,
862                            CurrentVersion().GetString());
863   pref_service_->SetString(prefs::kSystemWebAppLastAttemptedLocale,
864                            CurrentLocale());
865   pref_service_->CommitPendingWrite();
866 }
867 
CheckAndIncrementRetryAttempts()868 bool SystemWebAppManager::CheckAndIncrementRetryAttempts() {
869   int installation_failures =
870       pref_service_->GetInteger(prefs::kSystemWebAppInstallFailureCount);
871   bool reached_retry_limit = installation_failures > kInstallFailureAttempts;
872 
873   if (!reached_retry_limit) {
874     pref_service_->SetInteger(prefs::kSystemWebAppInstallFailureCount,
875                               installation_failures + 1);
876     pref_service_->CommitPendingWrite();
877     return false;
878   }
879   return true;
880 }
881 
OnAppsPolicyChanged()882 void SystemWebAppManager::OnAppsPolicyChanged() {
883 #if defined(OS_CHROMEOS)
884   if (!on_apps_synchronized_->is_signaled())
885     return;
886 
887   auto disabled_system_apps = GetDisabledSystemWebApps();
888 
889   for (const auto& id_and_type : app_id_to_app_type_) {
890     const bool is_disabled =
891         base::Contains(disabled_system_apps, id_and_type.second);
892     registry_controller_->SetAppIsDisabled(id_and_type.first, is_disabled);
893   }
894 #endif  // defined(OS_CHROMEOS)
895 }
896 
897 }  // namespace web_app
898