Home | History | Annotate | Download | only in file_manager
      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.
     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 (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.
     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.
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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