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/drive_app_registry.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/chromeos/fileapi/file_system_backend.h"
19 #include "chrome/browser/extensions/extension_host.h"
20 #include "chrome/browser/extensions/extension_service.h"
21 #include "chrome/browser/extensions/extension_service.h"
22 #include "chrome/browser/extensions/extension_system.h"
23 #include "chrome/browser/extensions/extension_system.h"
24 #include "chrome/browser/extensions/extension_tab_util.h"
25 #include "chrome/browser/extensions/extension_util.h"
26 #include "chrome/browser/profiles/profile.h"
27 #include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
28 #include "chrome/common/extensions/api/file_browser_handlers/file_browser_handler.h"
29 #include "chrome/common/pref_names.h"
30 #include "webkit/browser/fileapi/file_system_context.h"
31 #include "webkit/browser/fileapi/file_system_url.h"
33 using extensions::Extension;
34 using extensions::app_file_handler_util::FindFileHandlersForFiles;
35 using fileapi::FileSystemURL;
37 namespace file_manager {
38 namespace file_tasks {
42 // The values "file" and "app" are confusing, but cannot be changed easily as
43 // these are used in default task IDs stored in preferences.
45 // TODO(satorux): We should rename them to "file_browser_handler" and
46 // "file_handler" respectively when switching from preferences to
47 // chrome.storage crbug.com/267359
48 const char kFileBrowserHandlerTaskType[] = "file";
49 const char kFileHandlerTaskType[] = "app";
50 const char kDriveAppTaskType[] = "drive";
52 // Drive apps always use the action ID.
53 const char kDriveAppActionID[] = "open-with";
55 // Converts a TaskType to a string.
56 std::string TaskTypeToString(TaskType task_type) {
58 case TASK_TYPE_FILE_BROWSER_HANDLER:
59 return kFileBrowserHandlerTaskType;
60 case TASK_TYPE_FILE_HANDLER:
61 return kFileHandlerTaskType;
62 case TASK_TYPE_DRIVE_APP:
63 return kDriveAppTaskType;
64 case TASK_TYPE_UNKNOWN:
71 // Converts a string to a TaskType. Returns TASK_TYPE_UNKNOWN on error.
72 TaskType StringToTaskType(const std::string& str) {
73 if (str == kFileBrowserHandlerTaskType)
74 return TASK_TYPE_FILE_BROWSER_HANDLER;
75 if (str == kFileHandlerTaskType)
76 return TASK_TYPE_FILE_HANDLER;
77 if (str == kDriveAppTaskType)
78 return TASK_TYPE_DRIVE_APP;
79 return TASK_TYPE_UNKNOWN;
82 // Legacy Drive task extension prefix, used by CrackTaskID.
83 const char kDriveTaskExtensionPrefix[] = "drive-app:";
84 const size_t kDriveTaskExtensionPrefixLength =
85 arraysize(kDriveTaskExtensionPrefix) - 1;
87 // Checks if the file browser extension has permissions for the files in its
88 // file system context.
89 bool FileBrowserHasAccessPermissionForFiles(
91 const GURL& source_url,
92 const std::string& file_browser_id,
93 const std::vector<FileSystemURL>& files) {
94 fileapi::ExternalFileSystemBackend* backend =
95 util::GetFileSystemContextForExtensionId(
96 profile, file_browser_id)->external_backend();
100 for (size_t i = 0; i < files.size(); ++i) {
101 // Make sure this url really being used by the right caller extension.
102 if (source_url.GetOrigin() != files[i].origin())
105 if (!chromeos::FileSystemBackend::CanHandleURL(files[i]) ||
106 !backend->IsAccessAllowed(files[i])) {
114 // Returns true if path_mime_set contains a Google document.
115 bool ContainsGoogleDocument(const PathAndMimeTypeSet& path_mime_set) {
116 for (PathAndMimeTypeSet::const_iterator iter = path_mime_set.begin();
117 iter != path_mime_set.end(); ++iter) {
118 if (google_apis::ResourceEntry::ClassifyEntryKindByFileExtension(
120 google_apis::ResourceEntry::KIND_OF_GOOGLE_DOCUMENT) {
127 // Leaves tasks handled by the file manger itself as is and removes all others.
128 void KeepOnlyFileManagerInternalTasks(std::vector<FullTaskDescriptor>* tasks) {
129 std::vector<FullTaskDescriptor> filtered;
130 for (size_t i = 0; i < tasks->size(); ++i) {
131 if ((*tasks)[i].task_descriptor().app_id == kFileManagerAppId)
132 filtered.push_back((*tasks)[i]);
134 tasks->swap(filtered);
139 FullTaskDescriptor::FullTaskDescriptor(
140 const TaskDescriptor& task_descriptor,
141 const std::string& task_title,
142 const GURL& icon_url,
144 : task_descriptor_(task_descriptor),
145 task_title_(task_title),
147 is_default_(is_default){
150 void UpdateDefaultTask(PrefService* pref_service,
151 const std::string& task_id,
152 const std::set<std::string>& suffixes,
153 const std::set<std::string>& mime_types) {
157 if (!mime_types.empty()) {
158 DictionaryPrefUpdate mime_type_pref(pref_service,
159 prefs::kDefaultTasksByMimeType);
160 for (std::set<std::string>::const_iterator iter = mime_types.begin();
161 iter != mime_types.end(); ++iter) {
162 base::StringValue* value = new base::StringValue(task_id);
163 mime_type_pref->SetWithoutPathExpansion(*iter, value);
167 if (!suffixes.empty()) {
168 DictionaryPrefUpdate mime_type_pref(pref_service,
169 prefs::kDefaultTasksBySuffix);
170 for (std::set<std::string>::const_iterator iter = suffixes.begin();
171 iter != suffixes.end(); ++iter) {
172 base::StringValue* value = new base::StringValue(task_id);
173 // Suffixes are case insensitive.
174 std::string lower_suffix = StringToLowerASCII(*iter);
175 mime_type_pref->SetWithoutPathExpansion(lower_suffix, value);
180 std::string GetDefaultTaskIdFromPrefs(const PrefService& pref_service,
181 const std::string& mime_type,
182 const std::string& suffix) {
183 VLOG(1) << "Looking for default for MIME type: " << mime_type
184 << " and suffix: " << suffix;
186 if (!mime_type.empty()) {
187 const DictionaryValue* mime_task_prefs =
188 pref_service.GetDictionary(prefs::kDefaultTasksByMimeType);
189 DCHECK(mime_task_prefs);
190 LOG_IF(ERROR, !mime_task_prefs) << "Unable to open MIME type prefs";
191 if (mime_task_prefs &&
192 mime_task_prefs->GetStringWithoutPathExpansion(mime_type, &task_id)) {
193 VLOG(1) << "Found MIME default handler: " << task_id;
198 const DictionaryValue* suffix_task_prefs =
199 pref_service.GetDictionary(prefs::kDefaultTasksBySuffix);
200 DCHECK(suffix_task_prefs);
201 LOG_IF(ERROR, !suffix_task_prefs) << "Unable to open suffix prefs";
202 std::string lower_suffix = StringToLowerASCII(suffix);
203 if (suffix_task_prefs)
204 suffix_task_prefs->GetStringWithoutPathExpansion(lower_suffix, &task_id);
205 VLOG_IF(1, !task_id.empty()) << "Found suffix default handler: " << task_id;
209 std::string MakeTaskID(const std::string& app_id,
211 const std::string& action_id) {
212 return base::StringPrintf("%s|%s|%s",
214 TaskTypeToString(task_type).c_str(),
218 std::string MakeDriveAppTaskId(const std::string& app_id) {
219 return MakeTaskID(app_id, TASK_TYPE_DRIVE_APP, kDriveAppActionID);
222 std::string TaskDescriptorToId(const TaskDescriptor& task_descriptor) {
223 return MakeTaskID(task_descriptor.app_id,
224 task_descriptor.task_type,
225 task_descriptor.action_id);
228 bool ParseTaskID(const std::string& task_id, TaskDescriptor* task) {
231 std::vector<std::string> result;
232 int count = Tokenize(task_id, std::string("|"), &result);
234 // Parse a legacy task ID that only contain two parts. Drive tasks are
235 // identified by a prefix "drive-app:" on the extension ID. The legacy task
236 // IDs can be stored in preferences.
237 // TODO(satorux): We should get rid of this code: crbug.com/267359.
239 if (StartsWithASCII(result[0], kDriveTaskExtensionPrefix, true)) {
240 task->task_type = TASK_TYPE_DRIVE_APP;
241 task->app_id = result[0].substr(kDriveTaskExtensionPrefixLength);
243 task->task_type = TASK_TYPE_FILE_BROWSER_HANDLER;
244 task->app_id = result[0];
247 task->action_id = result[1];
255 TaskType task_type = StringToTaskType(result[1]);
256 if (task_type == TASK_TYPE_UNKNOWN)
259 task->app_id = result[0];
260 task->task_type = task_type;
261 task->action_id = result[2];
266 bool ExecuteFileTask(Profile* profile,
267 const GURL& source_url,
268 const std::string& app_id,
270 const TaskDescriptor& task,
271 const std::vector<FileSystemURL>& file_urls,
272 const FileTaskFinishedCallback& done) {
273 if (!FileBrowserHasAccessPermissionForFiles(profile, source_url,
277 // drive::FileTaskExecutor is responsible to handle drive tasks.
278 if (task.task_type == TASK_TYPE_DRIVE_APP) {
279 DCHECK_EQ(kDriveAppActionID, task.action_id);
280 drive::FileTaskExecutor* executor =
281 new drive::FileTaskExecutor(profile, task.app_id);
282 executor->Execute(file_urls, done);
286 // Get the extension.
287 ExtensionService* service =
288 extensions::ExtensionSystem::Get(profile)->extension_service();
289 const Extension* extension = service ?
290 service->GetExtensionById(task.app_id, false) : NULL;
295 if (task.task_type == TASK_TYPE_FILE_BROWSER_HANDLER) {
296 return file_browser_handlers::ExecuteFileBrowserHandler(
303 } else if (task.task_type == TASK_TYPE_FILE_HANDLER) {
304 for (size_t i = 0; i != file_urls.size(); ++i) {
305 apps::LaunchPlatformAppWithFileHandler(
306 profile, extension, task.action_id, file_urls[i].path());
317 void FindDriveAppTasks(
318 const drive::DriveAppRegistry& drive_app_registry,
319 const PathAndMimeTypeSet& path_mime_set,
320 std::vector<FullTaskDescriptor>* result_list) {
323 bool is_first = true;
324 typedef std::map<std::string, drive::DriveAppInfo> DriveAppInfoMap;
325 DriveAppInfoMap drive_app_map;
327 for (PathAndMimeTypeSet::const_iterator it = path_mime_set.begin();
328 it != path_mime_set.end(); ++it) {
329 const base::FilePath& file_path = it->first;
330 const std::string& mime_type = it->second;
331 // Return immediately if a file not on Drive is found, as Drive app tasks
332 // work only if all files are on Drive.
333 if (!drive::util::IsUnderDriveMountPoint(file_path))
336 ScopedVector<drive::DriveAppInfo> app_info_list;
337 drive_app_registry.GetAppsForFile(file_path.Extension(),
342 // For the first file, we store all the info.
343 for (size_t j = 0; j < app_info_list.size(); ++j) {
344 const drive::DriveAppInfo& app_info = *app_info_list[j];
345 drive_app_map[app_info.app_id] = app_info;
348 // For remaining files, take the intersection with the current
349 // result, based on the app id.
350 std::set<std::string> app_id_set;
351 for (size_t j = 0; j < app_info_list.size(); ++j)
352 app_id_set.insert(app_info_list[j]->app_id);
353 for (DriveAppInfoMap::iterator iter = drive_app_map.begin();
354 iter != drive_app_map.end();) {
355 if (app_id_set.count(iter->first) == 0) {
356 drive_app_map.erase(iter++);
366 for (DriveAppInfoMap::const_iterator iter = drive_app_map.begin();
367 iter != drive_app_map.end(); ++iter) {
368 const drive::DriveAppInfo& app_info = iter->second;
369 TaskDescriptor descriptor(app_info.app_id,
372 GURL icon_url = drive::util::FindPreferredIcon(
374 drive::util::kPreferredIconSize);
375 result_list->push_back(
376 FullTaskDescriptor(descriptor,
379 false /* is_default */));
383 void FindFileHandlerTasks(
385 const PathAndMimeTypeSet& path_mime_set,
386 std::vector<FullTaskDescriptor>* result_list) {
387 DCHECK(!path_mime_set.empty());
390 ExtensionService* service = profile->GetExtensionService();
394 for (ExtensionSet::const_iterator iter = service->extensions()->begin();
395 iter != service->extensions()->end();
397 const Extension* extension = iter->get();
399 // We don't support using hosted apps to open files.
400 if (!extension->is_platform_app())
403 if (profile->IsOffTheRecord() &&
404 !extension_util::IsIncognitoEnabled(extension->id(), service))
407 typedef std::vector<const extensions::FileHandlerInfo*> FileHandlerList;
408 FileHandlerList file_handlers =
409 FindFileHandlersForFiles(*extension, path_mime_set);
410 if (file_handlers.empty())
413 for (FileHandlerList::iterator i = file_handlers.begin();
414 i != file_handlers.end(); ++i) {
415 std::string task_id = file_tasks::MakeTaskID(
416 extension->id(), file_tasks::TASK_TYPE_FILE_HANDLER, (*i)->id);
418 GURL best_icon = extensions::ExtensionIconSource::GetIconURL(
420 drive::util::kPreferredIconSize,
421 ExtensionIconSet::MATCH_BIGGER,
425 result_list->push_back(FullTaskDescriptor(
426 TaskDescriptor(extension->id(),
427 file_tasks::TASK_TYPE_FILE_HANDLER,
431 false /* is_default */));
436 void FindFileBrowserHandlerTasks(
438 const std::vector<GURL>& file_urls,
439 std::vector<FullTaskDescriptor>* result_list) {
440 DCHECK(!file_urls.empty());
443 file_browser_handlers::FileBrowserHandlerList common_tasks =
444 file_browser_handlers::FindFileBrowserHandlers(profile, file_urls);
445 if (common_tasks.empty())
448 ExtensionService* service =
449 extensions::ExtensionSystem::Get(profile)->extension_service();
450 for (file_browser_handlers::FileBrowserHandlerList::const_iterator iter =
451 common_tasks.begin();
452 iter != common_tasks.end();
454 const FileBrowserHandler* handler = *iter;
455 const std::string extension_id = handler->extension_id();
456 const Extension* extension = service->GetExtensionById(extension_id, false);
459 // TODO(zelidrag): Figure out how to expose icon URL that task defined in
460 // manifest instead of the default extension icon.
461 const GURL icon_url = extensions::ExtensionIconSource::GetIconURL(
463 extension_misc::EXTENSION_ICON_BITTY,
464 ExtensionIconSet::MATCH_BIGGER,
468 result_list->push_back(FullTaskDescriptor(
469 TaskDescriptor(extension_id,
470 file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER,
474 false /* is_default */));
478 void FindAllTypesOfTasks(
480 const drive::DriveAppRegistry* drive_app_registry,
481 const PathAndMimeTypeSet& path_mime_set,
482 const std::vector<GURL>& file_urls,
483 std::vector<FullTaskDescriptor>* result_list) {
487 // Find Drive app tasks, if the drive app registry is present.
488 if (drive_app_registry)
489 FindDriveAppTasks(*drive_app_registry, path_mime_set, result_list);
491 // Find and append file handler tasks. We know there aren't duplicates
492 // because Drive apps and platform apps are entirely different kinds of
494 FindFileHandlerTasks(profile, path_mime_set, result_list);
496 // Find and append file browser handler tasks. We know there aren't
497 // duplicates because "file_browser_handlers" and "file_handlers" shouldn't
498 // be used in the same manifest.json.
499 FindFileBrowserHandlerTasks(profile, file_urls, result_list);
501 // Google documents can only be handled by internal handlers.
502 if (ContainsGoogleDocument(path_mime_set))
503 KeepOnlyFileManagerInternalTasks(result_list);
505 ChooseAndSetDefaultTask(*profile->GetPrefs(), path_mime_set, result_list);
508 void ChooseAndSetDefaultTask(const PrefService& pref_service,
509 const PathAndMimeTypeSet& path_mime_set,
510 std::vector<FullTaskDescriptor>* tasks) {
511 // Collect the task IDs of default tasks from the preferences into a set.
512 std::set<std::string> default_task_ids;
513 for (PathAndMimeTypeSet::const_iterator it = path_mime_set.begin();
514 it != path_mime_set.end(); ++it) {
515 const base::FilePath& file_path = it->first;
516 const std::string& mime_type = it->second;
517 std::string task_id = file_tasks::GetDefaultTaskIdFromPrefs(
518 pref_service, mime_type, file_path.Extension());
519 default_task_ids.insert(task_id);
522 // Go through all the tasks from the beginning and see if there is any
523 // default task. If found, pick and set it as default and return.
524 for (size_t i = 0; i < tasks->size(); ++i) {
525 FullTaskDescriptor* task = &tasks->at(i);
526 DCHECK(!task->is_default());
527 const std::string task_id = TaskDescriptorToId(task->task_descriptor());
528 if (ContainsKey(default_task_ids, task_id)) {
529 task->set_is_default(true);
534 // No default tasks found. If there is any fallback file browser handler,
535 // make it as default task, so it's selected by default.
536 for (size_t i = 0; i < tasks->size(); ++i) {
537 FullTaskDescriptor* task = &tasks->at(i);
538 DCHECK(!task->is_default());
539 if (file_browser_handlers::IsFallbackFileBrowserHandler(
540 task->task_descriptor())) {
541 task->set_is_default(true);
547 } // namespace file_tasks
548 } // namespace file_manager