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/strings/stringprintf.h"
11 #include "chrome/browser/chromeos/drive/file_system_util.h"
12 #include "chrome/browser/chromeos/drive/file_task_executor.h"
13 #include "chrome/browser/chromeos/file_manager/app_id.h"
14 #include "chrome/browser/chromeos/file_manager/file_browser_handlers.h"
15 #include "chrome/browser/chromeos/file_manager/fileapi_util.h"
16 #include "chrome/browser/chromeos/file_manager/open_util.h"
17 #include "chrome/browser/chromeos/fileapi/file_system_backend.h"
18 #include "chrome/browser/drive/drive_app_registry.h"
19 #include "chrome/browser/extensions/extension_service.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/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_browser_private.h"
26 #include "chrome/common/pref_names.h"
27 #include "extensions/browser/extension_host.h"
28 #include "extensions/browser/extension_system.h"
29 #include "extensions/common/extension_set.h"
30 #include "google_apis/drive/gdata_wapi_parser.h"
31 #include "webkit/browser/fileapi/file_system_context.h"
32 #include "webkit/browser/fileapi/file_system_url.h"
34 using extensions::Extension
;
35 using extensions::app_file_handler_util::FindFileHandlersForFiles
;
36 using fileapi::FileSystemURL
;
38 namespace file_manager
{
39 namespace file_tasks
{
43 // The values "file" and "app" are confusing, but cannot be changed easily as
44 // these are used in default task IDs stored in preferences.
46 // TODO(satorux): We should rename them to "file_browser_handler" and
47 // "file_handler" respectively when switching from preferences to
48 // chrome.storage crbug.com/267359
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 (google_apis::ResourceEntry::ClassifyEntryKindByFileExtension(
94 google_apis::ResourceEntry::KIND_OF_GOOGLE_DOCUMENT
) {
101 // Leaves tasks handled by the file manger itself as is and removes all others.
102 void KeepOnlyFileManagerInternalTasks(std::vector
<FullTaskDescriptor
>* tasks
) {
103 std::vector
<FullTaskDescriptor
> filtered
;
104 for (size_t i
= 0; i
< tasks
->size(); ++i
) {
105 if ((*tasks
)[i
].task_descriptor().app_id
== kFileManagerAppId
)
106 filtered
.push_back((*tasks
)[i
]);
108 tasks
->swap(filtered
);
111 // Finds a task that matches |app_id| and |action_id| from |task_list|.
112 // Returns a mutable iterator to the handler if found. Returns task_list->end()
114 std::vector
<FullTaskDescriptor
>::iterator
115 FindTaskForAppIdAndActionId(
116 std::vector
<FullTaskDescriptor
>* task_list
,
117 const std::string
& app_id
,
118 const std::string
& action_id
) {
121 std::vector
<FullTaskDescriptor
>::iterator iter
= task_list
->begin();
122 while (iter
!= task_list
->end() &&
123 !(iter
->task_descriptor().app_id
== app_id
&&
124 iter
->task_descriptor().action_id
== action_id
)) {
130 // Chooses a suitable video handeler and removes other internal video hander.
131 // Both "watch" and "gallery-video" actions are applicable which means that the
132 // selection is all videos. Showing them both is confusing, so we only keep
133 // the one that makes more sense ("watch" for single selection, "gallery"
134 // for multiple selection).
135 void ChooseSuitableVideoHandler(
136 const std::vector
<GURL
>& file_urls
,
137 std::vector
<FullTaskDescriptor
>* task_list
) {
138 std::vector
<FullTaskDescriptor
>::iterator video_player_iter
=
139 FindTaskForAppIdAndActionId(task_list
, kVideoPlayerAppId
, "video");
140 std::vector
<FullTaskDescriptor
>::iterator gallery_video_iter
=
141 FindTaskForAppIdAndActionId(
142 task_list
, kFileManagerAppId
, "gallery-video");
144 if (video_player_iter
!= task_list
->end() &&
145 gallery_video_iter
!= task_list
->end()) {
146 if (file_urls
.size() == 1)
147 task_list
->erase(gallery_video_iter
);
149 task_list
->erase(video_player_iter
);
155 FullTaskDescriptor::FullTaskDescriptor(
156 const TaskDescriptor
& task_descriptor
,
157 const std::string
& task_title
,
158 const GURL
& icon_url
,
160 : task_descriptor_(task_descriptor
),
161 task_title_(task_title
),
163 is_default_(is_default
){
166 void UpdateDefaultTask(PrefService
* pref_service
,
167 const std::string
& task_id
,
168 const std::set
<std::string
>& suffixes
,
169 const std::set
<std::string
>& mime_types
) {
173 if (!mime_types
.empty()) {
174 DictionaryPrefUpdate
mime_type_pref(pref_service
,
175 prefs::kDefaultTasksByMimeType
);
176 for (std::set
<std::string
>::const_iterator iter
= mime_types
.begin();
177 iter
!= mime_types
.end(); ++iter
) {
178 base::StringValue
* value
= new base::StringValue(task_id
);
179 mime_type_pref
->SetWithoutPathExpansion(*iter
, value
);
183 if (!suffixes
.empty()) {
184 DictionaryPrefUpdate
mime_type_pref(pref_service
,
185 prefs::kDefaultTasksBySuffix
);
186 for (std::set
<std::string
>::const_iterator iter
= suffixes
.begin();
187 iter
!= suffixes
.end(); ++iter
) {
188 base::StringValue
* value
= new base::StringValue(task_id
);
189 // Suffixes are case insensitive.
190 std::string lower_suffix
= StringToLowerASCII(*iter
);
191 mime_type_pref
->SetWithoutPathExpansion(lower_suffix
, value
);
196 std::string
GetDefaultTaskIdFromPrefs(const PrefService
& pref_service
,
197 const std::string
& mime_type
,
198 const std::string
& suffix
) {
199 VLOG(1) << "Looking for default for MIME type: " << mime_type
200 << " and suffix: " << suffix
;
202 if (!mime_type
.empty()) {
203 const base::DictionaryValue
* mime_task_prefs
=
204 pref_service
.GetDictionary(prefs::kDefaultTasksByMimeType
);
205 DCHECK(mime_task_prefs
);
206 LOG_IF(ERROR
, !mime_task_prefs
) << "Unable to open MIME type prefs";
207 if (mime_task_prefs
&&
208 mime_task_prefs
->GetStringWithoutPathExpansion(mime_type
, &task_id
)) {
209 VLOG(1) << "Found MIME default handler: " << task_id
;
214 const base::DictionaryValue
* suffix_task_prefs
=
215 pref_service
.GetDictionary(prefs::kDefaultTasksBySuffix
);
216 DCHECK(suffix_task_prefs
);
217 LOG_IF(ERROR
, !suffix_task_prefs
) << "Unable to open suffix prefs";
218 std::string lower_suffix
= StringToLowerASCII(suffix
);
219 if (suffix_task_prefs
)
220 suffix_task_prefs
->GetStringWithoutPathExpansion(lower_suffix
, &task_id
);
221 VLOG_IF(1, !task_id
.empty()) << "Found suffix default handler: " << task_id
;
225 std::string
MakeTaskID(const std::string
& app_id
,
227 const std::string
& action_id
) {
228 return base::StringPrintf("%s|%s|%s",
230 TaskTypeToString(task_type
).c_str(),
234 std::string
MakeDriveAppTaskId(const std::string
& app_id
) {
235 return MakeTaskID(app_id
, TASK_TYPE_DRIVE_APP
, kDriveAppActionID
);
238 std::string
TaskDescriptorToId(const TaskDescriptor
& task_descriptor
) {
239 return MakeTaskID(task_descriptor
.app_id
,
240 task_descriptor
.task_type
,
241 task_descriptor
.action_id
);
244 bool ParseTaskID(const std::string
& task_id
, TaskDescriptor
* task
) {
247 std::vector
<std::string
> result
;
248 int count
= Tokenize(task_id
, std::string("|"), &result
);
250 // Parse a legacy task ID that only contain two parts. Drive tasks are
251 // identified by a prefix "drive-app:" on the extension ID. The legacy task
252 // IDs can be stored in preferences.
253 // TODO(satorux): We should get rid of this code: crbug.com/267359.
255 if (StartsWithASCII(result
[0], kDriveTaskExtensionPrefix
, true)) {
256 task
->task_type
= TASK_TYPE_DRIVE_APP
;
257 task
->app_id
= result
[0].substr(kDriveTaskExtensionPrefixLength
);
259 task
->task_type
= TASK_TYPE_FILE_BROWSER_HANDLER
;
260 task
->app_id
= result
[0];
263 task
->action_id
= result
[1];
271 TaskType task_type
= StringToTaskType(result
[1]);
272 if (task_type
== TASK_TYPE_UNKNOWN
)
275 task
->app_id
= result
[0];
276 task
->task_type
= task_type
;
277 task
->action_id
= result
[2];
282 bool ExecuteFileTask(Profile
* profile
,
283 const GURL
& source_url
,
284 const TaskDescriptor
& task
,
285 const std::vector
<FileSystemURL
>& file_urls
,
286 const FileTaskFinishedCallback
& done
) {
287 // drive::FileTaskExecutor is responsible to handle drive tasks.
288 if (task
.task_type
== TASK_TYPE_DRIVE_APP
) {
289 DCHECK_EQ(kDriveAppActionID
, task
.action_id
);
290 drive::FileTaskExecutor
* executor
=
291 new drive::FileTaskExecutor(profile
, task
.app_id
);
292 executor
->Execute(file_urls
, done
);
296 // Get the extension.
297 ExtensionService
* service
=
298 extensions::ExtensionSystem::Get(profile
)->extension_service();
299 const Extension
* extension
= service
?
300 service
->GetExtensionById(task
.app_id
, false) : NULL
;
305 if (task
.task_type
== TASK_TYPE_FILE_BROWSER_HANDLER
) {
306 return file_browser_handlers::ExecuteFileBrowserHandler(
312 } else if (task
.task_type
== TASK_TYPE_FILE_HANDLER
) {
313 for (size_t i
= 0; i
!= file_urls
.size(); ++i
) {
314 apps::LaunchPlatformAppWithFileHandler(
315 profile
, extension
, task
.action_id
, file_urls
[i
].path());
319 done
.Run(extensions::api::file_browser_private::TASK_RESULT_MESSAGE_SENT
);
326 void FindDriveAppTasks(
327 const drive::DriveAppRegistry
& drive_app_registry
,
328 const PathAndMimeTypeSet
& path_mime_set
,
329 std::vector
<FullTaskDescriptor
>* result_list
) {
332 bool is_first
= true;
333 typedef std::map
<std::string
, drive::DriveAppInfo
> DriveAppInfoMap
;
334 DriveAppInfoMap drive_app_map
;
336 for (PathAndMimeTypeSet::const_iterator it
= path_mime_set
.begin();
337 it
!= path_mime_set
.end(); ++it
) {
338 const base::FilePath
& file_path
= it
->first
;
339 const std::string
& mime_type
= it
->second
;
340 // Return immediately if a file not on Drive is found, as Drive app tasks
341 // work only if all files are on Drive.
342 if (!drive::util::IsUnderDriveMountPoint(file_path
))
345 std::vector
<drive::DriveAppInfo
> app_info_list
;
346 drive_app_registry
.GetAppsForFile(file_path
.Extension(),
351 // For the first file, we store all the info.
352 for (size_t j
= 0; j
< app_info_list
.size(); ++j
)
353 drive_app_map
[app_info_list
[j
].app_id
] = app_info_list
[j
];
355 // For remaining files, take the intersection with the current
356 // result, based on the app id.
357 std::set
<std::string
> app_id_set
;
358 for (size_t j
= 0; j
< app_info_list
.size(); ++j
)
359 app_id_set
.insert(app_info_list
[j
].app_id
);
360 for (DriveAppInfoMap::iterator iter
= drive_app_map
.begin();
361 iter
!= drive_app_map
.end();) {
362 if (app_id_set
.count(iter
->first
) == 0) {
363 drive_app_map
.erase(iter
++);
373 for (DriveAppInfoMap::const_iterator iter
= drive_app_map
.begin();
374 iter
!= drive_app_map
.end(); ++iter
) {
375 const drive::DriveAppInfo
& app_info
= iter
->second
;
376 TaskDescriptor
descriptor(app_info
.app_id
,
379 GURL icon_url
= drive::util::FindPreferredIcon(
381 drive::util::kPreferredIconSize
);
382 result_list
->push_back(
383 FullTaskDescriptor(descriptor
,
386 false /* is_default */));
390 void FindFileHandlerTasks(
392 const PathAndMimeTypeSet
& path_mime_set
,
393 std::vector
<FullTaskDescriptor
>* result_list
) {
394 DCHECK(!path_mime_set
.empty());
397 ExtensionService
* service
= profile
->GetExtensionService();
401 for (extensions::ExtensionSet::const_iterator iter
=
402 service
->extensions()->begin();
403 iter
!= service
->extensions()->end();
405 const Extension
* extension
= iter
->get();
407 // We don't support using hosted apps to open files.
408 if (!extension
->is_platform_app())
411 if (profile
->IsOffTheRecord() &&
412 !extensions::util::IsIncognitoEnabled(extension
->id(), profile
))
415 typedef std::vector
<const extensions::FileHandlerInfo
*> FileHandlerList
;
416 FileHandlerList file_handlers
=
417 FindFileHandlersForFiles(*extension
, path_mime_set
);
418 if (file_handlers
.empty())
421 for (FileHandlerList::iterator i
= file_handlers
.begin();
422 i
!= file_handlers
.end(); ++i
) {
423 std::string task_id
= file_tasks::MakeTaskID(
424 extension
->id(), file_tasks::TASK_TYPE_FILE_HANDLER
, (*i
)->id
);
426 GURL best_icon
= extensions::ExtensionIconSource::GetIconURL(
428 drive::util::kPreferredIconSize
,
429 ExtensionIconSet::MATCH_BIGGER
,
433 result_list
->push_back(FullTaskDescriptor(
434 TaskDescriptor(extension
->id(),
435 file_tasks::TASK_TYPE_FILE_HANDLER
,
439 false /* is_default */));
444 void FindFileBrowserHandlerTasks(
446 const std::vector
<GURL
>& file_urls
,
447 std::vector
<FullTaskDescriptor
>* result_list
) {
448 DCHECK(!file_urls
.empty());
451 file_browser_handlers::FileBrowserHandlerList common_tasks
=
452 file_browser_handlers::FindFileBrowserHandlers(profile
, file_urls
);
453 if (common_tasks
.empty())
456 ExtensionService
* service
=
457 extensions::ExtensionSystem::Get(profile
)->extension_service();
458 for (file_browser_handlers::FileBrowserHandlerList::const_iterator iter
=
459 common_tasks
.begin();
460 iter
!= common_tasks
.end();
462 const FileBrowserHandler
* handler
= *iter
;
463 const std::string extension_id
= handler
->extension_id();
464 const Extension
* extension
= service
->GetExtensionById(extension_id
, false);
467 // TODO(zelidrag): Figure out how to expose icon URL that task defined in
468 // manifest instead of the default extension icon.
469 const GURL icon_url
= extensions::ExtensionIconSource::GetIconURL(
471 extension_misc::EXTENSION_ICON_BITTY
,
472 ExtensionIconSet::MATCH_BIGGER
,
476 result_list
->push_back(FullTaskDescriptor(
477 TaskDescriptor(extension_id
,
478 file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER
,
482 false /* is_default */));
486 void FindAllTypesOfTasks(
488 const drive::DriveAppRegistry
* drive_app_registry
,
489 const PathAndMimeTypeSet
& path_mime_set
,
490 const std::vector
<GURL
>& file_urls
,
491 std::vector
<FullTaskDescriptor
>* result_list
) {
495 // Find Drive app tasks, if the drive app registry is present.
496 if (drive_app_registry
)
497 FindDriveAppTasks(*drive_app_registry
, path_mime_set
, result_list
);
499 // Find and append file handler tasks. We know there aren't duplicates
500 // because Drive apps and platform apps are entirely different kinds of
502 FindFileHandlerTasks(profile
, path_mime_set
, result_list
);
504 // Find and append file browser handler tasks. We know there aren't
505 // duplicates because "file_browser_handlers" and "file_handlers" shouldn't
506 // be used in the same manifest.json.
507 FindFileBrowserHandlerTasks(profile
, file_urls
, result_list
);
509 // Google documents can only be handled by internal handlers.
510 if (ContainsGoogleDocument(path_mime_set
))
511 KeepOnlyFileManagerInternalTasks(result_list
);
513 ChooseSuitableVideoHandler(file_urls
, result_list
);
515 ChooseAndSetDefaultTask(*profile
->GetPrefs(), path_mime_set
, result_list
);
518 void ChooseAndSetDefaultTask(const PrefService
& pref_service
,
519 const PathAndMimeTypeSet
& path_mime_set
,
520 std::vector
<FullTaskDescriptor
>* tasks
) {
521 // Collect the task IDs of default tasks from the preferences into a set.
522 std::set
<std::string
> default_task_ids
;
523 for (PathAndMimeTypeSet::const_iterator it
= path_mime_set
.begin();
524 it
!= path_mime_set
.end(); ++it
) {
525 const base::FilePath
& file_path
= it
->first
;
526 const std::string
& mime_type
= it
->second
;
527 std::string task_id
= file_tasks::GetDefaultTaskIdFromPrefs(
528 pref_service
, mime_type
, file_path
.Extension());
529 default_task_ids
.insert(task_id
);
532 // Go through all the tasks from the beginning and see if there is any
533 // default task. If found, pick and set it as default and return.
534 for (size_t i
= 0; i
< tasks
->size(); ++i
) {
535 FullTaskDescriptor
* task
= &tasks
->at(i
);
536 DCHECK(!task
->is_default());
537 const std::string task_id
= TaskDescriptorToId(task
->task_descriptor());
538 if (ContainsKey(default_task_ids
, task_id
)) {
539 task
->set_is_default(true);
544 // No default tasks found. If there is any fallback file browser handler,
545 // make it as default task, so it's selected by default.
546 for (size_t i
= 0; i
< tasks
->size(); ++i
) {
547 FullTaskDescriptor
* task
= &tasks
->at(i
);
548 DCHECK(!task
->is_default());
549 if (file_browser_handlers::IsFallbackFileBrowserHandler(
550 task
->task_descriptor())) {
551 task
->set_is_default(true);
557 } // namespace file_tasks
558 } // namespace file_manager