Upstream version 11.40.277.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / chromeos / file_manager / file_tasks.cc
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 "chromeos/chromeos_switches.h"
30 #include "extensions/browser/extension_host.h"
31 #include "extensions/browser/extension_registry.h"
32 #include "extensions/browser/extension_system.h"
33 #include "extensions/browser/extension_util.h"
34 #include "extensions/common/constants.h"
35 #include "extensions/common/extension_set.h"
36 #include "storage/browser/fileapi/file_system_url.h"
37
38 using extensions::Extension;
39 using extensions::app_file_handler_util::FindFileHandlersForFiles;
40 using storage::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.
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.
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.
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 (drive::util::HasHostedDocumentExtension(iter->first))
93       return true;
94   }
95   return false;
96 }
97
98 // Leaves tasks handled by the file manger itself as is and removes all others.
99 void KeepOnlyFileManagerInternalTasks(std::vector<FullTaskDescriptor>* tasks) {
100   std::vector<FullTaskDescriptor> filtered;
101   for (size_t i = 0; i < tasks->size(); ++i) {
102     if ((*tasks)[i].task_descriptor().app_id == kFileManagerAppId)
103       filtered.push_back((*tasks)[i]);
104   }
105   tasks->swap(filtered);
106 }
107
108 // Returns true if the given task is a handler by built-in apps like Files.app
109 // itself or QuickOffice etc. They are used as the initial default app.
110 bool IsFallbackFileHandler(const file_tasks::TaskDescriptor& task) {
111   if (task.task_type != file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER &&
112       task.task_type != file_tasks::TASK_TYPE_FILE_HANDLER)
113     return false;
114
115   const char* const kBuiltInApps[] = {
116     kFileManagerAppId,
117     kVideoPlayerAppId,
118     kGalleryAppId,
119     extension_misc::kQuickOfficeComponentExtensionId,
120     extension_misc::kQuickOfficeInternalExtensionId,
121     extension_misc::kQuickOfficeExtensionId,
122   };
123
124   for (size_t i = 0; i < arraysize(kBuiltInApps); ++i) {
125     if (task.app_id == kBuiltInApps[i])
126       return true;
127   }
128   return false;
129 }
130
131 }  // namespace
132
133 FullTaskDescriptor::FullTaskDescriptor(
134     const TaskDescriptor& task_descriptor,
135     const std::string& task_title,
136     const GURL& icon_url,
137     bool is_default,
138     bool is_generic_file_handler)
139     : task_descriptor_(task_descriptor),
140       task_title_(task_title),
141       icon_url_(icon_url),
142       is_default_(is_default),
143       is_generic_file_handler_(is_generic_file_handler) {
144 }
145
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 = base::StringToLowerASCII(*iter);
171       mime_type_pref->SetWithoutPathExpansion(lower_suffix, value);
172     }
173   }
174 }
175
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 = base::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
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
214 std::string TaskDescriptorToId(const TaskDescriptor& task_descriptor) {
215   return MakeTaskID(task_descriptor.app_id,
216                     task_descriptor.task_type,
217                     task_descriptor.action_id);
218 }
219
220 bool ParseTaskID(const std::string& task_id, TaskDescriptor* task) {
221   DCHECK(task);
222
223   std::vector<std::string> result;
224   int count = Tokenize(task_id, std::string("|"), &result);
225
226   // Parse a legacy task ID that only contain two parts. Drive tasks are
227   // identified by a prefix "drive-app:" on the extension ID. The legacy task
228   // IDs can be stored in preferences.
229   if (count == 2) {
230     if (StartsWithASCII(result[0], kDriveTaskExtensionPrefix, true)) {
231       task->task_type = TASK_TYPE_DRIVE_APP;
232       task->app_id = result[0].substr(kDriveTaskExtensionPrefixLength);
233     } else {
234       task->task_type = TASK_TYPE_FILE_BROWSER_HANDLER;
235       task->app_id = result[0];
236     }
237
238     task->action_id = result[1];
239
240     return true;
241   }
242
243   if (count != 3)
244     return false;
245
246   TaskType task_type = StringToTaskType(result[1]);
247   if (task_type == TASK_TYPE_UNKNOWN)
248     return false;
249
250   task->app_id = result[0];
251   task->task_type = task_type;
252   task->action_id = result[2];
253
254   return true;
255 }
256
257 bool ExecuteFileTask(Profile* profile,
258                      const GURL& source_url,
259                      const TaskDescriptor& task,
260                      const std::vector<FileSystemURL>& file_urls,
261                      const FileTaskFinishedCallback& done) {
262   // drive::FileTaskExecutor is responsible to handle drive tasks.
263   if (task.task_type == TASK_TYPE_DRIVE_APP) {
264     DCHECK_EQ(kDriveAppActionID, task.action_id);
265     drive::FileTaskExecutor* executor =
266         new drive::FileTaskExecutor(profile, task.app_id);
267     executor->Execute(file_urls, done);
268     return true;
269   }
270
271   // Get the extension.
272   const Extension* extension = extensions::ExtensionRegistry::Get(
273       profile)->enabled_extensions().GetByID(task.app_id);
274   if (!extension)
275     return false;
276
277   // Execute the task.
278   if (task.task_type == TASK_TYPE_FILE_BROWSER_HANDLER) {
279     return file_browser_handlers::ExecuteFileBrowserHandler(
280         profile,
281         extension,
282         task.action_id,
283         file_urls,
284         done);
285   } else if (task.task_type == TASK_TYPE_FILE_HANDLER) {
286     std::vector<base::FilePath> paths;
287     for (size_t i = 0; i != file_urls.size(); ++i)
288       paths.push_back(file_urls[i].path());
289     apps::LaunchPlatformAppWithFileHandler(
290         profile, extension, task.action_id, paths);
291     if (!done.is_null())
292       done.Run(extensions::api::file_manager_private::TASK_RESULT_MESSAGE_SENT);
293     return true;
294   }
295   NOTREACHED();
296   return false;
297 }
298
299 void FindDriveAppTasks(
300     const drive::DriveAppRegistry& drive_app_registry,
301     const PathAndMimeTypeSet& path_mime_set,
302     std::vector<FullTaskDescriptor>* result_list) {
303   DCHECK(result_list);
304
305   bool is_first = true;
306   typedef std::map<std::string, drive::DriveAppInfo> DriveAppInfoMap;
307   DriveAppInfoMap drive_app_map;
308
309   for (PathAndMimeTypeSet::const_iterator it = path_mime_set.begin();
310        it != path_mime_set.end(); ++it) {
311     const base::FilePath& file_path = it->first;
312     const std::string& mime_type = it->second;
313     // Return immediately if a file not on Drive is found, as Drive app tasks
314     // work only if all files are on Drive.
315     if (!drive::util::IsUnderDriveMountPoint(file_path))
316       return;
317
318     std::vector<drive::DriveAppInfo> app_info_list;
319     drive_app_registry.GetAppsForFile(file_path.Extension(),
320                                       mime_type,
321                                       &app_info_list);
322
323     if (is_first) {
324       // For the first file, we store all the info.
325       for (size_t j = 0; j < app_info_list.size(); ++j)
326         drive_app_map[app_info_list[j].app_id] = app_info_list[j];
327     } else {
328       // For remaining files, take the intersection with the current
329       // result, based on the app id.
330       std::set<std::string> app_id_set;
331       for (size_t j = 0; j < app_info_list.size(); ++j)
332         app_id_set.insert(app_info_list[j].app_id);
333       for (DriveAppInfoMap::iterator iter = drive_app_map.begin();
334            iter != drive_app_map.end();) {
335         if (app_id_set.count(iter->first) == 0) {
336           drive_app_map.erase(iter++);
337         } else {
338           ++iter;
339         }
340       }
341     }
342
343     is_first = false;
344   }
345
346   for (DriveAppInfoMap::const_iterator iter = drive_app_map.begin();
347        iter != drive_app_map.end(); ++iter) {
348     const drive::DriveAppInfo& app_info = iter->second;
349     TaskDescriptor descriptor(app_info.app_id,
350                               TASK_TYPE_DRIVE_APP,
351                               kDriveAppActionID);
352     GURL icon_url = drive::util::FindPreferredIcon(
353         app_info.app_icons,
354         drive::util::kPreferredIconSize);
355     result_list->push_back(
356         FullTaskDescriptor(descriptor,
357                            app_info.app_name,
358                            icon_url,
359                            false /* is_default */,
360                            false /* is_generic_file_handler */));
361   }
362 }
363
364 bool IsGenericFileHandler(
365     const extensions::FileHandlerInfo& file_handler_info) {
366   return file_handler_info.extensions.count("*") > 0 ||
367       file_handler_info.types.count("*") > 0 ||
368       file_handler_info.types.count("*/*") > 0;
369 }
370
371 void FindFileHandlerTasks(
372     Profile* profile,
373     const PathAndMimeTypeSet& path_mime_set,
374     std::vector<FullTaskDescriptor>* result_list) {
375   DCHECK(!path_mime_set.empty());
376   DCHECK(result_list);
377
378   const extensions::ExtensionSet& enabled_extensions =
379       extensions::ExtensionRegistry::Get(profile)->enabled_extensions();
380
381   for (extensions::ExtensionSet::const_iterator iter =
382            enabled_extensions.begin();
383        iter != enabled_extensions.end();
384        ++iter) {
385     const Extension* extension = iter->get();
386
387     // Check that the extension can be launched via an event. This includes all
388     // platform apps plus whitelisted extensions.
389     if (!CanLaunchViaEvent(extension))
390       continue;
391
392     // Ephemeral apps cannot be file handlers.
393     if (extensions::util::IsEphemeralApp(extension->id(), profile))
394       continue;
395
396     if (profile->IsOffTheRecord() &&
397         !extensions::util::IsIncognitoEnabled(extension->id(), profile))
398       continue;
399
400     typedef std::vector<const extensions::FileHandlerInfo*> FileHandlerList;
401     FileHandlerList file_handlers =
402         FindFileHandlersForFiles(*extension, path_mime_set);
403     if (file_handlers.empty())
404       continue;
405
406     // If the new ZIP unpacker is disabled, then hide its handlers, so we don't
407     // show both the legacy one and the new one in Files app for ZIP files.
408     if (extension->id() == extension_misc::kZIPUnpackerExtensionId &&
409         CommandLine::ForCurrentProcess()->HasSwitch(
410             chromeos::switches::kDisableNewZIPUnpacker)) {
411       continue;
412     }
413
414     // Show the first matching non-generic handler of each app. If there doesn't
415     // exist such handler, show the first matching handler of the app.
416     const extensions::FileHandlerInfo* file_handler = nullptr;
417     for (auto handler : file_handlers) {
418       if (!IsGenericFileHandler(*handler)) {
419         file_handler = handler;
420         break;
421       }
422     }
423     if (file_handler == nullptr) {
424       file_handler = file_handlers.front();
425     }
426
427     std::string task_id = file_tasks::MakeTaskID(
428         extension->id(), file_tasks::TASK_TYPE_FILE_HANDLER, file_handler->id);
429
430     GURL best_icon = extensions::ExtensionIconSource::GetIconURL(
431         extension,
432         drive::util::kPreferredIconSize,
433         ExtensionIconSet::MATCH_BIGGER,
434         false,  // grayscale
435         NULL);  // exists
436
437     result_list->push_back(
438         FullTaskDescriptor(TaskDescriptor(extension->id(),
439                                           file_tasks::TASK_TYPE_FILE_HANDLER,
440                                           file_handler->id),
441                            extension->name(),
442                            best_icon,
443                            false /* is_default */,
444                            IsGenericFileHandler(*file_handler)));
445   }
446 }
447
448 void FindFileBrowserHandlerTasks(
449     Profile* profile,
450     const std::vector<GURL>& file_urls,
451     std::vector<FullTaskDescriptor>* result_list) {
452   DCHECK(!file_urls.empty());
453   DCHECK(result_list);
454
455   file_browser_handlers::FileBrowserHandlerList common_tasks =
456       file_browser_handlers::FindFileBrowserHandlers(profile, file_urls);
457   if (common_tasks.empty())
458     return;
459
460   const extensions::ExtensionSet& enabled_extensions =
461       extensions::ExtensionRegistry::Get(profile)->enabled_extensions();
462   for (file_browser_handlers::FileBrowserHandlerList::const_iterator iter =
463            common_tasks.begin();
464        iter != common_tasks.end();
465        ++iter) {
466     const FileBrowserHandler* handler = *iter;
467     const std::string extension_id = handler->extension_id();
468     const Extension* extension = enabled_extensions.GetByID(extension_id);
469     DCHECK(extension);
470
471     // TODO(zelidrag): Figure out how to expose icon URL that task defined in
472     // manifest instead of the default extension icon.
473     const GURL icon_url = extensions::ExtensionIconSource::GetIconURL(
474         extension,
475         extension_misc::EXTENSION_ICON_BITTY,
476         ExtensionIconSet::MATCH_BIGGER,
477         false,  // grayscale
478         NULL);  // exists
479
480     result_list->push_back(FullTaskDescriptor(
481         TaskDescriptor(extension_id,
482                        file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER,
483                        handler->id()),
484         handler->title(),
485         icon_url,
486         false /* is_default */,
487         false /* is_generic_file_handler */));
488   }
489 }
490
491 void FindAllTypesOfTasks(
492     Profile* profile,
493     const drive::DriveAppRegistry* drive_app_registry,
494     const PathAndMimeTypeSet& path_mime_set,
495     const std::vector<GURL>& file_urls,
496     std::vector<FullTaskDescriptor>* result_list) {
497   DCHECK(profile);
498   DCHECK(result_list);
499
500   // Find Drive app tasks, if the drive app registry is present.
501   if (drive_app_registry)
502     FindDriveAppTasks(*drive_app_registry, path_mime_set, result_list);
503
504   // Find and append file handler tasks. We know there aren't duplicates
505   // because Drive apps and platform apps are entirely different kinds of
506   // tasks.
507   FindFileHandlerTasks(profile, path_mime_set, result_list);
508
509   // Find and append file browser handler tasks. We know there aren't
510   // duplicates because "file_browser_handlers" and "file_handlers" shouldn't
511   // be used in the same manifest.json.
512   FindFileBrowserHandlerTasks(profile, file_urls, result_list);
513
514   // Google documents can only be handled by internal handlers.
515   if (ContainsGoogleDocument(path_mime_set))
516     KeepOnlyFileManagerInternalTasks(result_list);
517
518   ChooseAndSetDefaultTask(*profile->GetPrefs(), path_mime_set, result_list);
519 }
520
521 void ChooseAndSetDefaultTask(const PrefService& pref_service,
522                              const PathAndMimeTypeSet& path_mime_set,
523                              std::vector<FullTaskDescriptor>* tasks) {
524   // Collect the task IDs of default tasks from the preferences into a set.
525   std::set<std::string> default_task_ids;
526   for (PathAndMimeTypeSet::const_iterator it = path_mime_set.begin();
527        it != path_mime_set.end(); ++it) {
528     const base::FilePath& file_path = it->first;
529     const std::string& mime_type = it->second;
530     std::string task_id = file_tasks::GetDefaultTaskIdFromPrefs(
531         pref_service, mime_type, file_path.Extension());
532     default_task_ids.insert(task_id);
533   }
534
535   // Go through all the tasks from the beginning and see if there is any
536   // default task. If found, pick and set it as default and return.
537   for (size_t i = 0; i < tasks->size(); ++i) {
538     FullTaskDescriptor* task = &tasks->at(i);
539     DCHECK(!task->is_default());
540     const std::string task_id = TaskDescriptorToId(task->task_descriptor());
541     if (ContainsKey(default_task_ids, task_id)) {
542       task->set_is_default(true);
543       return;
544     }
545   }
546
547   // No default tasks found. If there is any fallback file browser handler,
548   // make it as default task, so it's selected by default.
549   for (size_t i = 0; i < tasks->size(); ++i) {
550     FullTaskDescriptor* task = &tasks->at(i);
551     DCHECK(!task->is_default());
552     if (IsFallbackFileHandler(task->task_descriptor())) {
553       task->set_is_default(true);
554       return;
555     }
556   }
557 }
558
559 }  // namespace file_tasks
560 }  // namespace file_manager