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