1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/ui/webui/settings/chromeos/apps_section.h"
6 
7 #include "base/feature_list.h"
8 #include "base/no_destructor.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/chromeos/arc/arc_util.h"
11 #include "chrome/browser/chromeos/plugin_vm/plugin_vm_features.h"
12 #include "chrome/browser/chromeos/plugin_vm/plugin_vm_pref_names.h"
13 #include "chrome/browser/chromeos/plugin_vm/plugin_vm_util.h"
14 #include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
15 #include "chrome/browser/ui/webui/app_management/app_management_page_handler.h"
16 #include "chrome/browser/ui/webui/settings/chromeos/android_apps_handler.h"
17 #include "chrome/browser/ui/webui/settings/chromeos/plugin_vm_handler.h"
18 #include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
19 #include "chrome/browser/ui/webui/webui_util.h"
20 #include "chrome/common/url_constants.h"
21 #include "chrome/grit/generated_resources.h"
22 #include "chrome/grit/os_settings_resources.h"
23 #include "chromeos/constants/chromeos_features.h"
24 #include "components/arc/arc_prefs.h"
25 #include "components/prefs/pref_service.h"
26 #include "content/public/browser/web_ui_data_source.h"
27 #include "ui/base/l10n/l10n_util.h"
28 #include "ui/base/webui/web_ui_util.h"
29 #include "ui/chromeos/devicetype_utils.h"
30 
31 namespace chromeos {
32 namespace settings {
33 namespace {
34 
GetAppsSearchConcepts()35 const std::vector<SearchConcept>& GetAppsSearchConcepts() {
36   static const base::NoDestructor<std::vector<SearchConcept>> tags({
37       {IDS_OS_SETTINGS_TAG_APPS,
38        mojom::kAppsSectionPath,
39        mojom::SearchResultIcon::kAppsGrid,
40        mojom::SearchResultDefaultRank::kMedium,
41        mojom::SearchResultType::kSection,
42        {.section = mojom::Section::kApps}},
43       {IDS_OS_SETTINGS_TAG_APPS_MANAGEMENT,
44        mojom::kAppManagementSubpagePath,
45        mojom::SearchResultIcon::kAppsGrid,
46        mojom::SearchResultDefaultRank::kMedium,
47        mojom::SearchResultType::kSubpage,
48        {.subpage = mojom::Subpage::kAppManagement},
49        {IDS_OS_SETTINGS_TAG_APPS_MANAGEMENT_ALT1, SearchConcept::kAltTagEnd}},
50   });
51   return *tags;
52 }
53 
GetAndroidPlayStoreSearchConcepts()54 const std::vector<SearchConcept>& GetAndroidPlayStoreSearchConcepts() {
55   static const base::NoDestructor<std::vector<SearchConcept>> tags({
56       {IDS_OS_SETTINGS_TAG_PLAY_STORE,
57        mojom::kGooglePlayStoreSubpagePath,
58        mojom::SearchResultIcon::kGooglePlay,
59        mojom::SearchResultDefaultRank::kMedium,
60        mojom::SearchResultType::kSubpage,
61        {.subpage = mojom::Subpage::kGooglePlayStore}},
62       {IDS_OS_SETTINGS_TAG_REMOVE_PLAY_STORE,
63        mojom::kGooglePlayStoreSubpagePath,
64        mojom::SearchResultIcon::kGooglePlay,
65        mojom::SearchResultDefaultRank::kMedium,
66        mojom::SearchResultType::kSetting,
67        {.setting = mojom::Setting::kRemovePlayStore},
68        {IDS_OS_SETTINGS_TAG_REMOVE_PLAY_STORE_ALT1, SearchConcept::kAltTagEnd}},
69   });
70   return *tags;
71 }
72 
GetAndroidSettingsSearchConcepts()73 const std::vector<SearchConcept>& GetAndroidSettingsSearchConcepts() {
74   static const base::NoDestructor<std::vector<SearchConcept>> tags({
75       {IDS_OS_SETTINGS_TAG_ANDROID_SETTINGS_WITH_PLAY_STORE,
76        mojom::kGooglePlayStoreSubpagePath,
77        mojom::SearchResultIcon::kGooglePlay,
78        mojom::SearchResultDefaultRank::kMedium,
79        mojom::SearchResultType::kSetting,
80        {.setting = mojom::Setting::kManageAndroidPreferences},
81        {IDS_OS_SETTINGS_TAG_ANDROID_SETTINGS_WITH_PLAY_STORE_ALT1,
82         SearchConcept::kAltTagEnd}},
83   });
84   return *tags;
85 }
86 
GetAndroidNoPlayStoreSearchConcepts()87 const std::vector<SearchConcept>& GetAndroidNoPlayStoreSearchConcepts() {
88   static const base::NoDestructor<std::vector<SearchConcept>> tags({
89       {IDS_OS_SETTINGS_TAG_ANDROID_SETTINGS,
90        mojom::kAppsSectionPath,
91        mojom::SearchResultIcon::kAndroid,
92        mojom::SearchResultDefaultRank::kMedium,
93        mojom::SearchResultType::kSetting,
94        {.setting = mojom::Setting::kManageAndroidPreferences},
95        {IDS_OS_SETTINGS_TAG_ANDROID_SETTINGS_ALT1, SearchConcept::kAltTagEnd}},
96   });
97   return *tags;
98 }
99 
GetAndroidPlayStoreDisabledSearchConcepts()100 const std::vector<SearchConcept>& GetAndroidPlayStoreDisabledSearchConcepts() {
101   static const base::NoDestructor<std::vector<SearchConcept>> tags({
102       {IDS_OS_SETTINGS_TAG_ANDROID_TURN_ON_PLAY_STORE,
103        mojom::kAppsSectionPath,
104        mojom::SearchResultIcon::kAndroid,
105        mojom::SearchResultDefaultRank::kMedium,
106        mojom::SearchResultType::kSetting,
107        {.setting = mojom::Setting::kTurnOnPlayStore},
108        {IDS_OS_SETTINGS_TAG_ANDROID_TURN_ON_PLAY_STORE_ALT1,
109         SearchConcept::kAltTagEnd}},
110   });
111   return *tags;
112 }
113 
AddAppManagementStrings(content::WebUIDataSource * html_source)114 void AddAppManagementStrings(content::WebUIDataSource* html_source) {
115   static constexpr webui::LocalizedString kLocalizedStrings[] = {
116       {"appManagementAppInstalledByPolicyLabel",
117        IDS_APP_MANAGEMENT_POLICY_APP_POLICY_STRING},
118       {"appManagementCameraPermissionLabel", IDS_APP_MANAGEMENT_CAMERA},
119       {"appManagementContactsPermissionLabel", IDS_APP_MANAGEMENT_CONTACTS},
120       {"appManagementLocationPermissionLabel", IDS_APP_MANAGEMENT_LOCATION},
121       {"appManagementMicrophonePermissionLabel", IDS_APP_MANAGEMENT_MICROPHONE},
122       {"appManagementMoreSettingsLabel", IDS_APP_MANAGEMENT_MORE_SETTINGS},
123       {"appManagementNoAppsFound", IDS_APP_MANAGEMENT_NO_APPS_FOUND},
124       {"appManagementNoPermissions",
125        IDS_APPLICATION_INFO_APP_NO_PERMISSIONS_TEXT},
126       {"appManagementNotificationsLabel", IDS_APP_MANAGEMENT_NOTIFICATIONS},
127       {"appManagementPermissionsLabel", IDS_APP_MANAGEMENT_PERMISSIONS},
128       {"appManagementPinToShelfLabel", IDS_APP_MANAGEMENT_PIN_TO_SHELF},
129       {"appManagementPrintingPermissionLabel", IDS_APP_MANAGEMENT_PRINTING},
130       {"appManagementSearchPrompt", IDS_APP_MANAGEMENT_SEARCH_PROMPT},
131       {"appManagementStoragePermissionLabel", IDS_APP_MANAGEMENT_STORAGE},
132       {"appManagementUninstallLabel", IDS_APP_MANAGEMENT_UNINSTALL_APP},
133   };
134   AddLocalizedStringsBulk(html_source, kLocalizedStrings);
135 }
136 
ShowPluginVm(const Profile * profile,const PrefService & pref_service)137 bool ShowPluginVm(const Profile* profile, const PrefService& pref_service) {
138   // Even if not allowed, we still want to show Plugin VM if the VM image is on
139   // disk, so that users are still able to delete the image at will.
140   return plugin_vm::PluginVmFeatures::Get()->IsAllowed(profile) ||
141          pref_service.GetBoolean(plugin_vm::prefs::kPluginVmImageExists);
142 }
143 
144 }  // namespace
145 
AppsSection(Profile * profile,SearchTagRegistry * search_tag_registry,PrefService * pref_service,ArcAppListPrefs * arc_app_list_prefs)146 AppsSection::AppsSection(Profile* profile,
147                          SearchTagRegistry* search_tag_registry,
148                          PrefService* pref_service,
149                          ArcAppListPrefs* arc_app_list_prefs)
150     : OsSettingsSection(profile, search_tag_registry),
151       pref_service_(pref_service),
152       arc_app_list_prefs_(arc_app_list_prefs) {
153   SearchTagRegistry::ScopedTagUpdater updater = registry()->StartUpdate();
154   updater.AddSearchTags(GetAppsSearchConcepts());
155 
156   if (arc::IsArcAllowedForProfile(profile)) {
157     pref_change_registrar_.Init(pref_service_);
158     pref_change_registrar_.Add(
159         arc::prefs::kArcEnabled,
160         base::BindRepeating(&AppsSection::UpdateAndroidSearchTags,
161                             base::Unretained(this)));
162 
163     if (arc_app_list_prefs_)
164       arc_app_list_prefs_->AddObserver(this);
165 
166     UpdateAndroidSearchTags();
167   }
168 }
169 
~AppsSection()170 AppsSection::~AppsSection() {
171   if (arc::IsArcAllowedForProfile(profile())) {
172     if (arc_app_list_prefs_)
173       arc_app_list_prefs_->RemoveObserver(this);
174   }
175 }
176 
AddLoadTimeData(content::WebUIDataSource * html_source)177 void AppsSection::AddLoadTimeData(content::WebUIDataSource* html_source) {
178   static constexpr webui::LocalizedString kLocalizedStrings[] = {
179       {"appsPageTitle", IDS_SETTINGS_APPS_TITLE},
180       {"appManagementTitle", IDS_SETTINGS_APPS_LINK_TEXT},
181   };
182   AddLocalizedStringsBulk(html_source, kLocalizedStrings);
183 
184   // We have 2 variants of Android apps settings. Default case, when the Play
185   // Store app exists we show expandable section that allows as to
186   // enable/disable the Play Store and link to Android settings which is
187   // available once settings app is registered in the system.
188   // For AOSP images we don't have the Play Store app. In last case we Android
189   // apps settings consists only from root link to Android settings and only
190   // visible once settings app is registered.
191   html_source->AddBoolean("androidAppsVisible",
192                           arc::IsArcAllowedForProfile(profile()));
193   html_source->AddBoolean("havePlayStoreApp", arc::IsPlayStoreAvailable());
194   html_source->AddBoolean(
195       "isSupportedArcVersion",
196       AppManagementPageHandler::IsCurrentArcVersionSupported(profile()));
197 
198   AddAppManagementStrings(html_source);
199   AddAndroidAppStrings(html_source);
200   AddPluginVmLoadTimeData(html_source);
201 }
202 
AddHandlers(content::WebUI * web_ui)203 void AppsSection::AddHandlers(content::WebUI* web_ui) {
204   web_ui->AddMessageHandler(
205       std::make_unique<chromeos::settings::AndroidAppsHandler>(profile()));
206 
207   if (ShowPluginVm(profile(), *pref_service_)) {
208     web_ui->AddMessageHandler(
209         std::make_unique<chromeos::settings::PluginVmHandler>(profile()));
210   }
211 }
212 
GetSectionNameMessageId() const213 int AppsSection::GetSectionNameMessageId() const {
214   return IDS_SETTINGS_APPS_TITLE;
215 }
216 
GetSection() const217 mojom::Section AppsSection::GetSection() const {
218   return mojom::Section::kApps;
219 }
220 
GetSectionIcon() const221 mojom::SearchResultIcon AppsSection::GetSectionIcon() const {
222   return mojom::SearchResultIcon::kAppsGrid;
223 }
224 
GetSectionPath() const225 std::string AppsSection::GetSectionPath() const {
226   return mojom::kAppsSectionPath;
227 }
228 
LogMetric(mojom::Setting setting,base::Value & value) const229 bool AppsSection::LogMetric(mojom::Setting setting, base::Value& value) const {
230   // Unimplemented.
231   return false;
232 }
233 
RegisterHierarchy(HierarchyGenerator * generator) const234 void AppsSection::RegisterHierarchy(HierarchyGenerator* generator) const {
235   generator->RegisterTopLevelSetting(mojom::Setting::kTurnOnPlayStore);
236 
237   // Manage apps.
238   generator->RegisterTopLevelSubpage(IDS_SETTINGS_APPS_LINK_TEXT,
239                                      mojom::Subpage::kAppManagement,
240                                      mojom::SearchResultIcon::kAppsGrid,
241                                      mojom::SearchResultDefaultRank::kMedium,
242                                      mojom::kAppManagementSubpagePath);
243   // Note: The subpage name in the UI is updated dynamically based on the app
244   // being shown, but we use a generic "App details" string here.
245   generator->RegisterNestedSubpage(
246       IDS_SETTINGS_APP_DETAILS_TITLE, mojom::Subpage::kAppDetails,
247       mojom::Subpage::kAppManagement, mojom::SearchResultIcon::kAppsGrid,
248       mojom::SearchResultDefaultRank::kMedium, mojom::kAppDetailsSubpagePath);
249   generator->RegisterNestedSubpage(IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS,
250                                    mojom::Subpage::kPluginVmSharedPaths,
251                                    mojom::Subpage::kAppManagement,
252                                    mojom::SearchResultIcon::kAppsGrid,
253                                    mojom::SearchResultDefaultRank::kMedium,
254                                    mojom::kPluginVmSharedPathsSubpagePath);
255   generator->RegisterNestedSubpage(
256       IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_USB_DEVICES_LABEL,
257       mojom::Subpage::kPluginVmUsbPreferences, mojom::Subpage::kAppManagement,
258       mojom::SearchResultIcon::kAppsGrid,
259       mojom::SearchResultDefaultRank::kMedium,
260       mojom::kPluginVmUsbPreferencesSubpagePath);
261 
262   // Google Play Store.
263   generator->RegisterTopLevelSubpage(IDS_SETTINGS_ANDROID_APPS_LABEL,
264                                      mojom::Subpage::kGooglePlayStore,
265                                      mojom::SearchResultIcon::kGooglePlay,
266                                      mojom::SearchResultDefaultRank::kMedium,
267                                      mojom::kGooglePlayStoreSubpagePath);
268   static constexpr mojom::Setting kGooglePlayStoreSettings[] = {
269       mojom::Setting::kManageAndroidPreferences,
270       mojom::Setting::kRemovePlayStore,
271   };
272   RegisterNestedSettingBulk(mojom::Subpage::kGooglePlayStore,
273                             kGooglePlayStoreSettings, generator);
274   generator->RegisterTopLevelAltSetting(
275       mojom::Setting::kManageAndroidPreferences);
276 }
277 
OnAppRegistered(const std::string & app_id,const ArcAppListPrefs::AppInfo & app_info)278 void AppsSection::OnAppRegistered(const std::string& app_id,
279                                   const ArcAppListPrefs::AppInfo& app_info) {
280   UpdateAndroidSearchTags();
281 }
282 
AddAndroidAppStrings(content::WebUIDataSource * html_source)283 void AppsSection::AddAndroidAppStrings(content::WebUIDataSource* html_source) {
284   static constexpr webui::LocalizedString kLocalizedStrings[] = {
285       {"androidAppsPageLabel", IDS_SETTINGS_ANDROID_APPS_LABEL},
286       {"androidAppsEnable", IDS_SETTINGS_TURN_ON},
287       {"androidAppsManageApps", IDS_SETTINGS_ANDROID_APPS_MANAGE_APPS},
288       {"androidAppsRemove", IDS_SETTINGS_ANDROID_APPS_REMOVE},
289       {"androidAppsRemoveButton", IDS_SETTINGS_ANDROID_APPS_REMOVE_BUTTON},
290       {"androidAppsDisableDialogTitle",
291        IDS_SETTINGS_ANDROID_APPS_DISABLE_DIALOG_TITLE},
292       {"androidAppsDisableDialogMessage",
293        IDS_SETTINGS_ANDROID_APPS_DISABLE_DIALOG_MESSAGE},
294       {"androidAppsDisableDialogRemove",
295        IDS_SETTINGS_ANDROID_APPS_DISABLE_DIALOG_REMOVE},
296       {"androidAppsManageAppLinks", IDS_SETTINGS_ANDROID_APPS_MANAGE_APP_LINKS},
297   };
298   AddLocalizedStringsBulk(html_source, kLocalizedStrings);
299   html_source->AddLocalizedString("androidAppsPageTitle",
300                                   arc::IsPlayStoreAvailable()
301                                       ? IDS_SETTINGS_ANDROID_APPS_TITLE
302                                       : IDS_SETTINGS_ANDROID_SETTINGS_TITLE);
303   html_source->AddString(
304       "androidAppsSubtext",
305       l10n_util::GetStringFUTF16(
306           IDS_SETTINGS_ANDROID_APPS_SUBTEXT, ui::GetChromeOSDeviceName(),
307           GetHelpUrlWithBoard(chrome::kAndroidAppsLearnMoreURL)));
308 }
309 
AddPluginVmLoadTimeData(content::WebUIDataSource * html_source)310 void AppsSection::AddPluginVmLoadTimeData(
311     content::WebUIDataSource* html_source) {
312   static constexpr webui::LocalizedString kLocalizedStrings[] = {
313       {"pluginVmSharedPaths", IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS},
314       {"pluginVmSharedPathsListHeading",
315        IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_LIST_HEADING},
316       {"pluginVmSharedPathsInstructionsAdd",
317        IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_INSTRUCTIONS_ADD},
318       {"pluginVmSharedPathsInstructionsRemove",
319        IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_INSTRUCTIONS_REMOVE},
320       {"pluginVmSharedPathsRemoveSharing",
321        IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_SHARING},
322       {"pluginVmSharedPathsRemoveFailureDialogMessage",
323        IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_FAILURE_DIALOG_MESSAGE},
324       {"pluginVmSharedPathsRemoveFailureDialogTitle",
325        IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_FAILURE_DIALOG_TITLE},
326       {"pluginVmSharedPathsRemoveFailureTryAgain",
327        IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_FAILURE_TRY_AGAIN},
328       {"pluginVmSharedPathsListEmptyMessage",
329        IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_LIST_EMPTY_MESSAGE},
330       {"pluginVmSharedUsbDevicesLabel",
331        IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_USB_DEVICES_LABEL},
332       {"pluginVmSharedUsbDevicesDescription",
333        IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_USB_DEVICES_DESCRIPTION},
334       {"pluginVmSharedUsbDevicesExtraDescription",
335        IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_USB_DEVICES_EXTRA_DESCRIPTION},
336       {"pluginVmSharedUsbDevicesListEmptyMessage",
337        IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_USB_DEVICES_LIST_EMPTY_MESSAGE},
338       {"pluginVmSharedUsbDevicesInUse",
339        IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_USB_DEVICES_IN_USE},
340       {"pluginVmSharedUsbDevicesReassign",
341        IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_USB_DEVICES_REASSIGN},
342       {"pluginVmPermissionDialogCameraLabel",
343        IDS_SETTINGS_APPS_PLUGIN_VM_PERMISSION_DIALOG_CAMERA_LABEL},
344       {"pluginVmPermissionDialogMicrophoneLabel",
345        IDS_SETTINGS_APPS_PLUGIN_VM_PERMISSION_DIALOG_MICROPHONE_LABEL},
346       {"pluginVmPermissionDialogRelaunchButton",
347        IDS_SETTINGS_APPS_PLUGIN_VM_PERMISSION_DIALOG_RELAUNCH_BUTTON},
348   };
349   AddLocalizedStringsBulk(html_source, kLocalizedStrings);
350 
351   html_source->AddBoolean("showPluginVm",
352                           ShowPluginVm(profile(), *pref_service_));
353   html_source->AddString(
354       "pluginVmSharedPathsInstructionsLocate",
355       l10n_util::GetStringFUTF16(
356           IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_INSTRUCTIONS_LOCATE,
357           base::UTF8ToUTF16(plugin_vm::kChromeOSBaseDirectoryDisplayText)));
358   html_source->AddBoolean(
359       "showPluginVmCameraPermissions",
360       base::FeatureList::IsEnabled(
361           chromeos::features::kPluginVmShowCameraPermissions));
362   html_source->AddBoolean(
363       "showPluginVmMicrophonePermissions",
364       base::FeatureList::IsEnabled(
365           chromeos::features::kPluginVmShowMicrophonePermissions));
366 }
367 
UpdateAndroidSearchTags()368 void AppsSection::UpdateAndroidSearchTags() {
369   SearchTagRegistry::ScopedTagUpdater updater = registry()->StartUpdate();
370 
371   updater.RemoveSearchTags(GetAndroidNoPlayStoreSearchConcepts());
372   updater.RemoveSearchTags(GetAndroidPlayStoreDisabledSearchConcepts());
373   updater.RemoveSearchTags(GetAndroidPlayStoreSearchConcepts());
374   updater.RemoveSearchTags(GetAndroidSettingsSearchConcepts());
375 
376   if (!arc::IsPlayStoreAvailable()) {
377     updater.AddSearchTags(GetAndroidNoPlayStoreSearchConcepts());
378     return;
379   }
380 
381   if (!arc::IsArcPlayStoreEnabledForProfile(profile())) {
382     updater.AddSearchTags(GetAndroidPlayStoreDisabledSearchConcepts());
383     return;
384   }
385 
386   updater.AddSearchTags(GetAndroidPlayStoreSearchConcepts());
387 
388   if (arc_app_list_prefs_ &&
389       arc_app_list_prefs_->IsRegistered(arc::kSettingsAppId)) {
390     updater.AddSearchTags(GetAndroidSettingsSearchConcepts());
391   }
392 }
393 
394 }  // namespace settings
395 }  // namespace chromeos
396