• 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/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 "extensions/browser/extension_host.h"
30 #include "extensions/browser/extension_registry.h"
31 #include "extensions/browser/extension_system.h"
32 #include "extensions/browser/extension_util.h"
33 #include "extensions/common/constants.h"
34 #include "extensions/common/extension_set.h"
35 #include "storage/browser/fileapi/file_system_url.h"
36 
37 using extensions::Extension;
38 using extensions::app_file_handler_util::FindFileHandlersForFiles;
39 using storage::FileSystemURL;
40 
41 namespace file_manager {
42 namespace file_tasks {
43 
44 namespace {
45 
46 // The values "file" and "app" are confusing, but cannot be changed easily as
47 // these are used in default task IDs stored in preferences.
48 const char kFileBrowserHandlerTaskType[] = "file";
49 const char kFileHandlerTaskType[] = "app";
50 const char kDriveAppTaskType[] = "drive";
51 
52 // Drive apps always use the action ID.
53 const char kDriveAppActionID[] = "open-with";
54 
55 // Converts a TaskType to a string.
TaskTypeToString(TaskType task_type)56 std::string TaskTypeToString(TaskType task_type) {
57   switch (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:
65       break;
66   }
67   NOTREACHED();
68   return "";
69 }
70 
71 // Converts a string to a TaskType. Returns TASK_TYPE_UNKNOWN on error.
StringToTaskType(const std::string & str)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;
80 }
81 
82 // Legacy Drive task extension prefix, used by CrackTaskID.
83 const char kDriveTaskExtensionPrefix[] = "drive-app:";
84 const size_t kDriveTaskExtensionPrefixLength =
85     arraysize(kDriveTaskExtensionPrefix) - 1;
86 
87 // Returns true if path_mime_set contains a Google document.
ContainsGoogleDocument(const PathAndMimeTypeSet & path_mime_set)88 bool ContainsGoogleDocument(const PathAndMimeTypeSet& path_mime_set) {
89   for (PathAndMimeTypeSet::const_iterator iter = path_mime_set.begin();
90        iter != path_mime_set.end(); ++iter) {
91     if (drive::util::HasHostedDocumentExtension(iter->first))
92       return true;
93   }
94   return false;
95 }
96 
97 // Leaves tasks handled by the file manger itself as is and removes all others.
KeepOnlyFileManagerInternalTasks(std::vector<FullTaskDescriptor> * tasks)98 void KeepOnlyFileManagerInternalTasks(std::vector<FullTaskDescriptor>* tasks) {
99   std::vector<FullTaskDescriptor> filtered;
100   for (size_t i = 0; i < tasks->size(); ++i) {
101     if ((*tasks)[i].task_descriptor().app_id == kFileManagerAppId)
102       filtered.push_back((*tasks)[i]);
103   }
104   tasks->swap(filtered);
105 }
106 
107 // Returns true if the given task is a handler by built-in apps like Files.app
108 // itself or QuickOffice etc. They are used as the initial default app.
IsFallbackFileHandler(const file_tasks::TaskDescriptor & task)109 bool IsFallbackFileHandler(const file_tasks::TaskDescriptor& task) {
110   if (task.task_type != file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER &&
111       task.task_type != file_tasks::TASK_TYPE_FILE_HANDLER)
112     return false;
113 
114   const char* kBuiltInApps[] = {
115     kFileManagerAppId,
116     kVideoPlayerAppId,
117     kGalleryAppId,
118     extension_misc::kQuickOfficeComponentExtensionId,
119     extension_misc::kQuickOfficeInternalExtensionId,
120     extension_misc::kQuickOfficeExtensionId,
121   };
122 
123   for (size_t i = 0; i < arraysize(kBuiltInApps); ++i) {
124     if (task.app_id == kBuiltInApps[i])
125       return true;
126   }
127   return false;
128 }
129 
130 }  // namespace
131 
FullTaskDescriptor(const TaskDescriptor & task_descriptor,const std::string & task_title,const GURL & icon_url,bool is_default)132 FullTaskDescriptor::FullTaskDescriptor(
133     const TaskDescriptor& task_descriptor,
134     const std::string& task_title,
135     const GURL& icon_url,
136     bool is_default)
137     : task_descriptor_(task_descriptor),
138       task_title_(task_title),
139       icon_url_(icon_url),
140       is_default_(is_default) {
141 }
142 
UpdateDefaultTask(PrefService * pref_service,const std::string & task_id,const std::set<std::string> & suffixes,const std::set<std::string> & mime_types)143 void UpdateDefaultTask(PrefService* pref_service,
144                        const std::string& task_id,
145                        const std::set<std::string>& suffixes,
146                        const std::set<std::string>& mime_types) {
147   if (!pref_service)
148     return;
149 
150   if (!mime_types.empty()) {
151     DictionaryPrefUpdate mime_type_pref(pref_service,
152                                         prefs::kDefaultTasksByMimeType);
153     for (std::set<std::string>::const_iterator iter = mime_types.begin();
154         iter != mime_types.end(); ++iter) {
155       base::StringValue* value = new base::StringValue(task_id);
156       mime_type_pref->SetWithoutPathExpansion(*iter, value);
157     }
158   }
159 
160   if (!suffixes.empty()) {
161     DictionaryPrefUpdate mime_type_pref(pref_service,
162                                         prefs::kDefaultTasksBySuffix);
163     for (std::set<std::string>::const_iterator iter = suffixes.begin();
164         iter != suffixes.end(); ++iter) {
165       base::StringValue* value = new base::StringValue(task_id);
166       // Suffixes are case insensitive.
167       std::string lower_suffix = base::StringToLowerASCII(*iter);
168       mime_type_pref->SetWithoutPathExpansion(lower_suffix, value);
169     }
170   }
171 }
172 
GetDefaultTaskIdFromPrefs(const PrefService & pref_service,const std::string & mime_type,const std::string & suffix)173 std::string GetDefaultTaskIdFromPrefs(const PrefService& pref_service,
174                                       const std::string& mime_type,
175                                       const std::string& suffix) {
176   VLOG(1) << "Looking for default for MIME type: " << mime_type
177       << " and suffix: " << suffix;
178   std::string task_id;
179   if (!mime_type.empty()) {
180     const base::DictionaryValue* mime_task_prefs =
181         pref_service.GetDictionary(prefs::kDefaultTasksByMimeType);
182     DCHECK(mime_task_prefs);
183     LOG_IF(ERROR, !mime_task_prefs) << "Unable to open MIME type prefs";
184     if (mime_task_prefs &&
185         mime_task_prefs->GetStringWithoutPathExpansion(mime_type, &task_id)) {
186       VLOG(1) << "Found MIME default handler: " << task_id;
187       return task_id;
188     }
189   }
190 
191   const base::DictionaryValue* suffix_task_prefs =
192       pref_service.GetDictionary(prefs::kDefaultTasksBySuffix);
193   DCHECK(suffix_task_prefs);
194   LOG_IF(ERROR, !suffix_task_prefs) << "Unable to open suffix prefs";
195   std::string lower_suffix = base::StringToLowerASCII(suffix);
196   if (suffix_task_prefs)
197     suffix_task_prefs->GetStringWithoutPathExpansion(lower_suffix, &task_id);
198   VLOG_IF(1, !task_id.empty()) << "Found suffix default handler: " << task_id;
199   return task_id;
200 }
201 
MakeTaskID(const std::string & app_id,TaskType task_type,const std::string & action_id)202 std::string MakeTaskID(const std::string& app_id,
203                        TaskType task_type,
204                        const std::string& action_id) {
205   return base::StringPrintf("%s|%s|%s",
206                             app_id.c_str(),
207                             TaskTypeToString(task_type).c_str(),
208                             action_id.c_str());
209 }
210 
TaskDescriptorToId(const TaskDescriptor & task_descriptor)211 std::string TaskDescriptorToId(const TaskDescriptor& task_descriptor) {
212   return MakeTaskID(task_descriptor.app_id,
213                     task_descriptor.task_type,
214                     task_descriptor.action_id);
215 }
216 
ParseTaskID(const std::string & task_id,TaskDescriptor * task)217 bool ParseTaskID(const std::string& task_id, TaskDescriptor* task) {
218   DCHECK(task);
219 
220   std::vector<std::string> result;
221   int count = Tokenize(task_id, std::string("|"), &result);
222 
223   // Parse a legacy task ID that only contain two parts. Drive tasks are
224   // identified by a prefix "drive-app:" on the extension ID. The legacy task
225   // IDs can be stored in preferences.
226   if (count == 2) {
227     if (StartsWithASCII(result[0], kDriveTaskExtensionPrefix, true)) {
228       task->task_type = TASK_TYPE_DRIVE_APP;
229       task->app_id = result[0].substr(kDriveTaskExtensionPrefixLength);
230     } else {
231       task->task_type = TASK_TYPE_FILE_BROWSER_HANDLER;
232       task->app_id = result[0];
233     }
234 
235     task->action_id = result[1];
236 
237     return true;
238   }
239 
240   if (count != 3)
241     return false;
242 
243   TaskType task_type = StringToTaskType(result[1]);
244   if (task_type == TASK_TYPE_UNKNOWN)
245     return false;
246 
247   task->app_id = result[0];
248   task->task_type = task_type;
249   task->action_id = result[2];
250 
251   return true;
252 }
253 
ExecuteFileTask(Profile * profile,const GURL & source_url,const TaskDescriptor & task,const std::vector<FileSystemURL> & file_urls,const FileTaskFinishedCallback & done)254 bool ExecuteFileTask(Profile* profile,
255                      const GURL& source_url,
256                      const TaskDescriptor& task,
257                      const std::vector<FileSystemURL>& file_urls,
258                      const FileTaskFinishedCallback& done) {
259   // drive::FileTaskExecutor is responsible to handle drive tasks.
260   if (task.task_type == TASK_TYPE_DRIVE_APP) {
261     DCHECK_EQ(kDriveAppActionID, task.action_id);
262     drive::FileTaskExecutor* executor =
263         new drive::FileTaskExecutor(profile, task.app_id);
264     executor->Execute(file_urls, done);
265     return true;
266   }
267 
268   // Get the extension.
269   const Extension* extension = extensions::ExtensionRegistry::Get(
270       profile)->enabled_extensions().GetByID(task.app_id);
271   if (!extension)
272     return false;
273 
274   // Execute the task.
275   if (task.task_type == TASK_TYPE_FILE_BROWSER_HANDLER) {
276     return file_browser_handlers::ExecuteFileBrowserHandler(
277         profile,
278         extension,
279         task.action_id,
280         file_urls,
281         done);
282   } else if (task.task_type == TASK_TYPE_FILE_HANDLER) {
283     std::vector<base::FilePath> paths;
284     for (size_t i = 0; i != file_urls.size(); ++i)
285       paths.push_back(file_urls[i].path());
286     apps::LaunchPlatformAppWithFileHandler(
287         profile, extension, task.action_id, paths);
288     if (!done.is_null())
289       done.Run(extensions::api::file_manager_private::TASK_RESULT_MESSAGE_SENT);
290     return true;
291   }
292   NOTREACHED();
293   return false;
294 }
295 
FindDriveAppTasks(const drive::DriveAppRegistry & drive_app_registry,const PathAndMimeTypeSet & path_mime_set,std::vector<FullTaskDescriptor> * result_list)296 void FindDriveAppTasks(
297     const drive::DriveAppRegistry& drive_app_registry,
298     const PathAndMimeTypeSet& path_mime_set,
299     std::vector<FullTaskDescriptor>* result_list) {
300   DCHECK(result_list);
301 
302   bool is_first = true;
303   typedef std::map<std::string, drive::DriveAppInfo> DriveAppInfoMap;
304   DriveAppInfoMap drive_app_map;
305 
306   for (PathAndMimeTypeSet::const_iterator it = path_mime_set.begin();
307        it != path_mime_set.end(); ++it) {
308     const base::FilePath& file_path = it->first;
309     const std::string& mime_type = it->second;
310     // Return immediately if a file not on Drive is found, as Drive app tasks
311     // work only if all files are on Drive.
312     if (!drive::util::IsUnderDriveMountPoint(file_path))
313       return;
314 
315     std::vector<drive::DriveAppInfo> app_info_list;
316     drive_app_registry.GetAppsForFile(file_path.Extension(),
317                                       mime_type,
318                                       &app_info_list);
319 
320     if (is_first) {
321       // For the first file, we store all the info.
322       for (size_t j = 0; j < app_info_list.size(); ++j)
323         drive_app_map[app_info_list[j].app_id] = app_info_list[j];
324     } else {
325       // For remaining files, take the intersection with the current
326       // result, based on the app id.
327       std::set<std::string> app_id_set;
328       for (size_t j = 0; j < app_info_list.size(); ++j)
329         app_id_set.insert(app_info_list[j].app_id);
330       for (DriveAppInfoMap::iterator iter = drive_app_map.begin();
331            iter != drive_app_map.end();) {
332         if (app_id_set.count(iter->first) == 0) {
333           drive_app_map.erase(iter++);
334         } else {
335           ++iter;
336         }
337       }
338     }
339 
340     is_first = false;
341   }
342 
343   for (DriveAppInfoMap::const_iterator iter = drive_app_map.begin();
344        iter != drive_app_map.end(); ++iter) {
345     const drive::DriveAppInfo& app_info = iter->second;
346     TaskDescriptor descriptor(app_info.app_id,
347                               TASK_TYPE_DRIVE_APP,
348                               kDriveAppActionID);
349     GURL icon_url = drive::util::FindPreferredIcon(
350         app_info.app_icons,
351         drive::util::kPreferredIconSize);
352     result_list->push_back(
353         FullTaskDescriptor(descriptor,
354                            app_info.app_name,
355                            icon_url,
356                            false /* is_default */));
357   }
358 }
359 
FindFileHandlerTasks(Profile * profile,const PathAndMimeTypeSet & path_mime_set,std::vector<FullTaskDescriptor> * result_list)360 void FindFileHandlerTasks(
361     Profile* profile,
362     const PathAndMimeTypeSet& path_mime_set,
363     std::vector<FullTaskDescriptor>* result_list) {
364   DCHECK(!path_mime_set.empty());
365   DCHECK(result_list);
366 
367   const extensions::ExtensionSet& enabled_extensions =
368       extensions::ExtensionRegistry::Get(profile)->enabled_extensions();
369   for (extensions::ExtensionSet::const_iterator iter =
370            enabled_extensions.begin();
371        iter != enabled_extensions.end();
372        ++iter) {
373     const Extension* extension = iter->get();
374 
375     // Check that the extension can be launched via an event. This includes all
376     // platform apps plus whitelisted extensions.
377     if (!CanLaunchViaEvent(extension))
378       continue;
379 
380     // Ephemeral apps cannot be file handlers.
381     if (extensions::util::IsEphemeralApp(extension->id(), profile))
382       continue;
383 
384     if (profile->IsOffTheRecord() &&
385         !extensions::util::IsIncognitoEnabled(extension->id(), profile))
386       continue;
387 
388     typedef std::vector<const extensions::FileHandlerInfo*> FileHandlerList;
389     FileHandlerList file_handlers =
390         FindFileHandlersForFiles(*extension, path_mime_set);
391     if (file_handlers.empty())
392       continue;
393 
394     // Only show the first matching handler from each app.
395     const extensions::FileHandlerInfo* file_handler = file_handlers.front();
396     std::string task_id = file_tasks::MakeTaskID(
397         extension->id(), file_tasks::TASK_TYPE_FILE_HANDLER, file_handler->id);
398 
399     GURL best_icon = extensions::ExtensionIconSource::GetIconURL(
400         extension,
401         drive::util::kPreferredIconSize,
402         ExtensionIconSet::MATCH_BIGGER,
403         false,  // grayscale
404         NULL);  // exists
405 
406     result_list->push_back(
407         FullTaskDescriptor(TaskDescriptor(extension->id(),
408                                           file_tasks::TASK_TYPE_FILE_HANDLER,
409                                           file_handler->id),
410                            extension->name(),
411                            best_icon,
412                            false /* is_default */));
413   }
414 }
415 
FindFileBrowserHandlerTasks(Profile * profile,const std::vector<GURL> & file_urls,std::vector<FullTaskDescriptor> * result_list)416 void FindFileBrowserHandlerTasks(
417     Profile* profile,
418     const std::vector<GURL>& file_urls,
419     std::vector<FullTaskDescriptor>* result_list) {
420   DCHECK(!file_urls.empty());
421   DCHECK(result_list);
422 
423   file_browser_handlers::FileBrowserHandlerList common_tasks =
424       file_browser_handlers::FindFileBrowserHandlers(profile, file_urls);
425   if (common_tasks.empty())
426     return;
427 
428   const extensions::ExtensionSet& enabled_extensions =
429       extensions::ExtensionRegistry::Get(profile)->enabled_extensions();
430   for (file_browser_handlers::FileBrowserHandlerList::const_iterator iter =
431            common_tasks.begin();
432        iter != common_tasks.end();
433        ++iter) {
434     const FileBrowserHandler* handler = *iter;
435     const std::string extension_id = handler->extension_id();
436     const Extension* extension = enabled_extensions.GetByID(extension_id);
437     DCHECK(extension);
438 
439     // TODO(zelidrag): Figure out how to expose icon URL that task defined in
440     // manifest instead of the default extension icon.
441     const GURL icon_url = extensions::ExtensionIconSource::GetIconURL(
442         extension,
443         extension_misc::EXTENSION_ICON_BITTY,
444         ExtensionIconSet::MATCH_BIGGER,
445         false,  // grayscale
446         NULL);  // exists
447 
448     result_list->push_back(FullTaskDescriptor(
449         TaskDescriptor(extension_id,
450                        file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER,
451                        handler->id()),
452         handler->title(),
453         icon_url,
454         false /* is_default */));
455   }
456 }
457 
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)458 void FindAllTypesOfTasks(
459     Profile* profile,
460     const drive::DriveAppRegistry* drive_app_registry,
461     const PathAndMimeTypeSet& path_mime_set,
462     const std::vector<GURL>& file_urls,
463     std::vector<FullTaskDescriptor>* result_list) {
464   DCHECK(profile);
465   DCHECK(result_list);
466 
467   // Find Drive app tasks, if the drive app registry is present.
468   if (drive_app_registry)
469     FindDriveAppTasks(*drive_app_registry, path_mime_set, result_list);
470 
471   // Find and append file handler tasks. We know there aren't duplicates
472   // because Drive apps and platform apps are entirely different kinds of
473   // tasks.
474   FindFileHandlerTasks(profile, path_mime_set, result_list);
475 
476   // Find and append file browser handler tasks. We know there aren't
477   // duplicates because "file_browser_handlers" and "file_handlers" shouldn't
478   // be used in the same manifest.json.
479   FindFileBrowserHandlerTasks(profile, file_urls, result_list);
480 
481   // Google documents can only be handled by internal handlers.
482   if (ContainsGoogleDocument(path_mime_set))
483     KeepOnlyFileManagerInternalTasks(result_list);
484 
485   ChooseAndSetDefaultTask(*profile->GetPrefs(), path_mime_set, result_list);
486 }
487 
ChooseAndSetDefaultTask(const PrefService & pref_service,const PathAndMimeTypeSet & path_mime_set,std::vector<FullTaskDescriptor> * tasks)488 void ChooseAndSetDefaultTask(const PrefService& pref_service,
489                              const PathAndMimeTypeSet& path_mime_set,
490                              std::vector<FullTaskDescriptor>* tasks) {
491   // Collect the task IDs of default tasks from the preferences into a set.
492   std::set<std::string> default_task_ids;
493   for (PathAndMimeTypeSet::const_iterator it = path_mime_set.begin();
494        it != path_mime_set.end(); ++it) {
495     const base::FilePath& file_path = it->first;
496     const std::string& mime_type = it->second;
497     std::string task_id = file_tasks::GetDefaultTaskIdFromPrefs(
498         pref_service, mime_type, file_path.Extension());
499     default_task_ids.insert(task_id);
500   }
501 
502   // Go through all the tasks from the beginning and see if there is any
503   // default task. If found, pick and set it as default and return.
504   for (size_t i = 0; i < tasks->size(); ++i) {
505     FullTaskDescriptor* task = &tasks->at(i);
506     DCHECK(!task->is_default());
507     const std::string task_id = TaskDescriptorToId(task->task_descriptor());
508     if (ContainsKey(default_task_ids, task_id)) {
509       task->set_is_default(true);
510       return;
511     }
512   }
513 
514   // No default tasks found. If there is any fallback file browser handler,
515   // make it as default task, so it's selected by default.
516   for (size_t i = 0; i < tasks->size(); ++i) {
517     FullTaskDescriptor* task = &tasks->at(i);
518     DCHECK(!task->is_default());
519     if (IsFallbackFileHandler(task->task_descriptor())) {
520       task->set_is_default(true);
521       return;
522     }
523   }
524 }
525 
526 }  // namespace file_tasks
527 }  // namespace file_manager
528