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/stringprintf.h"
12 #include "chrome/browser/chromeos/drive/file_system_util.h"
13 #include "chrome/browser/chromeos/drive/file_task_executor.h"
14 #include "chrome/browser/chromeos/file_manager/app_id.h"
15 #include "chrome/browser/chromeos/file_manager/file_browser_handlers.h"
16 #include "chrome/browser/chromeos/file_manager/fileapi_util.h"
17 #include "chrome/browser/chromeos/file_manager/open_util.h"
18 #include "chrome/browser/drive/drive_api_util.h"
19 #include "chrome/browser/drive/drive_app_registry.h"
20 #include "chrome/browser/extensions/extension_tab_util.h"
21 #include "chrome/browser/extensions/extension_util.h"
22 #include "chrome/browser/profiles/profile.h"
23 #include "chrome/browser/ui/extensions/application_launch.h"
24 #include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
25 #include "chrome/common/extensions/api/file_browser_handlers/file_browser_handler.h"
26 #include "chrome/common/extensions/api/file_manager_private.h"
27 #include "chrome/common/extensions/extension_constants.h"
28 #include "chrome/common/pref_names.h"
29 #include "chromeos/chromeos_switches.h"
30 #include "extensions/browser/extension_host.h"
31 #include "extensions/browser/extension_registry.h"
32 #include "extensions/browser/extension_system.h"
33 #include "extensions/browser/extension_util.h"
34 #include "extensions/common/constants.h"
35 #include "extensions/common/extension_set.h"
36 #include "storage/browser/fileapi/file_system_url.h"
38 using extensions::Extension
;
39 using extensions::app_file_handler_util::FindFileHandlersForFiles
;
40 using storage::FileSystemURL
;
42 namespace file_manager
{
43 namespace file_tasks
{
47 // The values "file" and "app" are confusing, but cannot be changed easily as
48 // these are used in default task IDs stored in preferences.
49 const char kFileBrowserHandlerTaskType
[] = "file";
50 const char kFileHandlerTaskType
[] = "app";
51 const char kDriveAppTaskType
[] = "drive";
53 // Drive apps always use the action ID.
54 const char kDriveAppActionID
[] = "open-with";
56 // Converts a TaskType to a string.
57 std::string
TaskTypeToString(TaskType task_type
) {
59 case TASK_TYPE_FILE_BROWSER_HANDLER
:
60 return kFileBrowserHandlerTaskType
;
61 case TASK_TYPE_FILE_HANDLER
:
62 return kFileHandlerTaskType
;
63 case TASK_TYPE_DRIVE_APP
:
64 return kDriveAppTaskType
;
65 case TASK_TYPE_UNKNOWN
:
72 // Converts a string to a TaskType. Returns TASK_TYPE_UNKNOWN on error.
73 TaskType
StringToTaskType(const std::string
& str
) {
74 if (str
== kFileBrowserHandlerTaskType
)
75 return TASK_TYPE_FILE_BROWSER_HANDLER
;
76 if (str
== kFileHandlerTaskType
)
77 return TASK_TYPE_FILE_HANDLER
;
78 if (str
== kDriveAppTaskType
)
79 return TASK_TYPE_DRIVE_APP
;
80 return TASK_TYPE_UNKNOWN
;
83 // Legacy Drive task extension prefix, used by CrackTaskID.
84 const char kDriveTaskExtensionPrefix
[] = "drive-app:";
85 const size_t kDriveTaskExtensionPrefixLength
=
86 arraysize(kDriveTaskExtensionPrefix
) - 1;
88 // Returns true if path_mime_set contains a Google document.
89 bool ContainsGoogleDocument(const PathAndMimeTypeSet
& path_mime_set
) {
90 for (PathAndMimeTypeSet::const_iterator iter
= path_mime_set
.begin();
91 iter
!= path_mime_set
.end(); ++iter
) {
92 if (drive::util::HasHostedDocumentExtension(iter
->first
))
98 // Leaves tasks handled by the file manger itself as is and removes all others.
99 void KeepOnlyFileManagerInternalTasks(std::vector
<FullTaskDescriptor
>* tasks
) {
100 std::vector
<FullTaskDescriptor
> filtered
;
101 for (size_t i
= 0; i
< tasks
->size(); ++i
) {
102 if ((*tasks
)[i
].task_descriptor().app_id
== kFileManagerAppId
)
103 filtered
.push_back((*tasks
)[i
]);
105 tasks
->swap(filtered
);
108 // Returns true if the given task is a handler by built-in apps like Files.app
109 // itself or QuickOffice etc. They are used as the initial default app.
110 bool IsFallbackFileHandler(const file_tasks::TaskDescriptor
& task
) {
111 if (task
.task_type
!= file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER
&&
112 task
.task_type
!= file_tasks::TASK_TYPE_FILE_HANDLER
)
115 const char* const kBuiltInApps
[] = {
119 extension_misc::kQuickOfficeComponentExtensionId
,
120 extension_misc::kQuickOfficeInternalExtensionId
,
121 extension_misc::kQuickOfficeExtensionId
,
124 for (size_t i
= 0; i
< arraysize(kBuiltInApps
); ++i
) {
125 if (task
.app_id
== kBuiltInApps
[i
])
133 FullTaskDescriptor::FullTaskDescriptor(
134 const TaskDescriptor
& task_descriptor
,
135 const std::string
& task_title
,
136 const GURL
& icon_url
,
138 bool is_generic_file_handler
)
139 : task_descriptor_(task_descriptor
),
140 task_title_(task_title
),
142 is_default_(is_default
),
143 is_generic_file_handler_(is_generic_file_handler
) {
146 void UpdateDefaultTask(PrefService
* pref_service
,
147 const std::string
& task_id
,
148 const std::set
<std::string
>& suffixes
,
149 const std::set
<std::string
>& mime_types
) {
153 if (!mime_types
.empty()) {
154 DictionaryPrefUpdate
mime_type_pref(pref_service
,
155 prefs::kDefaultTasksByMimeType
);
156 for (std::set
<std::string
>::const_iterator iter
= mime_types
.begin();
157 iter
!= mime_types
.end(); ++iter
) {
158 base::StringValue
* value
= new base::StringValue(task_id
);
159 mime_type_pref
->SetWithoutPathExpansion(*iter
, value
);
163 if (!suffixes
.empty()) {
164 DictionaryPrefUpdate
mime_type_pref(pref_service
,
165 prefs::kDefaultTasksBySuffix
);
166 for (std::set
<std::string
>::const_iterator iter
= suffixes
.begin();
167 iter
!= suffixes
.end(); ++iter
) {
168 base::StringValue
* value
= new base::StringValue(task_id
);
169 // Suffixes are case insensitive.
170 std::string lower_suffix
= base::StringToLowerASCII(*iter
);
171 mime_type_pref
->SetWithoutPathExpansion(lower_suffix
, value
);
176 std::string
GetDefaultTaskIdFromPrefs(const PrefService
& pref_service
,
177 const std::string
& mime_type
,
178 const std::string
& suffix
) {
179 VLOG(1) << "Looking for default for MIME type: " << mime_type
180 << " and suffix: " << suffix
;
182 if (!mime_type
.empty()) {
183 const base::DictionaryValue
* mime_task_prefs
=
184 pref_service
.GetDictionary(prefs::kDefaultTasksByMimeType
);
185 DCHECK(mime_task_prefs
);
186 LOG_IF(ERROR
, !mime_task_prefs
) << "Unable to open MIME type prefs";
187 if (mime_task_prefs
&&
188 mime_task_prefs
->GetStringWithoutPathExpansion(mime_type
, &task_id
)) {
189 VLOG(1) << "Found MIME default handler: " << task_id
;
194 const base::DictionaryValue
* suffix_task_prefs
=
195 pref_service
.GetDictionary(prefs::kDefaultTasksBySuffix
);
196 DCHECK(suffix_task_prefs
);
197 LOG_IF(ERROR
, !suffix_task_prefs
) << "Unable to open suffix prefs";
198 std::string lower_suffix
= base::StringToLowerASCII(suffix
);
199 if (suffix_task_prefs
)
200 suffix_task_prefs
->GetStringWithoutPathExpansion(lower_suffix
, &task_id
);
201 VLOG_IF(1, !task_id
.empty()) << "Found suffix default handler: " << task_id
;
205 std::string
MakeTaskID(const std::string
& app_id
,
207 const std::string
& action_id
) {
208 return base::StringPrintf("%s|%s|%s",
210 TaskTypeToString(task_type
).c_str(),
214 std::string
TaskDescriptorToId(const TaskDescriptor
& task_descriptor
) {
215 return MakeTaskID(task_descriptor
.app_id
,
216 task_descriptor
.task_type
,
217 task_descriptor
.action_id
);
220 bool ParseTaskID(const std::string
& task_id
, TaskDescriptor
* task
) {
223 std::vector
<std::string
> result
;
224 int count
= Tokenize(task_id
, std::string("|"), &result
);
226 // Parse a legacy task ID that only contain two parts. Drive tasks are
227 // identified by a prefix "drive-app:" on the extension ID. The legacy task
228 // IDs can be stored in preferences.
230 if (StartsWithASCII(result
[0], kDriveTaskExtensionPrefix
, true)) {
231 task
->task_type
= TASK_TYPE_DRIVE_APP
;
232 task
->app_id
= result
[0].substr(kDriveTaskExtensionPrefixLength
);
234 task
->task_type
= TASK_TYPE_FILE_BROWSER_HANDLER
;
235 task
->app_id
= result
[0];
238 task
->action_id
= result
[1];
246 TaskType task_type
= StringToTaskType(result
[1]);
247 if (task_type
== TASK_TYPE_UNKNOWN
)
250 task
->app_id
= result
[0];
251 task
->task_type
= task_type
;
252 task
->action_id
= result
[2];
257 bool ExecuteFileTask(Profile
* profile
,
258 const GURL
& source_url
,
259 const TaskDescriptor
& task
,
260 const std::vector
<FileSystemURL
>& file_urls
,
261 const FileTaskFinishedCallback
& done
) {
262 // drive::FileTaskExecutor is responsible to handle drive tasks.
263 if (task
.task_type
== TASK_TYPE_DRIVE_APP
) {
264 DCHECK_EQ(kDriveAppActionID
, task
.action_id
);
265 drive::FileTaskExecutor
* executor
=
266 new drive::FileTaskExecutor(profile
, task
.app_id
);
267 executor
->Execute(file_urls
, done
);
271 // Get the extension.
272 const Extension
* extension
= extensions::ExtensionRegistry::Get(
273 profile
)->enabled_extensions().GetByID(task
.app_id
);
278 if (task
.task_type
== TASK_TYPE_FILE_BROWSER_HANDLER
) {
279 return file_browser_handlers::ExecuteFileBrowserHandler(
285 } else if (task
.task_type
== TASK_TYPE_FILE_HANDLER
) {
286 std::vector
<base::FilePath
> paths
;
287 for (size_t i
= 0; i
!= file_urls
.size(); ++i
)
288 paths
.push_back(file_urls
[i
].path());
289 apps::LaunchPlatformAppWithFileHandler(
290 profile
, extension
, task
.action_id
, paths
);
292 done
.Run(extensions::api::file_manager_private::TASK_RESULT_MESSAGE_SENT
);
299 void FindDriveAppTasks(
300 const drive::DriveAppRegistry
& drive_app_registry
,
301 const PathAndMimeTypeSet
& path_mime_set
,
302 std::vector
<FullTaskDescriptor
>* result_list
) {
305 bool is_first
= true;
306 typedef std::map
<std::string
, drive::DriveAppInfo
> DriveAppInfoMap
;
307 DriveAppInfoMap drive_app_map
;
309 for (PathAndMimeTypeSet::const_iterator it
= path_mime_set
.begin();
310 it
!= path_mime_set
.end(); ++it
) {
311 const base::FilePath
& file_path
= it
->first
;
312 const std::string
& mime_type
= it
->second
;
313 // Return immediately if a file not on Drive is found, as Drive app tasks
314 // work only if all files are on Drive.
315 if (!drive::util::IsUnderDriveMountPoint(file_path
))
318 std::vector
<drive::DriveAppInfo
> app_info_list
;
319 drive_app_registry
.GetAppsForFile(file_path
.Extension(),
324 // For the first file, we store all the info.
325 for (size_t j
= 0; j
< app_info_list
.size(); ++j
)
326 drive_app_map
[app_info_list
[j
].app_id
] = app_info_list
[j
];
328 // For remaining files, take the intersection with the current
329 // result, based on the app id.
330 std::set
<std::string
> app_id_set
;
331 for (size_t j
= 0; j
< app_info_list
.size(); ++j
)
332 app_id_set
.insert(app_info_list
[j
].app_id
);
333 for (DriveAppInfoMap::iterator iter
= drive_app_map
.begin();
334 iter
!= drive_app_map
.end();) {
335 if (app_id_set
.count(iter
->first
) == 0) {
336 drive_app_map
.erase(iter
++);
346 for (DriveAppInfoMap::const_iterator iter
= drive_app_map
.begin();
347 iter
!= drive_app_map
.end(); ++iter
) {
348 const drive::DriveAppInfo
& app_info
= iter
->second
;
349 TaskDescriptor
descriptor(app_info
.app_id
,
352 GURL icon_url
= drive::util::FindPreferredIcon(
354 drive::util::kPreferredIconSize
);
355 result_list
->push_back(
356 FullTaskDescriptor(descriptor
,
359 false /* is_default */,
360 false /* is_generic_file_handler */));
364 bool IsGenericFileHandler(
365 const extensions::FileHandlerInfo
& file_handler_info
) {
366 return file_handler_info
.extensions
.count("*") > 0 ||
367 file_handler_info
.types
.count("*") > 0 ||
368 file_handler_info
.types
.count("*/*") > 0;
371 void FindFileHandlerTasks(
373 const PathAndMimeTypeSet
& path_mime_set
,
374 std::vector
<FullTaskDescriptor
>* result_list
) {
375 DCHECK(!path_mime_set
.empty());
378 const extensions::ExtensionSet
& enabled_extensions
=
379 extensions::ExtensionRegistry::Get(profile
)->enabled_extensions();
381 for (extensions::ExtensionSet::const_iterator iter
=
382 enabled_extensions
.begin();
383 iter
!= enabled_extensions
.end();
385 const Extension
* extension
= iter
->get();
387 // Check that the extension can be launched via an event. This includes all
388 // platform apps plus whitelisted extensions.
389 if (!CanLaunchViaEvent(extension
))
392 // Ephemeral apps cannot be file handlers.
393 if (extensions::util::IsEphemeralApp(extension
->id(), profile
))
396 if (profile
->IsOffTheRecord() &&
397 !extensions::util::IsIncognitoEnabled(extension
->id(), profile
))
400 typedef std::vector
<const extensions::FileHandlerInfo
*> FileHandlerList
;
401 FileHandlerList file_handlers
=
402 FindFileHandlersForFiles(*extension
, path_mime_set
);
403 if (file_handlers
.empty())
406 // If the new ZIP unpacker is disabled, then hide its handlers, so we don't
407 // show both the legacy one and the new one in Files app for ZIP files.
408 if (extension
->id() == extension_misc::kZIPUnpackerExtensionId
&&
409 base::CommandLine::ForCurrentProcess()->HasSwitch(
410 chromeos::switches::kDisableNewZIPUnpacker
)) {
414 // Show the first matching non-generic handler of each app. If there doesn't
415 // exist such handler, show the first matching handler of the app.
416 const extensions::FileHandlerInfo
* file_handler
= nullptr;
417 for (auto handler
: file_handlers
) {
418 if (!IsGenericFileHandler(*handler
)) {
419 file_handler
= handler
;
423 if (file_handler
== nullptr) {
424 file_handler
= file_handlers
.front();
427 std::string task_id
= file_tasks::MakeTaskID(
428 extension
->id(), file_tasks::TASK_TYPE_FILE_HANDLER
, file_handler
->id
);
430 GURL best_icon
= extensions::ExtensionIconSource::GetIconURL(
432 drive::util::kPreferredIconSize
,
433 ExtensionIconSet::MATCH_BIGGER
,
437 result_list
->push_back(
438 FullTaskDescriptor(TaskDescriptor(extension
->id(),
439 file_tasks::TASK_TYPE_FILE_HANDLER
,
443 false /* is_default */,
444 IsGenericFileHandler(*file_handler
)));
448 void FindFileBrowserHandlerTasks(
450 const std::vector
<GURL
>& file_urls
,
451 std::vector
<FullTaskDescriptor
>* result_list
) {
452 DCHECK(!file_urls
.empty());
455 file_browser_handlers::FileBrowserHandlerList common_tasks
=
456 file_browser_handlers::FindFileBrowserHandlers(profile
, file_urls
);
457 if (common_tasks
.empty())
460 const extensions::ExtensionSet
& enabled_extensions
=
461 extensions::ExtensionRegistry::Get(profile
)->enabled_extensions();
462 for (file_browser_handlers::FileBrowserHandlerList::const_iterator iter
=
463 common_tasks
.begin();
464 iter
!= common_tasks
.end();
466 const FileBrowserHandler
* handler
= *iter
;
467 const std::string extension_id
= handler
->extension_id();
468 const Extension
* extension
= enabled_extensions
.GetByID(extension_id
);
471 // TODO(zelidrag): Figure out how to expose icon URL that task defined in
472 // manifest instead of the default extension icon.
473 const GURL icon_url
= extensions::ExtensionIconSource::GetIconURL(
475 extension_misc::EXTENSION_ICON_BITTY
,
476 ExtensionIconSet::MATCH_BIGGER
,
480 result_list
->push_back(FullTaskDescriptor(
481 TaskDescriptor(extension_id
,
482 file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER
,
486 false /* is_default */,
487 false /* is_generic_file_handler */));
491 void FindAllTypesOfTasks(
493 const drive::DriveAppRegistry
* drive_app_registry
,
494 const PathAndMimeTypeSet
& path_mime_set
,
495 const std::vector
<GURL
>& file_urls
,
496 std::vector
<FullTaskDescriptor
>* result_list
) {
500 // Find Drive app tasks, if the drive app registry is present.
501 if (drive_app_registry
)
502 FindDriveAppTasks(*drive_app_registry
, path_mime_set
, result_list
);
504 // Find and append file handler tasks. We know there aren't duplicates
505 // because Drive apps and platform apps are entirely different kinds of
507 FindFileHandlerTasks(profile
, path_mime_set
, result_list
);
509 // Find and append file browser handler tasks. We know there aren't
510 // duplicates because "file_browser_handlers" and "file_handlers" shouldn't
511 // be used in the same manifest.json.
512 FindFileBrowserHandlerTasks(profile
, file_urls
, result_list
);
514 // Google documents can only be handled by internal handlers.
515 if (ContainsGoogleDocument(path_mime_set
))
516 KeepOnlyFileManagerInternalTasks(result_list
);
518 ChooseAndSetDefaultTask(*profile
->GetPrefs(), path_mime_set
, result_list
);
521 void ChooseAndSetDefaultTask(const PrefService
& pref_service
,
522 const PathAndMimeTypeSet
& path_mime_set
,
523 std::vector
<FullTaskDescriptor
>* tasks
) {
524 // Collect the task IDs of default tasks from the preferences into a set.
525 std::set
<std::string
> default_task_ids
;
526 for (PathAndMimeTypeSet::const_iterator it
= path_mime_set
.begin();
527 it
!= path_mime_set
.end(); ++it
) {
528 const base::FilePath
& file_path
= it
->first
;
529 const std::string
& mime_type
= it
->second
;
530 std::string task_id
= file_tasks::GetDefaultTaskIdFromPrefs(
531 pref_service
, mime_type
, file_path
.Extension());
532 default_task_ids
.insert(task_id
);
535 // Go through all the tasks from the beginning and see if there is any
536 // default task. If found, pick and set it as default and return.
537 for (size_t i
= 0; i
< tasks
->size(); ++i
) {
538 FullTaskDescriptor
* task
= &tasks
->at(i
);
539 DCHECK(!task
->is_default());
540 const std::string task_id
= TaskDescriptorToId(task
->task_descriptor());
541 if (ContainsKey(default_task_ids
, task_id
)) {
542 task
->set_is_default(true);
547 // No default tasks found. If there is any fallback file browser handler,
548 // make it as default task, so it's selected by default.
549 for (size_t i
= 0; i
< tasks
->size(); ++i
) {
550 FullTaskDescriptor
* task
= &tasks
->at(i
);
551 DCHECK(!task
->is_default());
552 if (IsFallbackFileHandler(task
->task_descriptor())) {
553 task
->set_is_default(true);
559 } // namespace file_tasks
560 } // namespace file_manager