| // Copyright (c) 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ash/file_manager/app_service_file_tasks.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "ash/constants/ash_features.h" |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/feature_list.h" |
| #include "base/files/file_path.h" |
| #include "base/logging.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "chrome/browser/apps/app_service/app_icon/app_icon_source.h" |
| #include "chrome/browser/apps/app_service/app_service_proxy.h" |
| #include "chrome/browser/apps/app_service/app_service_proxy_factory.h" |
| #include "chrome/browser/apps/app_service/launch_utils.h" |
| #include "chrome/browser/ash/crostini/crostini_features.h" |
| #include "chrome/browser/ash/file_manager/fileapi_util.h" |
| #include "chrome/browser/ash/file_manager/filesystem_api_util.h" |
| #include "chrome/browser/ash/file_manager/path_util.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/web_applications/os_integration_manager.h" |
| #include "chrome/browser/web_applications/web_app_id_constants.h" |
| #include "chrome/browser/web_applications/web_app_provider.h" |
| #include "chrome/common/extensions/api/file_manager_private.h" |
| #include "components/arc/intent_helper/intent_constants.h" |
| #include "components/arc/mojom/file_system.mojom.h" |
| #include "components/arc/mojom/intent_helper.mojom.h" |
| #include "components/services/app_service/public/cpp/intent_util.h" |
| #include "components/services/app_service/public/mojom/types.mojom-shared.h" |
| #include "components/services/app_service/public/mojom/types.mojom.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "extensions/browser/entry_info.h" |
| #include "extensions/browser/extension_util.h" |
| #include "storage/browser/file_system/file_system_context.h" |
| #include "storage/browser/file_system/file_system_url.h" |
| #include "url/gurl.h" |
| |
| namespace file_manager { |
| namespace file_tasks { |
| |
| using extensions::api::file_manager_private::Verb; |
| |
| namespace { |
| // TODO(crbug/1092784): Only going to support ARC app and web app |
| // for now. |
| TaskType GetTaskType(apps::mojom::AppType app_type) { |
| switch (app_type) { |
| case apps::mojom::AppType::kArc: |
| return TASK_TYPE_ARC_APP; |
| case apps::mojom::AppType::kWeb: |
| case apps::mojom::AppType::kSystemWeb: |
| return TASK_TYPE_WEB_APP; |
| case apps::mojom::AppType::kExtension: |
| // TODO(petermarshall): Distinguish Chrome apps from Extensions. |
| return TASK_TYPE_FILE_HANDLER; |
| case apps::mojom::AppType::kUnknown: |
| case apps::mojom::AppType::kCrostini: |
| case apps::mojom::AppType::kBuiltIn: |
| case apps::mojom::AppType::kMacOs: |
| case apps::mojom::AppType::kPluginVm: |
| case apps::mojom::AppType::kStandaloneBrowser: |
| case apps::mojom::AppType::kRemote: |
| case apps::mojom::AppType::kBorealis: |
| case apps::mojom::AppType::kStandaloneBrowserExtension: |
| return TASK_TYPE_UNKNOWN; |
| } |
| } |
| |
| const char kImportCrostiniImageHandlerId[] = "import-crostini-image"; |
| const char kInstallLinuxPackageHandlerId[] = "install-linux-package"; |
| |
| } // namespace |
| |
| bool FileHandlerIsEnabled(Profile* profile, |
| const std::string& file_handler_id) { |
| // Crostini deb files and backup files can be disabled by policy. |
| if (file_handler_id == kInstallLinuxPackageHandlerId) { |
| return crostini::CrostiniFeatures::Get()->IsRootAccessAllowed(profile); |
| } |
| if (file_handler_id == kImportCrostiniImageHandlerId) { |
| return crostini::CrostiniFeatures::Get()->IsExportImportUIAllowed(profile); |
| } |
| return true; |
| } |
| |
| void FindAppServiceTasks(Profile* profile, |
| const std::vector<extensions::EntryInfo>& entries, |
| const std::vector<GURL>& file_urls, |
| std::vector<FullTaskDescriptor>* result_list) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK_EQ(entries.size(), file_urls.size()); |
| // App Service uses the file extension in the URL for file_handlers for Web |
| // Apps. |
| #if DCHECK_IS_ON() |
| for (const GURL& url : file_urls) |
| DCHECK(url.is_valid()); |
| #endif // DCHECK_IS_ON() |
| |
| // WebApps only have full support for files backed by inodes, so tasks |
| // provided by most Web Apps will be skipped if any non-native files are |
| // present. "System" Web Apps are an exception: we have more control over what |
| // they can do, so tasks provided by System Web Apps are the only ones |
| // permitted at present. See https://crbug.com/1079065. |
| bool has_non_native_file = false; |
| bool has_pdf_file = false; |
| for (const auto& entry : entries) { |
| if (!has_non_native_file && |
| util::IsUnderNonNativeLocalPath(profile, entry.path)) |
| has_non_native_file = true; |
| if (!has_pdf_file && entry.path.Extension() == ".pdf") |
| has_pdf_file = true; |
| } |
| |
| // App Service doesn't exist in Incognito mode but we still want to find |
| // handlers to open a download from its notification from Incognito mode. Use |
| // the base profile in these cases (see crbug.com/1111695). |
| Profile* maybe_original_profile = profile; |
| if (!apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile)) |
| maybe_original_profile = profile->GetOriginalProfile(); |
| |
| apps::AppServiceProxyChromeOs* proxy = |
| apps::AppServiceProxyFactory::GetForProfile(maybe_original_profile); |
| |
| std::vector<apps::mojom::IntentFilePtr> intent_files; |
| intent_files.reserve(entries.size()); |
| for (size_t i = 0; i < entries.size(); i++) { |
| auto file = apps::mojom::IntentFile::New(); |
| file->url = file_urls.at(i); |
| file->mime_type = entries[i].mime_type; |
| file->is_directory = entries[i].is_directory |
| ? apps::mojom::OptionalBool::kTrue |
| : apps::mojom::OptionalBool::kFalse; |
| intent_files.push_back(std::move(file)); |
| } |
| std::vector<apps::IntentLaunchInfo> intent_launch_info = |
| proxy->GetAppsForFiles(std::move(intent_files)); |
| |
| for (auto& launch_entry : intent_launch_info) { |
| apps::mojom::AppType app_type = |
| proxy->AppRegistryCache().GetAppType(launch_entry.app_id); |
| if (!(app_type == apps::mojom::AppType::kArc || |
| app_type == apps::mojom::AppType::kWeb || |
| app_type == apps::mojom::AppType::kSystemWeb || |
| app_type == apps::mojom::AppType::kExtension)) |
| continue; |
| |
| if (app_type == apps::mojom::AppType::kWeb || |
| app_type == apps::mojom::AppType::kSystemWeb) { |
| // Media app and other SWAs can handle "non-native" files. |
| if (has_non_native_file && |
| !web_app::IsSystemAppIdWithFileHandlers(launch_entry.app_id)) { |
| continue; |
| } |
| |
| // "Hide" the media app task (i.e. skip the rest of this loop which would |
| // add it as a handler) when the flag to handle PDF is off. |
| if (launch_entry.app_id == web_app::kMediaAppId && |
| !base::FeatureList::IsEnabled(ash::features::kMediaAppHandlesPdf) && |
| has_pdf_file) |
| continue; |
| |
| // Check the origin trial and feature flag for file handling in web apps. |
| // TODO(1240018): Remove when this feature is fully launched. This check |
| // will not work for lacros web apps. |
| web_app::WebAppProvider* provider = |
| web_app::WebAppProvider::GetDeprecated(maybe_original_profile); |
| web_app::OsIntegrationManager& os_integration_manager = |
| provider->os_integration_manager(); |
| if (!os_integration_manager.IsFileHandlingAPIAvailable( |
| launch_entry.app_id)) |
| continue; |
| } |
| |
| if (app_type == apps::mojom::AppType::kExtension) { |
| if (profile->IsOffTheRecord() && |
| !extensions::util::IsIncognitoEnabled(launch_entry.app_id, profile)) |
| continue; |
| if (!FileHandlerIsEnabled(maybe_original_profile, |
| launch_entry.activity_name)) |
| continue; |
| } |
| |
| constexpr int kIconSize = 32; |
| GURL icon_url = |
| apps::AppIconSource::GetIconURL(launch_entry.app_id, kIconSize); |
| result_list->push_back(FullTaskDescriptor( |
| TaskDescriptor(launch_entry.app_id, GetTaskType(app_type), |
| launch_entry.activity_name), |
| launch_entry.activity_label, Verb::VERB_OPEN_WITH, icon_url, |
| /* is_default=*/false, |
| // TODO(petermarshall): Handle the rest of the logic from FindWebTasks() |
| // e.g. prioritise non-generic handlers. |
| /* is_generic=*/launch_entry.is_generic_file_handler, |
| /* is_file_extension_match=*/launch_entry.is_file_extension_match)); |
| } |
| } |
| |
| void ExecuteAppServiceTask( |
| Profile* profile, |
| const TaskDescriptor& task, |
| const std::vector<storage::FileSystemURL>& file_system_urls, |
| const std::vector<std::string>& mime_types, |
| FileTaskFinishedCallback done) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK_EQ(file_system_urls.size(), mime_types.size()); |
| |
| if (!apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile)) |
| return; |
| |
| constexpr auto launch_source = apps::mojom::LaunchSource::kFromFileManager; |
| constexpr auto launch_container = |
| apps::mojom::LaunchContainer::kLaunchContainerWindow; |
| |
| std::vector<GURL> file_urls; |
| |
| for (auto& file_system_url : file_system_urls) |
| file_urls.push_back(file_system_url.ToGURL()); |
| |
| apps::AppServiceProxyFactory::GetForProfile(profile)->LaunchAppWithFileUrls( |
| task.app_id, |
| apps::GetEventFlags(launch_container, WindowOpenDisposition::NEW_WINDOW, |
| /*prefer_container=*/true), |
| launch_source, file_urls, mime_types); |
| |
| // TODO(benwells): return the correct code here, depending on how the app will |
| // be opened in multiprofile. |
| std::move(done).Run( |
| extensions::api::file_manager_private::TASK_RESULT_MESSAGE_SENT, ""); |
| } |
| |
| } // namespace file_tasks |
| } // namespace file_manager |