1 // Copyright (c) 2012 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.
5 #include "chrome/browser/chromeos/file_manager/file_tasks.h"
7 #include "apps/launcher.h"
9 #include "base/prefs/pref_service.h"
10 #include "base/prefs/scoped_user_pref_update.h"
11 #include "base/strings/string_split.h"
12 #include "base/strings/stringprintf.h"
13 #include "chrome/browser/chromeos/drive/file_system_util.h"
14 #include "chrome/browser/chromeos/drive/file_task_executor.h"
15 #include "chrome/browser/chromeos/file_manager/app_id.h"
16 #include "chrome/browser/chromeos/file_manager/file_browser_handlers.h"
17 #include "chrome/browser/chromeos/file_manager/fileapi_util.h"
18 #include "chrome/browser/chromeos/file_manager/open_util.h"
19 #include "chrome/browser/extensions/extension_tab_util.h"
20 #include "chrome/browser/extensions/extension_util.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/ui/extensions/application_launch.h"
23 #include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
24 #include "chrome/common/extensions/api/file_browser_handlers/file_browser_handler.h"
25 #include "chrome/common/extensions/api/file_manager_private.h"
26 #include "chrome/common/extensions/extension_constants.h"
27 #include "chrome/common/pref_names.h"
28 #include "chromeos/chromeos_switches.h"
29 #include "components/drive/drive_api_util.h"
30 #include "components/drive/drive_app_registry.h"
31 #include "components/mime_util/mime_util.h"
32 #include "extensions/browser/extension_host.h"
33 #include "extensions/browser/extension_registry.h"
34 #include "extensions/browser/extension_system.h"
35 #include "extensions/browser/extension_util.h"
36 #include "extensions/common/constants.h"
37 #include "extensions/common/extension_set.h"
38 #include "storage/browser/fileapi/file_system_url.h"
40 using extensions::Extension
;
41 using extensions::app_file_handler_util::FindFileHandlersForFiles
;
42 using storage::FileSystemURL
;
44 namespace file_manager
{
45 namespace file_tasks
{
49 // The values "file" and "app" are confusing, but cannot be changed easily as
50 // these are used in default task IDs stored in preferences.
51 const char kFileBrowserHandlerTaskType
[] = "file";
52 const char kFileHandlerTaskType
[] = "app";
53 const char kDriveAppTaskType
[] = "drive";
55 // Drive apps always use the action ID.
56 const char kDriveAppActionID
[] = "open-with";
58 // Converts a TaskType to a string.
59 std::string
TaskTypeToString(TaskType task_type
) {
61 case TASK_TYPE_FILE_BROWSER_HANDLER
:
62 return kFileBrowserHandlerTaskType
;
63 case TASK_TYPE_FILE_HANDLER
:
64 return kFileHandlerTaskType
;
65 case TASK_TYPE_DRIVE_APP
:
66 return kDriveAppTaskType
;
67 case TASK_TYPE_UNKNOWN
:
74 // Converts a string to a TaskType. Returns TASK_TYPE_UNKNOWN on error.
75 TaskType
StringToTaskType(const std::string
& str
) {
76 if (str
== kFileBrowserHandlerTaskType
)
77 return TASK_TYPE_FILE_BROWSER_HANDLER
;
78 if (str
== kFileHandlerTaskType
)
79 return TASK_TYPE_FILE_HANDLER
;
80 if (str
== kDriveAppTaskType
)
81 return TASK_TYPE_DRIVE_APP
;
82 return TASK_TYPE_UNKNOWN
;
85 // Legacy Drive task extension prefix, used by CrackTaskID.
86 const char kDriveTaskExtensionPrefix
[] = "drive-app:";
87 const size_t kDriveTaskExtensionPrefixLength
=
88 arraysize(kDriveTaskExtensionPrefix
) - 1;
90 // Returns true if path_mime_set contains a Google document.
91 bool ContainsGoogleDocument(const PathAndMimeTypeSet
& path_mime_set
) {
92 for (PathAndMimeTypeSet::const_iterator iter
= path_mime_set
.begin();
93 iter
!= path_mime_set
.end(); ++iter
) {
94 if (drive::util::HasHostedDocumentExtension(iter
->first
))
100 // Leaves tasks handled by the file manger itself as is and removes all others.
101 void KeepOnlyFileManagerInternalTasks(std::vector
<FullTaskDescriptor
>* tasks
) {
102 std::vector
<FullTaskDescriptor
> filtered
;
103 for (size_t i
= 0; i
< tasks
->size(); ++i
) {
104 if ((*tasks
)[i
].task_descriptor().app_id
== kFileManagerAppId
)
105 filtered
.push_back((*tasks
)[i
]);
107 tasks
->swap(filtered
);
110 // Returns true if the given task is a handler by built-in apps like Files.app
111 // itself or QuickOffice etc. They are used as the initial default app.
112 bool IsFallbackFileHandler(const file_tasks::TaskDescriptor
& task
) {
113 if (task
.task_type
!= file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER
&&
114 task
.task_type
!= file_tasks::TASK_TYPE_FILE_HANDLER
)
117 const char* const kBuiltInApps
[] = {
121 extension_misc::kQuickOfficeComponentExtensionId
,
122 extension_misc::kQuickOfficeInternalExtensionId
,
123 extension_misc::kQuickOfficeExtensionId
,
126 for (size_t i
= 0; i
< arraysize(kBuiltInApps
); ++i
) {
127 if (task
.app_id
== kBuiltInApps
[i
])
135 FullTaskDescriptor::FullTaskDescriptor(
136 const TaskDescriptor
& task_descriptor
,
137 const std::string
& task_title
,
138 const GURL
& icon_url
,
140 bool is_generic_file_handler
)
141 : task_descriptor_(task_descriptor
),
142 task_title_(task_title
),
144 is_default_(is_default
),
145 is_generic_file_handler_(is_generic_file_handler
) {
148 void UpdateDefaultTask(PrefService
* pref_service
,
149 const std::string
& task_id
,
150 const std::set
<std::string
>& suffixes
,
151 const std::set
<std::string
>& mime_types
) {
155 if (!mime_types
.empty()) {
156 DictionaryPrefUpdate
mime_type_pref(pref_service
,
157 prefs::kDefaultTasksByMimeType
);
158 for (std::set
<std::string
>::const_iterator iter
= mime_types
.begin();
159 iter
!= mime_types
.end(); ++iter
) {
160 base::StringValue
* value
= new base::StringValue(task_id
);
161 mime_type_pref
->SetWithoutPathExpansion(*iter
, value
);
165 if (!suffixes
.empty()) {
166 DictionaryPrefUpdate
mime_type_pref(pref_service
,
167 prefs::kDefaultTasksBySuffix
);
168 for (std::set
<std::string
>::const_iterator iter
= suffixes
.begin();
169 iter
!= suffixes
.end(); ++iter
) {
170 base::StringValue
* value
= new base::StringValue(task_id
);
171 // Suffixes are case insensitive.
172 std::string lower_suffix
= base::ToLowerASCII(*iter
);
173 mime_type_pref
->SetWithoutPathExpansion(lower_suffix
, value
);
178 std::string
GetDefaultTaskIdFromPrefs(const PrefService
& pref_service
,
179 const std::string
& mime_type
,
180 const std::string
& suffix
) {
181 VLOG(1) << "Looking for default for MIME type: " << mime_type
182 << " and suffix: " << suffix
;
184 if (!mime_type
.empty()) {
185 const base::DictionaryValue
* mime_task_prefs
=
186 pref_service
.GetDictionary(prefs::kDefaultTasksByMimeType
);
187 DCHECK(mime_task_prefs
);
188 LOG_IF(ERROR
, !mime_task_prefs
) << "Unable to open MIME type prefs";
189 if (mime_task_prefs
&&
190 mime_task_prefs
->GetStringWithoutPathExpansion(mime_type
, &task_id
)) {
191 VLOG(1) << "Found MIME default handler: " << task_id
;
196 const base::DictionaryValue
* suffix_task_prefs
=
197 pref_service
.GetDictionary(prefs::kDefaultTasksBySuffix
);
198 DCHECK(suffix_task_prefs
);
199 LOG_IF(ERROR
, !suffix_task_prefs
) << "Unable to open suffix prefs";
200 std::string lower_suffix
= base::ToLowerASCII(suffix
);
201 if (suffix_task_prefs
)
202 suffix_task_prefs
->GetStringWithoutPathExpansion(lower_suffix
, &task_id
);
203 VLOG_IF(1, !task_id
.empty()) << "Found suffix default handler: " << task_id
;
207 std::string
MakeTaskID(const std::string
& app_id
,
209 const std::string
& action_id
) {
210 return base::StringPrintf("%s|%s|%s",
212 TaskTypeToString(task_type
).c_str(),
216 std::string
TaskDescriptorToId(const TaskDescriptor
& task_descriptor
) {
217 return MakeTaskID(task_descriptor
.app_id
,
218 task_descriptor
.task_type
,
219 task_descriptor
.action_id
);
222 bool ParseTaskID(const std::string
& task_id
, TaskDescriptor
* task
) {
225 std::vector
<std::string
> result
= base::SplitString(
226 task_id
, "|", base::KEEP_WHITESPACE
, base::SPLIT_WANT_NONEMPTY
);
228 // Parse a legacy task ID that only contain two parts. Drive tasks are
229 // identified by a prefix "drive-app:" on the extension ID. The legacy task
230 // IDs can be stored in preferences.
231 if (result
.size() == 2) {
232 if (base::StartsWith(result
[0], kDriveTaskExtensionPrefix
,
233 base::CompareCase::SENSITIVE
)) {
234 task
->task_type
= TASK_TYPE_DRIVE_APP
;
235 task
->app_id
= result
[0].substr(kDriveTaskExtensionPrefixLength
);
237 task
->task_type
= TASK_TYPE_FILE_BROWSER_HANDLER
;
238 task
->app_id
= result
[0];
241 task
->action_id
= result
[1];
246 if (result
.size() != 3)
249 TaskType task_type
= StringToTaskType(result
[1]);
250 if (task_type
== TASK_TYPE_UNKNOWN
)
253 task
->app_id
= result
[0];
254 task
->task_type
= task_type
;
255 task
->action_id
= result
[2];
260 bool ExecuteFileTask(Profile
* profile
,
261 const GURL
& source_url
,
262 const TaskDescriptor
& task
,
263 const std::vector
<FileSystemURL
>& file_urls
,
264 const FileTaskFinishedCallback
& done
) {
265 // drive::FileTaskExecutor is responsible to handle drive tasks.
266 if (task
.task_type
== TASK_TYPE_DRIVE_APP
) {
267 DCHECK_EQ(kDriveAppActionID
, task
.action_id
);
268 drive::FileTaskExecutor
* executor
=
269 new drive::FileTaskExecutor(profile
, task
.app_id
);
270 executor
->Execute(file_urls
, done
);
274 // Get the extension.
275 const Extension
* extension
= extensions::ExtensionRegistry::Get(
276 profile
)->enabled_extensions().GetByID(task
.app_id
);
281 if (task
.task_type
== TASK_TYPE_FILE_BROWSER_HANDLER
) {
282 return file_browser_handlers::ExecuteFileBrowserHandler(
288 } else if (task
.task_type
== TASK_TYPE_FILE_HANDLER
) {
289 std::vector
<base::FilePath
> paths
;
290 for (size_t i
= 0; i
!= file_urls
.size(); ++i
)
291 paths
.push_back(file_urls
[i
].path());
292 apps::LaunchPlatformAppWithFileHandler(
293 profile
, extension
, task
.action_id
, paths
);
295 done
.Run(extensions::api::file_manager_private::TASK_RESULT_MESSAGE_SENT
);
302 void FindDriveAppTasks(
303 const drive::DriveAppRegistry
& drive_app_registry
,
304 const PathAndMimeTypeSet
& path_mime_set
,
305 std::vector
<FullTaskDescriptor
>* result_list
) {
308 bool is_first
= true;
309 typedef std::map
<std::string
, drive::DriveAppInfo
> DriveAppInfoMap
;
310 DriveAppInfoMap drive_app_map
;
312 for (PathAndMimeTypeSet::const_iterator it
= path_mime_set
.begin();
313 it
!= path_mime_set
.end(); ++it
) {
314 const base::FilePath
& file_path
= it
->first
;
315 const std::string
& mime_type
= it
->second
;
316 // Return immediately if a file not on Drive is found, as Drive app tasks
317 // work only if all files are on Drive.
318 if (!drive::util::IsUnderDriveMountPoint(file_path
))
321 std::vector
<drive::DriveAppInfo
> app_info_list
;
322 drive_app_registry
.GetAppsForFile(file_path
.Extension(),
327 // For the first file, we store all the info.
328 for (size_t j
= 0; j
< app_info_list
.size(); ++j
)
329 drive_app_map
[app_info_list
[j
].app_id
] = app_info_list
[j
];
331 // For remaining files, take the intersection with the current
332 // result, based on the app id.
333 std::set
<std::string
> app_id_set
;
334 for (size_t j
= 0; j
< app_info_list
.size(); ++j
)
335 app_id_set
.insert(app_info_list
[j
].app_id
);
336 for (DriveAppInfoMap::iterator iter
= drive_app_map
.begin();
337 iter
!= drive_app_map
.end();) {
338 if (app_id_set
.count(iter
->first
) == 0) {
339 drive_app_map
.erase(iter
++);
349 for (DriveAppInfoMap::const_iterator iter
= drive_app_map
.begin();
350 iter
!= drive_app_map
.end(); ++iter
) {
351 const drive::DriveAppInfo
& app_info
= iter
->second
;
352 TaskDescriptor
descriptor(app_info
.app_id
,
355 GURL icon_url
= drive::util::FindPreferredIcon(
357 drive::util::kPreferredIconSize
);
358 result_list
->push_back(
359 FullTaskDescriptor(descriptor
,
362 false /* is_default */,
363 false /* is_generic_file_handler */));
367 bool IsGoodMatchFileHandler(
368 const extensions::FileHandlerInfo
& file_handler_info
,
369 const PathAndMimeTypeSet
& path_mime_set
) {
370 if (file_handler_info
.extensions
.count("*") > 0 ||
371 file_handler_info
.types
.count("*") > 0 ||
372 file_handler_info
.types
.count("*/*") > 0)
375 // If text/* file handler matches with unsupported text mime type, we don't
376 // regard it as good match.
377 if (file_handler_info
.types
.count("text/*")) {
378 for (const auto& path_mime
: path_mime_set
) {
379 if (mime_util::IsUnsupportedTextMimeType(path_mime
.second
))
387 void FindFileHandlerTasks(
389 const PathAndMimeTypeSet
& path_mime_set
,
390 std::vector
<FullTaskDescriptor
>* result_list
) {
391 DCHECK(!path_mime_set
.empty());
394 const extensions::ExtensionSet
& enabled_extensions
=
395 extensions::ExtensionRegistry::Get(profile
)->enabled_extensions();
397 for (extensions::ExtensionSet::const_iterator iter
=
398 enabled_extensions
.begin();
399 iter
!= enabled_extensions
.end();
401 const Extension
* extension
= iter
->get();
403 // Check that the extension can be launched via an event. This includes all
404 // platform apps plus whitelisted extensions.
405 if (!CanLaunchViaEvent(extension
))
408 // Ephemeral apps cannot be file handlers.
409 if (extensions::util::IsEphemeralApp(extension
->id(), profile
))
412 if (profile
->IsOffTheRecord() &&
413 !extensions::util::IsIncognitoEnabled(extension
->id(), profile
))
416 typedef std::vector
<const extensions::FileHandlerInfo
*> FileHandlerList
;
417 FileHandlerList file_handlers
=
418 FindFileHandlersForFiles(*extension
, path_mime_set
);
419 if (file_handlers
.empty())
422 // If the new ZIP unpacker is disabled, then hide its handlers, so we don't
423 // show both the legacy one and the new one in Files app for ZIP files.
424 if (extension
->id() == extension_misc::kZIPUnpackerExtensionId
&&
425 base::CommandLine::ForCurrentProcess()->HasSwitch(
426 chromeos::switches::kDisableNewZIPUnpacker
)) {
430 // Show the first good matching handler of each app. If there doesn't exist
431 // such handler, show the first matching handler of the app.
432 const extensions::FileHandlerInfo
* file_handler
= file_handlers
.front();
433 for (auto handler
: file_handlers
) {
434 if (IsGoodMatchFileHandler(*handler
, path_mime_set
)) {
435 file_handler
= handler
;
440 std::string task_id
= file_tasks::MakeTaskID(
441 extension
->id(), file_tasks::TASK_TYPE_FILE_HANDLER
, file_handler
->id
);
443 GURL best_icon
= extensions::ExtensionIconSource::GetIconURL(
445 drive::util::kPreferredIconSize
,
446 ExtensionIconSet::MATCH_BIGGER
,
450 // If file handler doesn't match as good match, regards it as generic file
452 const bool is_generic_file_handler
=
453 !IsGoodMatchFileHandler(*file_handler
, path_mime_set
);
454 result_list
->push_back(FullTaskDescriptor(
455 TaskDescriptor(extension
->id(), file_tasks::TASK_TYPE_FILE_HANDLER
,
457 extension
->name(), best_icon
, false /* is_default */,
458 is_generic_file_handler
));
462 void FindFileBrowserHandlerTasks(
464 const std::vector
<GURL
>& file_urls
,
465 std::vector
<FullTaskDescriptor
>* result_list
) {
466 DCHECK(!file_urls
.empty());
469 file_browser_handlers::FileBrowserHandlerList common_tasks
=
470 file_browser_handlers::FindFileBrowserHandlers(profile
, file_urls
);
471 if (common_tasks
.empty())
474 const extensions::ExtensionSet
& enabled_extensions
=
475 extensions::ExtensionRegistry::Get(profile
)->enabled_extensions();
476 for (file_browser_handlers::FileBrowserHandlerList::const_iterator iter
=
477 common_tasks
.begin();
478 iter
!= common_tasks
.end();
480 const FileBrowserHandler
* handler
= *iter
;
481 const std::string extension_id
= handler
->extension_id();
482 const Extension
* extension
= enabled_extensions
.GetByID(extension_id
);
485 // TODO(zelidrag): Figure out how to expose icon URL that task defined in
486 // manifest instead of the default extension icon.
487 const GURL icon_url
= extensions::ExtensionIconSource::GetIconURL(
489 extension_misc::EXTENSION_ICON_BITTY
,
490 ExtensionIconSet::MATCH_BIGGER
,
494 result_list
->push_back(FullTaskDescriptor(
495 TaskDescriptor(extension_id
,
496 file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER
,
500 false /* is_default */,
501 false /* is_generic_file_handler */));
505 void FindAllTypesOfTasks(
507 const drive::DriveAppRegistry
* drive_app_registry
,
508 const PathAndMimeTypeSet
& path_mime_set
,
509 const std::vector
<GURL
>& file_urls
,
510 std::vector
<FullTaskDescriptor
>* result_list
) {
514 // Find Drive app tasks, if the drive app registry is present.
515 if (drive_app_registry
)
516 FindDriveAppTasks(*drive_app_registry
, path_mime_set
, result_list
);
518 // Find and append file handler tasks. We know there aren't duplicates
519 // because Drive apps and platform apps are entirely different kinds of
521 FindFileHandlerTasks(profile
, path_mime_set
, result_list
);
523 // Find and append file browser handler tasks. We know there aren't
524 // duplicates because "file_browser_handlers" and "file_handlers" shouldn't
525 // be used in the same manifest.json.
526 FindFileBrowserHandlerTasks(profile
, file_urls
, result_list
);
528 // Google documents can only be handled by internal handlers.
529 if (ContainsGoogleDocument(path_mime_set
))
530 KeepOnlyFileManagerInternalTasks(result_list
);
532 ChooseAndSetDefaultTask(*profile
->GetPrefs(), path_mime_set
, result_list
);
535 void ChooseAndSetDefaultTask(const PrefService
& pref_service
,
536 const PathAndMimeTypeSet
& path_mime_set
,
537 std::vector
<FullTaskDescriptor
>* tasks
) {
538 // Collect the task IDs of default tasks from the preferences into a set.
539 std::set
<std::string
> default_task_ids
;
540 for (PathAndMimeTypeSet::const_iterator it
= path_mime_set
.begin();
541 it
!= path_mime_set
.end(); ++it
) {
542 const base::FilePath
& file_path
= it
->first
;
543 const std::string
& mime_type
= it
->second
;
544 std::string task_id
= file_tasks::GetDefaultTaskIdFromPrefs(
545 pref_service
, mime_type
, file_path
.Extension());
546 default_task_ids
.insert(task_id
);
549 // Go through all the tasks from the beginning and see if there is any
550 // default task. If found, pick and set it as default and return.
551 for (size_t i
= 0; i
< tasks
->size(); ++i
) {
552 FullTaskDescriptor
* task
= &tasks
->at(i
);
553 DCHECK(!task
->is_default());
554 const std::string task_id
= TaskDescriptorToId(task
->task_descriptor());
555 if (ContainsKey(default_task_ids
, task_id
)) {
556 task
->set_is_default(true);
561 // No default tasks found. If there is any fallback file browser handler,
562 // make it as default task, so it's selected by default.
563 for (size_t i
= 0; i
< tasks
->size(); ++i
) {
564 FullTaskDescriptor
* task
= &tasks
->at(i
);
565 DCHECK(!task
->is_default());
566 if (IsFallbackFileHandler(task
->task_descriptor())) {
567 task
->set_is_default(true);
573 } // namespace file_tasks
574 } // namespace file_manager