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