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