OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "chrome/browser/chromeos/extensions/file_manager/file_tasks.h" | |
6 | |
7 #include "apps/launcher.h" | |
8 #include "base/bind.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/drive_integration_service.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/extensions/file_manager/file_browser_handlers.
h" | |
16 #include "chrome/browser/chromeos/extensions/file_manager/fileapi_util.h" | |
17 #include "chrome/browser/chromeos/extensions/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/profiles/profile.h" | |
26 #include "chrome/browser/ui/webui/extensions/extension_icon_source.h" | |
27 #include "chrome/common/extensions/api/file_browser_handlers/file_browser_handle
r.h" | |
28 #include "chrome/common/pref_names.h" | |
29 #include "webkit/browser/fileapi/file_system_context.h" | |
30 #include "webkit/browser/fileapi/file_system_url.h" | |
31 | |
32 using extensions::Extension; | |
33 using extensions::app_file_handler_util::FindFileHandlersForFiles; | |
34 using fileapi::FileSystemURL; | |
35 | |
36 namespace file_manager { | |
37 namespace file_tasks { | |
38 | |
39 namespace { | |
40 | |
41 // The values "file" and "app" are confusing, but cannot be changed easily as | |
42 // these are used in default task IDs stored in preferences. | |
43 // | |
44 // TODO(satorux): We should rename them to "file_browser_handler" and | |
45 // "file_handler" respectively when switching from preferences to | |
46 // chrome.storage crbug.com/267359 | |
47 const char kFileBrowserHandlerTaskType[] = "file"; | |
48 const char kFileHandlerTaskType[] = "app"; | |
49 const char kDriveAppTaskType[] = "drive"; | |
50 | |
51 // Drive apps always use the action ID. | |
52 const char kDriveAppActionID[] = "open-with"; | |
53 | |
54 // Default icon path for drive docs. | |
55 const char kDefaultIcon[] = "images/filetype_generic.png"; | |
56 | |
57 // Converts a TaskType to a string. | |
58 std::string TaskTypeToString(TaskType task_type) { | |
59 switch (task_type) { | |
60 case TASK_TYPE_FILE_BROWSER_HANDLER: | |
61 return kFileBrowserHandlerTaskType; | |
62 case TASK_TYPE_FILE_HANDLER: | |
63 return kFileHandlerTaskType; | |
64 case TASK_TYPE_DRIVE_APP: | |
65 return kDriveAppTaskType; | |
66 case TASK_TYPE_UNKNOWN: | |
67 break; | |
68 } | |
69 NOTREACHED(); | |
70 return ""; | |
71 } | |
72 | |
73 // Converts a string to a TaskType. Returns TASK_TYPE_UNKNOWN on error. | |
74 TaskType StringToTaskType(const std::string& str) { | |
75 if (str == kFileBrowserHandlerTaskType) | |
76 return TASK_TYPE_FILE_BROWSER_HANDLER; | |
77 if (str == kFileHandlerTaskType) | |
78 return TASK_TYPE_FILE_HANDLER; | |
79 if (str == kDriveAppTaskType) | |
80 return TASK_TYPE_DRIVE_APP; | |
81 return TASK_TYPE_UNKNOWN; | |
82 } | |
83 | |
84 // Legacy Drive task extension prefix, used by CrackTaskID. | |
85 const char kDriveTaskExtensionPrefix[] = "drive-app:"; | |
86 const size_t kDriveTaskExtensionPrefixLength = | |
87 arraysize(kDriveTaskExtensionPrefix) - 1; | |
88 | |
89 // Checks if the file browser extension has permissions for the files in its | |
90 // file system context. | |
91 bool FileBrowserHasAccessPermissionForFiles( | |
92 Profile* profile, | |
93 const GURL& source_url, | |
94 const std::string& file_browser_id, | |
95 const std::vector<FileSystemURL>& files) { | |
96 fileapi::ExternalFileSystemBackend* backend = | |
97 util::GetFileSystemContextForExtensionId( | |
98 profile, file_browser_id)->external_backend(); | |
99 if (!backend) | |
100 return false; | |
101 | |
102 for (size_t i = 0; i < files.size(); ++i) { | |
103 // Make sure this url really being used by the right caller extension. | |
104 if (source_url.GetOrigin() != files[i].origin()) | |
105 return false; | |
106 | |
107 if (!chromeos::FileSystemBackend::CanHandleURL(files[i]) || | |
108 !backend->IsAccessAllowed(files[i])) { | |
109 return false; | |
110 } | |
111 } | |
112 | |
113 return true; | |
114 } | |
115 | |
116 } // namespace | |
117 | |
118 FullTaskDescriptor::FullTaskDescriptor( | |
119 const TaskDescriptor& task_descriptor, | |
120 const std::string& task_title, | |
121 const GURL& icon_url, | |
122 bool is_default) | |
123 : task_descriptor_(task_descriptor), | |
124 task_title_(task_title), | |
125 icon_url_(icon_url), | |
126 is_default_(is_default){ | |
127 } | |
128 | |
129 scoped_ptr<base::DictionaryValue> | |
130 FullTaskDescriptor::AsDictionaryValue() const { | |
131 scoped_ptr<base::DictionaryValue> dictionary(new base::DictionaryValue); | |
132 dictionary->SetString("taskId", TaskDescriptorToId(task_descriptor_)); | |
133 if (!icon_url_.is_empty()) | |
134 dictionary->SetString("iconUrl", icon_url_.spec()); | |
135 dictionary->SetString("title", task_title_); | |
136 dictionary->SetBoolean("isDefault", is_default_); | |
137 return dictionary.Pass(); | |
138 } | |
139 | |
140 void UpdateDefaultTask(PrefService* pref_service, | |
141 const std::string& task_id, | |
142 const std::set<std::string>& suffixes, | |
143 const std::set<std::string>& mime_types) { | |
144 if (!pref_service) | |
145 return; | |
146 | |
147 if (!mime_types.empty()) { | |
148 DictionaryPrefUpdate mime_type_pref(pref_service, | |
149 prefs::kDefaultTasksByMimeType); | |
150 for (std::set<std::string>::const_iterator iter = mime_types.begin(); | |
151 iter != mime_types.end(); ++iter) { | |
152 base::StringValue* value = new base::StringValue(task_id); | |
153 mime_type_pref->SetWithoutPathExpansion(*iter, value); | |
154 } | |
155 } | |
156 | |
157 if (!suffixes.empty()) { | |
158 DictionaryPrefUpdate mime_type_pref(pref_service, | |
159 prefs::kDefaultTasksBySuffix); | |
160 for (std::set<std::string>::const_iterator iter = suffixes.begin(); | |
161 iter != suffixes.end(); ++iter) { | |
162 base::StringValue* value = new base::StringValue(task_id); | |
163 // Suffixes are case insensitive. | |
164 std::string lower_suffix = StringToLowerASCII(*iter); | |
165 mime_type_pref->SetWithoutPathExpansion(lower_suffix, value); | |
166 } | |
167 } | |
168 } | |
169 | |
170 std::string GetDefaultTaskIdFromPrefs(const PrefService& pref_service, | |
171 const std::string& mime_type, | |
172 const std::string& suffix) { | |
173 VLOG(1) << "Looking for default for MIME type: " << mime_type | |
174 << " and suffix: " << suffix; | |
175 std::string task_id; | |
176 if (!mime_type.empty()) { | |
177 const DictionaryValue* mime_task_prefs = | |
178 pref_service.GetDictionary(prefs::kDefaultTasksByMimeType); | |
179 DCHECK(mime_task_prefs); | |
180 LOG_IF(ERROR, !mime_task_prefs) << "Unable to open MIME type prefs"; | |
181 if (mime_task_prefs && | |
182 mime_task_prefs->GetStringWithoutPathExpansion(mime_type, &task_id)) { | |
183 VLOG(1) << "Found MIME default handler: " << task_id; | |
184 return task_id; | |
185 } | |
186 } | |
187 | |
188 const DictionaryValue* suffix_task_prefs = | |
189 pref_service.GetDictionary(prefs::kDefaultTasksBySuffix); | |
190 DCHECK(suffix_task_prefs); | |
191 LOG_IF(ERROR, !suffix_task_prefs) << "Unable to open suffix prefs"; | |
192 std::string lower_suffix = StringToLowerASCII(suffix); | |
193 if (suffix_task_prefs) | |
194 suffix_task_prefs->GetStringWithoutPathExpansion(lower_suffix, &task_id); | |
195 VLOG_IF(1, !task_id.empty()) << "Found suffix default handler: " << task_id; | |
196 return task_id; | |
197 } | |
198 | |
199 std::string MakeTaskID(const std::string& app_id, | |
200 TaskType task_type, | |
201 const std::string& action_id) { | |
202 return base::StringPrintf("%s|%s|%s", | |
203 app_id.c_str(), | |
204 TaskTypeToString(task_type).c_str(), | |
205 action_id.c_str()); | |
206 } | |
207 | |
208 std::string MakeDriveAppTaskId(const std::string& app_id) { | |
209 return MakeTaskID(app_id, TASK_TYPE_DRIVE_APP, kDriveAppActionID); | |
210 } | |
211 | |
212 std::string TaskDescriptorToId(const TaskDescriptor& task_descriptor) { | |
213 return MakeTaskID(task_descriptor.app_id, | |
214 task_descriptor.task_type, | |
215 task_descriptor.action_id); | |
216 } | |
217 | |
218 bool ParseTaskID(const std::string& task_id, TaskDescriptor* task) { | |
219 DCHECK(task); | |
220 | |
221 std::vector<std::string> result; | |
222 int count = Tokenize(task_id, std::string("|"), &result); | |
223 | |
224 // Parse a legacy task ID that only contain two parts. Drive tasks are | |
225 // identified by a prefix "drive-app:" on the extension ID. The legacy task | |
226 // IDs can be stored in preferences. | |
227 // TODO(satorux): We should get rid of this code: crbug.com/267359. | |
228 if (count == 2) { | |
229 if (StartsWithASCII(result[0], kDriveTaskExtensionPrefix, true)) { | |
230 task->task_type = TASK_TYPE_DRIVE_APP; | |
231 task->app_id = result[0].substr(kDriveTaskExtensionPrefixLength); | |
232 } else { | |
233 task->task_type = TASK_TYPE_FILE_BROWSER_HANDLER; | |
234 task->app_id = result[0]; | |
235 } | |
236 | |
237 task->action_id = result[1]; | |
238 | |
239 return true; | |
240 } | |
241 | |
242 if (count != 3) | |
243 return false; | |
244 | |
245 TaskType task_type = StringToTaskType(result[1]); | |
246 if (task_type == TASK_TYPE_UNKNOWN) | |
247 return false; | |
248 | |
249 task->app_id = result[0]; | |
250 task->task_type = task_type; | |
251 task->action_id = result[2]; | |
252 | |
253 return true; | |
254 } | |
255 | |
256 bool ExecuteFileTask(Profile* profile, | |
257 const GURL& source_url, | |
258 const std::string& app_id, | |
259 int32 tab_id, | |
260 const TaskDescriptor& task, | |
261 const std::vector<FileSystemURL>& file_urls, | |
262 const FileTaskFinishedCallback& done) { | |
263 if (!FileBrowserHasAccessPermissionForFiles(profile, source_url, | |
264 app_id, file_urls)) | |
265 return false; | |
266 | |
267 // drive::FileTaskExecutor is responsible to handle drive tasks. | |
268 if (task.task_type == TASK_TYPE_DRIVE_APP) { | |
269 DCHECK_EQ(kDriveAppActionID, task.action_id); | |
270 drive::FileTaskExecutor* executor = | |
271 new drive::FileTaskExecutor(profile, task.app_id); | |
272 executor->Execute(file_urls, done); | |
273 return true; | |
274 } | |
275 | |
276 // Get the extension. | |
277 ExtensionService* service = | |
278 extensions::ExtensionSystem::Get(profile)->extension_service(); | |
279 const Extension* extension = service ? | |
280 service->GetExtensionById(task.app_id, false) : NULL; | |
281 if (!extension) | |
282 return false; | |
283 | |
284 // Execute the task. | |
285 if (task.task_type == TASK_TYPE_FILE_BROWSER_HANDLER) { | |
286 return file_browser_handlers::ExecuteFileBrowserHandler( | |
287 profile, | |
288 extension, | |
289 tab_id, | |
290 task.action_id, | |
291 file_urls, | |
292 done); | |
293 } else if (task.task_type == TASK_TYPE_FILE_HANDLER) { | |
294 for (size_t i = 0; i != file_urls.size(); ++i) { | |
295 apps::LaunchPlatformAppWithFileHandler( | |
296 profile, extension, task.action_id, file_urls[i].path()); | |
297 } | |
298 | |
299 if (!done.is_null()) | |
300 done.Run(true); | |
301 return true; | |
302 } | |
303 NOTREACHED(); | |
304 return false; | |
305 } | |
306 | |
307 void FindDriveAppTasks( | |
308 const drive::DriveAppRegistry& drive_app_registry, | |
309 const PathAndMimeTypeSet& path_mime_set, | |
310 std::vector<FullTaskDescriptor>* result_list) { | |
311 DCHECK(result_list); | |
312 | |
313 bool is_first = true; | |
314 typedef std::map<std::string, drive::DriveAppInfo> DriveAppInfoMap; | |
315 DriveAppInfoMap drive_app_map; | |
316 | |
317 for (PathAndMimeTypeSet::const_iterator it = path_mime_set.begin(); | |
318 it != path_mime_set.end(); ++it) { | |
319 const base::FilePath& file_path = it->first; | |
320 const std::string& mime_type = it->second; | |
321 // Return immediately if a file not on Drive is found, as Drive app tasks | |
322 // work only if all files are on Drive. | |
323 if (!drive::util::IsUnderDriveMountPoint(file_path)) | |
324 return; | |
325 | |
326 ScopedVector<drive::DriveAppInfo> app_info_list; | |
327 drive_app_registry.GetAppsForFile(file_path.Extension(), | |
328 mime_type, | |
329 &app_info_list); | |
330 | |
331 if (is_first) { | |
332 // For the first file, we store all the info. | |
333 for (size_t j = 0; j < app_info_list.size(); ++j) { | |
334 const drive::DriveAppInfo& app_info = *app_info_list[j]; | |
335 drive_app_map[app_info.app_id] = app_info; | |
336 } | |
337 } else { | |
338 // For remaining files, take the intersection with the current | |
339 // result, based on the app id. | |
340 std::set<std::string> app_id_set; | |
341 for (size_t j = 0; j < app_info_list.size(); ++j) | |
342 app_id_set.insert(app_info_list[j]->app_id); | |
343 for (DriveAppInfoMap::iterator iter = drive_app_map.begin(); | |
344 iter != drive_app_map.end();) { | |
345 if (app_id_set.count(iter->first) == 0) { | |
346 drive_app_map.erase(iter++); | |
347 } else { | |
348 ++iter; | |
349 } | |
350 } | |
351 } | |
352 | |
353 is_first = false; | |
354 } | |
355 | |
356 for (DriveAppInfoMap::const_iterator iter = drive_app_map.begin(); | |
357 iter != drive_app_map.end(); ++iter) { | |
358 const drive::DriveAppInfo& app_info = iter->second; | |
359 TaskDescriptor descriptor(app_info.app_id, | |
360 TASK_TYPE_DRIVE_APP, | |
361 kDriveAppActionID); | |
362 GURL icon_url = drive::util::FindPreferredIcon( | |
363 app_info.app_icons, | |
364 drive::util::kPreferredIconSize); | |
365 result_list->push_back( | |
366 FullTaskDescriptor(descriptor, | |
367 app_info.app_name, | |
368 icon_url, | |
369 false /* is_default */)); | |
370 } | |
371 } | |
372 | |
373 void FindFileHandlerTasks( | |
374 Profile* profile, | |
375 const PathAndMimeTypeSet& path_mime_set, | |
376 std::vector<FullTaskDescriptor>* result_list) { | |
377 DCHECK(!path_mime_set.empty()); | |
378 DCHECK(result_list); | |
379 | |
380 ExtensionService* service = profile->GetExtensionService(); | |
381 if (!service) | |
382 return; | |
383 | |
384 for (ExtensionSet::const_iterator iter = service->extensions()->begin(); | |
385 iter != service->extensions()->end(); | |
386 ++iter) { | |
387 const Extension* extension = iter->get(); | |
388 | |
389 // We don't support using hosted apps to open files. | |
390 if (!extension->is_platform_app()) | |
391 continue; | |
392 | |
393 if (profile->IsOffTheRecord() && | |
394 !service->IsIncognitoEnabled(extension->id())) | |
395 continue; | |
396 | |
397 typedef std::vector<const extensions::FileHandlerInfo*> FileHandlerList; | |
398 FileHandlerList file_handlers = | |
399 FindFileHandlersForFiles(*extension, path_mime_set); | |
400 if (file_handlers.empty()) | |
401 continue; | |
402 | |
403 for (FileHandlerList::iterator i = file_handlers.begin(); | |
404 i != file_handlers.end(); ++i) { | |
405 std::string task_id = file_tasks::MakeTaskID( | |
406 extension->id(), file_tasks::TASK_TYPE_FILE_HANDLER, (*i)->id); | |
407 | |
408 GURL best_icon = extensions::ExtensionIconSource::GetIconURL( | |
409 extension, | |
410 drive::util::kPreferredIconSize, | |
411 ExtensionIconSet::MATCH_BIGGER, | |
412 false, // grayscale | |
413 NULL); // exists | |
414 | |
415 result_list->push_back(FullTaskDescriptor( | |
416 TaskDescriptor(extension->id(), | |
417 file_tasks::TASK_TYPE_FILE_HANDLER, | |
418 (*i)->id), | |
419 (*i)->title, | |
420 best_icon, | |
421 false /* is_default */)); | |
422 } | |
423 } | |
424 } | |
425 | |
426 void FindFileBrowserHandlerTasks( | |
427 Profile* profile, | |
428 const std::vector<GURL>& file_urls, | |
429 std::vector<FullTaskDescriptor>* result_list) { | |
430 DCHECK(!file_urls.empty()); | |
431 DCHECK(result_list); | |
432 | |
433 file_browser_handlers::FileBrowserHandlerList common_tasks = | |
434 file_browser_handlers::FindCommonFileBrowserHandlers(profile, file_urls); | |
435 if (common_tasks.empty()) | |
436 return; | |
437 | |
438 ExtensionService* service = | |
439 extensions::ExtensionSystem::Get(profile)->extension_service(); | |
440 for (file_browser_handlers::FileBrowserHandlerList::const_iterator iter = | |
441 common_tasks.begin(); | |
442 iter != common_tasks.end(); | |
443 ++iter) { | |
444 const FileBrowserHandler* handler = *iter; | |
445 const std::string extension_id = handler->extension_id(); | |
446 const Extension* extension = service->GetExtensionById(extension_id, false); | |
447 DCHECK(extension); | |
448 | |
449 // TODO(zelidrag): Figure out how to expose icon URL that task defined in | |
450 // manifest instead of the default extension icon. | |
451 const GURL icon_url = extensions::ExtensionIconSource::GetIconURL( | |
452 extension, | |
453 extension_misc::EXTENSION_ICON_BITTY, | |
454 ExtensionIconSet::MATCH_BIGGER, | |
455 false, // grayscale | |
456 NULL); // exists | |
457 | |
458 result_list->push_back(FullTaskDescriptor( | |
459 TaskDescriptor(extension_id, | |
460 file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER, | |
461 handler->id()), | |
462 handler->title(), | |
463 icon_url, | |
464 false /* is_default */)); | |
465 } | |
466 } | |
467 | |
468 void FindAllTypesOfTasks( | |
469 Profile* profile, | |
470 const PathAndMimeTypeSet& path_mime_set, | |
471 const std::vector<GURL>& file_urls, | |
472 std::vector<FullTaskDescriptor>* result_list) { | |
473 DCHECK(profile); | |
474 DCHECK(result_list); | |
475 | |
476 // Check if file_paths contain a google document. | |
477 bool has_google_document = false; | |
478 for (PathAndMimeTypeSet::const_iterator iter = path_mime_set.begin(); | |
479 iter != path_mime_set.end(); ++iter) { | |
480 if (google_apis::ResourceEntry::ClassifyEntryKindByFileExtension( | |
481 iter->first) & | |
482 google_apis::ResourceEntry::KIND_OF_GOOGLE_DOCUMENT) { | |
483 has_google_document = true; | |
484 break; | |
485 } | |
486 } | |
487 | |
488 // Google document are not opened by drive apps but file manager. | |
489 if (!has_google_document) { | |
490 drive::DriveIntegrationService* integration_service = | |
491 drive::DriveIntegrationServiceFactory::GetForProfile(profile); | |
492 // |integration_service| is NULL if Drive is disabled. | |
493 if (!integration_service || !integration_service->drive_app_registry()) | |
494 return; | |
495 | |
496 FindDriveAppTasks(*integration_service->drive_app_registry(), | |
497 path_mime_set, | |
498 result_list); | |
499 } | |
500 | |
501 // Find and append file handler tasks. We know there aren't duplicates | |
502 // because Drive apps and platform apps are entirely different kinds of | |
503 // tasks. | |
504 FindFileHandlerTasks(profile, | |
505 path_mime_set, | |
506 result_list); | |
507 | |
508 // Find and append file browser handler tasks. We know there aren't | |
509 // duplicates because "file_browser_handlers" and "file_handlers" shouldn't | |
510 // be used in the same manifest.json. | |
511 FindFileBrowserHandlerTasks(profile, | |
512 file_urls, | |
513 result_list); | |
514 | |
515 ChooseAndSetDefaultTask(*profile->GetPrefs(), | |
516 path_mime_set, | |
517 result_list); | |
518 } | |
519 | |
520 void ChooseAndSetDefaultTask(const PrefService& pref_service, | |
521 const PathAndMimeTypeSet& path_mime_set, | |
522 std::vector<FullTaskDescriptor>* tasks) { | |
523 // Collect the task IDs of default tasks from the preferences into a set. | |
524 std::set<std::string> default_task_ids; | |
525 for (PathAndMimeTypeSet::const_iterator it = path_mime_set.begin(); | |
526 it != path_mime_set.end(); ++it) { | |
527 const base::FilePath& file_path = it->first; | |
528 const std::string& mime_type = it->second; | |
529 std::string task_id = file_tasks::GetDefaultTaskIdFromPrefs( | |
530 pref_service, mime_type, file_path.Extension()); | |
531 default_task_ids.insert(task_id); | |
532 } | |
533 | |
534 // Go through all the tasks from the beginning and see if there is any | |
535 // default task. If found, pick and set it as default and return. | |
536 for (size_t i = 0; i < tasks->size(); ++i) { | |
537 FullTaskDescriptor* task = &tasks->at(i); | |
538 DCHECK(!task->is_default()); | |
539 const std::string task_id = TaskDescriptorToId(task->task_descriptor()); | |
540 if (ContainsKey(default_task_ids, task_id)) { | |
541 task->set_is_default(true); | |
542 return; | |
543 } | |
544 } | |
545 | |
546 // No default tasks found. If there is any fallback file browser handler, | |
547 // make it as default task, so it's selected by default. | |
548 for (size_t i = 0; i < tasks->size(); ++i) { | |
549 FullTaskDescriptor* task = &tasks->at(i); | |
550 DCHECK(!task->is_default()); | |
551 if (file_browser_handlers::IsFallbackFileBrowserHandler( | |
552 task->task_descriptor())) { | |
553 task->set_is_default(true); | |
554 return; | |
555 } | |
556 } | |
557 } | |
558 | |
559 } // namespace file_tasks | |
560 } // namespace file_manager | |
OLD | NEW |