• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/file_manager/file_tasks.h"
6 
7 #include "apps/launcher.h"
8 #include "base/bind.h"
9 #include "base/command_line.h"
10 #include "base/prefs/pref_service.h"
11 #include "base/prefs/scoped_user_pref_update.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/chromeos/fileapi/file_system_backend.h"
20 #include "chrome/browser/drive/drive_app_registry.h"
21 #include "chrome/browser/extensions/extension_service.h"
22 #include "chrome/browser/extensions/extension_tab_util.h"
23 #include "chrome/browser/extensions/extension_util.h"
24 #include "chrome/browser/profiles/profile.h"
25 #include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
26 #include "chrome/common/extensions/api/file_browser_handlers/file_browser_handler.h"
27 #include "chrome/common/extensions/api/file_browser_private.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_system.h"
32 #include "extensions/common/constants.h"
33 #include "extensions/common/extension_set.h"
34 #include "google_apis/drive/gdata_wapi_parser.h"
35 #include "webkit/browser/fileapi/file_system_context.h"
36 #include "webkit/browser/fileapi/file_system_url.h"
37 
38 using extensions::Extension;
39 using extensions::app_file_handler_util::FindFileHandlersForFiles;
40 using fileapi::FileSystemURL;
41 
42 namespace file_manager {
43 namespace file_tasks {
44 
45 namespace {
46 
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";
52 
53 // Drive apps always use the action ID.
54 const char kDriveAppActionID[] = "open-with";
55 
56 // Converts a TaskType to a string.
TaskTypeToString(TaskType task_type)57 std::string TaskTypeToString(TaskType task_type) {
58   switch (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:
66       break;
67   }
68   NOTREACHED();
69   return "";
70 }
71 
72 // Converts a string to a TaskType. Returns TASK_TYPE_UNKNOWN on error.
StringToTaskType(const std::string & str)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;
81 }
82 
83 // Legacy Drive task extension prefix, used by CrackTaskID.
84 const char kDriveTaskExtensionPrefix[] = "drive-app:";
85 const size_t kDriveTaskExtensionPrefixLength =
86     arraysize(kDriveTaskExtensionPrefix) - 1;
87 
88 // Returns true if path_mime_set contains a Google document.
ContainsGoogleDocument(const PathAndMimeTypeSet & path_mime_set)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(
93             iter->first) &
94         google_apis::ResourceEntry::KIND_OF_GOOGLE_DOCUMENT) {
95       return true;
96     }
97   }
98   return false;
99 }
100 
101 // Leaves tasks handled by the file manger itself as is and removes all others.
KeepOnlyFileManagerInternalTasks(std::vector<FullTaskDescriptor> * tasks)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]);
107   }
108   tasks->swap(filtered);
109 }
110 
ChooseSuitableGalleryHandler(std::vector<FullTaskDescriptor> * task_list)111 void ChooseSuitableGalleryHandler(std::vector<FullTaskDescriptor>* task_list) {
112   const bool disable_new_gallery =
113       base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
114           chromeos::switches::kFileManagerEnableNewGallery) == "false";
115   std::vector<FullTaskDescriptor>::iterator it = task_list->begin();
116   while (it != task_list->end()) {
117     if (disable_new_gallery) {
118       if (it->task_descriptor().app_id == kGalleryAppId)
119         it = task_list->erase(it);
120       else
121         ++it;
122     } else {
123       if (it->task_descriptor().app_id == kFileManagerAppId &&
124           it->task_descriptor().action_id == "gallery") {
125         it = task_list->erase(it);
126       } else {
127         ++it;
128       }
129     }
130   }
131 }
132 
133 }  // namespace
134 
FullTaskDescriptor(const TaskDescriptor & task_descriptor,const std::string & task_title,const GURL & icon_url,bool is_default)135 FullTaskDescriptor::FullTaskDescriptor(
136     const TaskDescriptor& task_descriptor,
137     const std::string& task_title,
138     const GURL& icon_url,
139     bool is_default)
140     : task_descriptor_(task_descriptor),
141       task_title_(task_title),
142       icon_url_(icon_url),
143       is_default_(is_default){
144 }
145 
UpdateDefaultTask(PrefService * pref_service,const std::string & task_id,const std::set<std::string> & suffixes,const std::set<std::string> & mime_types)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) {
150   if (!pref_service)
151     return;
152 
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);
160     }
161   }
162 
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 = StringToLowerASCII(*iter);
171       mime_type_pref->SetWithoutPathExpansion(lower_suffix, value);
172     }
173   }
174 }
175 
GetDefaultTaskIdFromPrefs(const PrefService & pref_service,const std::string & mime_type,const std::string & suffix)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;
181   std::string task_id;
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;
190       return task_id;
191     }
192   }
193 
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 = 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;
202   return task_id;
203 }
204 
MakeTaskID(const std::string & app_id,TaskType task_type,const std::string & action_id)205 std::string MakeTaskID(const std::string& app_id,
206                        TaskType task_type,
207                        const std::string& action_id) {
208   return base::StringPrintf("%s|%s|%s",
209                             app_id.c_str(),
210                             TaskTypeToString(task_type).c_str(),
211                             action_id.c_str());
212 }
213 
MakeDriveAppTaskId(const std::string & app_id)214 std::string MakeDriveAppTaskId(const std::string& app_id) {
215   return MakeTaskID(app_id, TASK_TYPE_DRIVE_APP, kDriveAppActionID);
216 }
217 
TaskDescriptorToId(const TaskDescriptor & task_descriptor)218 std::string TaskDescriptorToId(const TaskDescriptor& task_descriptor) {
219   return MakeTaskID(task_descriptor.app_id,
220                     task_descriptor.task_type,
221                     task_descriptor.action_id);
222 }
223 
ParseTaskID(const std::string & task_id,TaskDescriptor * task)224 bool ParseTaskID(const std::string& task_id, TaskDescriptor* task) {
225   DCHECK(task);
226 
227   std::vector<std::string> result;
228   int count = Tokenize(task_id, std::string("|"), &result);
229 
230   // Parse a legacy task ID that only contain two parts. Drive tasks are
231   // identified by a prefix "drive-app:" on the extension ID. The legacy task
232   // IDs can be stored in preferences.
233   if (count == 2) {
234     if (StartsWithASCII(result[0], kDriveTaskExtensionPrefix, true)) {
235       task->task_type = TASK_TYPE_DRIVE_APP;
236       task->app_id = result[0].substr(kDriveTaskExtensionPrefixLength);
237     } else {
238       task->task_type = TASK_TYPE_FILE_BROWSER_HANDLER;
239       task->app_id = result[0];
240     }
241 
242     task->action_id = result[1];
243 
244     return true;
245   }
246 
247   if (count != 3)
248     return false;
249 
250   TaskType task_type = StringToTaskType(result[1]);
251   if (task_type == TASK_TYPE_UNKNOWN)
252     return false;
253 
254   task->app_id = result[0];
255   task->task_type = task_type;
256   task->action_id = result[2];
257 
258   return true;
259 }
260 
ExecuteFileTask(Profile * profile,const GURL & source_url,const TaskDescriptor & task,const std::vector<FileSystemURL> & file_urls,const FileTaskFinishedCallback & done)261 bool ExecuteFileTask(Profile* profile,
262                      const GURL& source_url,
263                      const TaskDescriptor& task,
264                      const std::vector<FileSystemURL>& file_urls,
265                      const FileTaskFinishedCallback& done) {
266   // drive::FileTaskExecutor is responsible to handle drive tasks.
267   if (task.task_type == TASK_TYPE_DRIVE_APP) {
268     DCHECK_EQ(kDriveAppActionID, task.action_id);
269     drive::FileTaskExecutor* executor =
270         new drive::FileTaskExecutor(profile, task.app_id);
271     executor->Execute(file_urls, done);
272     return true;
273   }
274 
275   // Get the extension.
276   ExtensionService* service =
277       extensions::ExtensionSystem::Get(profile)->extension_service();
278   const Extension* extension = service ?
279       service->GetExtensionById(task.app_id, false) : NULL;
280   if (!extension)
281     return false;
282 
283   // Execute the task.
284   if (task.task_type == TASK_TYPE_FILE_BROWSER_HANDLER) {
285     return file_browser_handlers::ExecuteFileBrowserHandler(
286         profile,
287         extension,
288         task.action_id,
289         file_urls,
290         done);
291   } else if (task.task_type == TASK_TYPE_FILE_HANDLER) {
292     std::vector<base::FilePath> paths;
293     for (size_t i = 0; i != file_urls.size(); ++i) {
294       paths.push_back(file_urls[i].path());
295     }
296     apps::LaunchPlatformAppWithFileHandler(
297         profile, extension, task.action_id, paths);
298     if (!done.is_null())
299       done.Run(extensions::api::file_browser_private::TASK_RESULT_MESSAGE_SENT);
300     return true;
301   }
302   NOTREACHED();
303   return false;
304 }
305 
FindDriveAppTasks(const drive::DriveAppRegistry & drive_app_registry,const PathAndMimeTypeSet & path_mime_set,std::vector<FullTaskDescriptor> * result_list)306 void FindDriveAppTasks(
307     const drive::DriveAppRegistry& drive_app_registry,
308     const PathAndMimeTypeSet& path_mime_set,
309     std::vector<FullTaskDescriptor>* result_list) {
310   DCHECK(result_list);
311 
312   bool is_first = true;
313   typedef std::map<std::string, drive::DriveAppInfo> DriveAppInfoMap;
314   DriveAppInfoMap drive_app_map;
315 
316   for (PathAndMimeTypeSet::const_iterator it = path_mime_set.begin();
317        it != path_mime_set.end(); ++it) {
318     const base::FilePath& file_path = it->first;
319     const std::string& mime_type = it->second;
320     // Return immediately if a file not on Drive is found, as Drive app tasks
321     // work only if all files are on Drive.
322     if (!drive::util::IsUnderDriveMountPoint(file_path))
323       return;
324 
325     std::vector<drive::DriveAppInfo> app_info_list;
326     drive_app_registry.GetAppsForFile(file_path.Extension(),
327                                       mime_type,
328                                       &app_info_list);
329 
330     if (is_first) {
331       // For the first file, we store all the info.
332       for (size_t j = 0; j < app_info_list.size(); ++j)
333         drive_app_map[app_info_list[j].app_id] = app_info_list[j];
334     } else {
335       // For remaining files, take the intersection with the current
336       // result, based on the app id.
337       std::set<std::string> app_id_set;
338       for (size_t j = 0; j < app_info_list.size(); ++j)
339         app_id_set.insert(app_info_list[j].app_id);
340       for (DriveAppInfoMap::iterator iter = drive_app_map.begin();
341            iter != drive_app_map.end();) {
342         if (app_id_set.count(iter->first) == 0) {
343           drive_app_map.erase(iter++);
344         } else {
345           ++iter;
346         }
347       }
348     }
349 
350     is_first = false;
351   }
352 
353   for (DriveAppInfoMap::const_iterator iter = drive_app_map.begin();
354        iter != drive_app_map.end(); ++iter) {
355     const drive::DriveAppInfo& app_info = iter->second;
356     TaskDescriptor descriptor(app_info.app_id,
357                               TASK_TYPE_DRIVE_APP,
358                               kDriveAppActionID);
359     GURL icon_url = drive::util::FindPreferredIcon(
360         app_info.app_icons,
361         drive::util::kPreferredIconSize);
362     result_list->push_back(
363         FullTaskDescriptor(descriptor,
364                            app_info.app_name,
365                            icon_url,
366                            false /* is_default */));
367   }
368 }
369 
FindFileHandlerTasks(Profile * profile,const PathAndMimeTypeSet & path_mime_set,std::vector<FullTaskDescriptor> * result_list)370 void FindFileHandlerTasks(
371     Profile* profile,
372     const PathAndMimeTypeSet& path_mime_set,
373     std::vector<FullTaskDescriptor>* result_list) {
374   DCHECK(!path_mime_set.empty());
375   DCHECK(result_list);
376 
377   ExtensionService* service = profile->GetExtensionService();
378   if (!service)
379     return;
380 
381   for (extensions::ExtensionSet::const_iterator iter =
382            service->extensions()->begin();
383        iter != service->extensions()->end();
384        ++iter) {
385     const Extension* extension = iter->get();
386 
387     // We don't support using hosted apps to open files.
388     if (!extension->is_platform_app())
389       continue;
390 
391     if (profile->IsOffTheRecord() &&
392         !extensions::util::IsIncognitoEnabled(extension->id(), profile))
393       continue;
394 
395     typedef std::vector<const extensions::FileHandlerInfo*> FileHandlerList;
396     FileHandlerList file_handlers =
397         FindFileHandlersForFiles(*extension, path_mime_set);
398     if (file_handlers.empty())
399       continue;
400 
401     for (FileHandlerList::iterator i = file_handlers.begin();
402          i != file_handlers.end(); ++i) {
403       std::string task_id = file_tasks::MakeTaskID(
404           extension->id(), file_tasks::TASK_TYPE_FILE_HANDLER, (*i)->id);
405 
406       GURL best_icon = extensions::ExtensionIconSource::GetIconURL(
407           extension,
408           drive::util::kPreferredIconSize,
409           ExtensionIconSet::MATCH_BIGGER,
410           false,  // grayscale
411           NULL);  // exists
412 
413       result_list->push_back(FullTaskDescriptor(
414           TaskDescriptor(extension->id(),
415                          file_tasks::TASK_TYPE_FILE_HANDLER,
416                          (*i)->id),
417           (*i)->title,
418           best_icon,
419           false /* is_default */));
420     }
421   }
422 }
423 
FindFileBrowserHandlerTasks(Profile * profile,const std::vector<GURL> & file_urls,std::vector<FullTaskDescriptor> * result_list)424 void FindFileBrowserHandlerTasks(
425     Profile* profile,
426     const std::vector<GURL>& file_urls,
427     std::vector<FullTaskDescriptor>* result_list) {
428   DCHECK(!file_urls.empty());
429   DCHECK(result_list);
430 
431   file_browser_handlers::FileBrowserHandlerList common_tasks =
432       file_browser_handlers::FindFileBrowserHandlers(profile, file_urls);
433   if (common_tasks.empty())
434     return;
435 
436   ExtensionService* service =
437       extensions::ExtensionSystem::Get(profile)->extension_service();
438   for (file_browser_handlers::FileBrowserHandlerList::const_iterator iter =
439            common_tasks.begin();
440        iter != common_tasks.end();
441        ++iter) {
442     const FileBrowserHandler* handler = *iter;
443     const std::string extension_id = handler->extension_id();
444     const Extension* extension = service->GetExtensionById(extension_id, false);
445     DCHECK(extension);
446 
447     // TODO(zelidrag): Figure out how to expose icon URL that task defined in
448     // manifest instead of the default extension icon.
449     const GURL icon_url = extensions::ExtensionIconSource::GetIconURL(
450         extension,
451         extension_misc::EXTENSION_ICON_BITTY,
452         ExtensionIconSet::MATCH_BIGGER,
453         false,  // grayscale
454         NULL);  // exists
455 
456     result_list->push_back(FullTaskDescriptor(
457         TaskDescriptor(extension_id,
458                        file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER,
459                        handler->id()),
460         handler->title(),
461         icon_url,
462         false /* is_default */));
463   }
464 }
465 
FindAllTypesOfTasks(Profile * profile,const drive::DriveAppRegistry * drive_app_registry,const PathAndMimeTypeSet & path_mime_set,const std::vector<GURL> & file_urls,std::vector<FullTaskDescriptor> * result_list)466 void FindAllTypesOfTasks(
467     Profile* profile,
468     const drive::DriveAppRegistry* drive_app_registry,
469     const PathAndMimeTypeSet& path_mime_set,
470     const std::vector<GURL>& file_urls,
471     std::vector<FullTaskDescriptor>* result_list) {
472   DCHECK(profile);
473   DCHECK(result_list);
474 
475   // Find Drive app tasks, if the drive app registry is present.
476   if (drive_app_registry)
477     FindDriveAppTasks(*drive_app_registry, path_mime_set, result_list);
478 
479   // Find and append file handler tasks. We know there aren't duplicates
480   // because Drive apps and platform apps are entirely different kinds of
481   // tasks.
482   FindFileHandlerTasks(profile, path_mime_set, result_list);
483 
484   // Find and append file browser handler tasks. We know there aren't
485   // duplicates because "file_browser_handlers" and "file_handlers" shouldn't
486   // be used in the same manifest.json.
487   FindFileBrowserHandlerTasks(profile, file_urls, result_list);
488 
489   // Google documents can only be handled by internal handlers.
490   if (ContainsGoogleDocument(path_mime_set))
491     KeepOnlyFileManagerInternalTasks(result_list);
492 
493   ChooseSuitableGalleryHandler(result_list);
494   ChooseAndSetDefaultTask(*profile->GetPrefs(), path_mime_set, result_list);
495 }
496 
ChooseAndSetDefaultTask(const PrefService & pref_service,const PathAndMimeTypeSet & path_mime_set,std::vector<FullTaskDescriptor> * tasks)497 void ChooseAndSetDefaultTask(const PrefService& pref_service,
498                              const PathAndMimeTypeSet& path_mime_set,
499                              std::vector<FullTaskDescriptor>* tasks) {
500   // Collect the task IDs of default tasks from the preferences into a set.
501   std::set<std::string> default_task_ids;
502   for (PathAndMimeTypeSet::const_iterator it = path_mime_set.begin();
503        it != path_mime_set.end(); ++it) {
504     const base::FilePath& file_path = it->first;
505     const std::string& mime_type = it->second;
506     std::string task_id = file_tasks::GetDefaultTaskIdFromPrefs(
507         pref_service, mime_type, file_path.Extension());
508     default_task_ids.insert(task_id);
509   }
510 
511   // Go through all the tasks from the beginning and see if there is any
512   // default task. If found, pick and set it as default and return.
513   for (size_t i = 0; i < tasks->size(); ++i) {
514     FullTaskDescriptor* task = &tasks->at(i);
515     DCHECK(!task->is_default());
516     const std::string task_id = TaskDescriptorToId(task->task_descriptor());
517     if (ContainsKey(default_task_ids, task_id)) {
518       task->set_is_default(true);
519       return;
520     }
521   }
522 
523   // No default tasks found. If there is any fallback file browser handler,
524   // make it as default task, so it's selected by default.
525   for (size_t i = 0; i < tasks->size(); ++i) {
526     FullTaskDescriptor* task = &tasks->at(i);
527     DCHECK(!task->is_default());
528     if (file_browser_handlers::IsFallbackFileBrowserHandler(
529             task->task_descriptor())) {
530       task->set_is_default(true);
531       return;
532     }
533   }
534 }
535 
536 }  // namespace file_tasks
537 }  // namespace file_manager
538