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/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/chromeos/fileapi/file_system_backend.h"
     20 #include "chrome/browser/drive/drive_app_registry.h"
     21 #include "chrome/browser/extensions/extension_service.h"
     22 #include "chrome/browser/extensions/extension_tab_util.h"
     23 #include "chrome/browser/extensions/extension_util.h"
     24 #include "chrome/browser/profiles/profile.h"
     25 #include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
     26 #include "chrome/common/extensions/api/file_browser_handlers/file_browser_handler.h"
     27 #include "chrome/common/extensions/api/file_browser_private.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_system.h"
     32 #include "extensions/common/constants.h"
     33 #include "extensions/common/extension_set.h"
     34 #include "google_apis/drive/gdata_wapi_parser.h"
     35 #include "webkit/browser/fileapi/file_system_context.h"
     36 #include "webkit/browser/fileapi/file_system_url.h"
     37 
     38 using extensions::Extension;
     39 using extensions::app_file_handler_util::FindFileHandlersForFiles;
     40 using fileapi::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 (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.
    102 void 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 void ChooseSuitableGalleryHandler(std::vector<FullTaskDescriptor>* task_list) {
    112   const bool disable_new_gallery =
    113       base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
    114           chromeos::switches::kFileManagerEnableNewGallery) == "false";
    115   std::vector<FullTaskDescriptor>::iterator it = task_list->begin();
    116   while (it != task_list->end()) {
    117     if (disable_new_gallery) {
    118       if (it->task_descriptor().app_id == kGalleryAppId)
    119         it = task_list->erase(it);
    120       else
    121         ++it;
    122     } else {
    123       if (it->task_descriptor().app_id == kFileManagerAppId &&
    124           it->task_descriptor().action_id == "gallery") {
    125         it = task_list->erase(it);
    126       } else {
    127         ++it;
    128       }
    129     }
    130   }
    131 }
    132 
    133 }  // namespace
    134 
    135 FullTaskDescriptor::FullTaskDescriptor(
    136     const TaskDescriptor& task_descriptor,
    137     const std::string& task_title,
    138     const GURL& icon_url,
    139     bool is_default)
    140     : task_descriptor_(task_descriptor),
    141       task_title_(task_title),
    142       icon_url_(icon_url),
    143       is_default_(is_default){
    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 = 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 = 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 MakeDriveAppTaskId(const std::string& app_id) {
    215   return MakeTaskID(app_id, TASK_TYPE_DRIVE_APP, kDriveAppActionID);
    216 }
    217 
    218 std::string TaskDescriptorToId(const TaskDescriptor& task_descriptor) {
    219   return MakeTaskID(task_descriptor.app_id,
    220                     task_descriptor.task_type,
    221                     task_descriptor.action_id);
    222 }
    223 
    224 bool ParseTaskID(const std::string& task_id, TaskDescriptor* task) {
    225   DCHECK(task);
    226 
    227   std::vector<std::string> result;
    228   int count = Tokenize(task_id, std::string("|"), &result);
    229 
    230   // Parse a legacy task ID that only contain two parts. Drive tasks are
    231   // identified by a prefix "drive-app:" on the extension ID. The legacy task
    232   // IDs can be stored in preferences.
    233   if (count == 2) {
    234     if (StartsWithASCII(result[0], kDriveTaskExtensionPrefix, true)) {
    235       task->task_type = TASK_TYPE_DRIVE_APP;
    236       task->app_id = result[0].substr(kDriveTaskExtensionPrefixLength);
    237     } else {
    238       task->task_type = TASK_TYPE_FILE_BROWSER_HANDLER;
    239       task->app_id = result[0];
    240     }
    241 
    242     task->action_id = result[1];
    243 
    244     return true;
    245   }
    246 
    247   if (count != 3)
    248     return false;
    249 
    250   TaskType task_type = StringToTaskType(result[1]);
    251   if (task_type == TASK_TYPE_UNKNOWN)
    252     return false;
    253 
    254   task->app_id = result[0];
    255   task->task_type = task_type;
    256   task->action_id = result[2];
    257 
    258   return true;
    259 }
    260 
    261 bool ExecuteFileTask(Profile* profile,
    262                      const GURL& source_url,
    263                      const TaskDescriptor& task,
    264                      const std::vector<FileSystemURL>& file_urls,
    265                      const FileTaskFinishedCallback& done) {
    266   // drive::FileTaskExecutor is responsible to handle drive tasks.
    267   if (task.task_type == TASK_TYPE_DRIVE_APP) {
    268     DCHECK_EQ(kDriveAppActionID, task.action_id);
    269     drive::FileTaskExecutor* executor =
    270         new drive::FileTaskExecutor(profile, task.app_id);
    271     executor->Execute(file_urls, done);
    272     return true;
    273   }
    274 
    275   // Get the extension.
    276   ExtensionService* service =
    277       extensions::ExtensionSystem::Get(profile)->extension_service();
    278   const Extension* extension = service ?
    279       service->GetExtensionById(task.app_id, false) : NULL;
    280   if (!extension)
    281     return false;
    282 
    283   // Execute the task.
    284   if (task.task_type == TASK_TYPE_FILE_BROWSER_HANDLER) {
    285     return file_browser_handlers::ExecuteFileBrowserHandler(
    286         profile,
    287         extension,
    288         task.action_id,
    289         file_urls,
    290         done);
    291   } else if (task.task_type == TASK_TYPE_FILE_HANDLER) {
    292     std::vector<base::FilePath> paths;
    293     for (size_t i = 0; i != file_urls.size(); ++i) {
    294       paths.push_back(file_urls[i].path());
    295     }
    296     apps::LaunchPlatformAppWithFileHandler(
    297         profile, extension, task.action_id, paths);
    298     if (!done.is_null())
    299       done.Run(extensions::api::file_browser_private::TASK_RESULT_MESSAGE_SENT);
    300     return true;
    301   }
    302   NOTREACHED();
    303   return false;
    304 }
    305 
    306 void FindDriveAppTasks(
    307     const drive::DriveAppRegistry& drive_app_registry,
    308     const PathAndMimeTypeSet& path_mime_set,
    309     std::vector<FullTaskDescriptor>* result_list) {
    310   DCHECK(result_list);
    311 
    312   bool is_first = true;
    313   typedef std::map<std::string, drive::DriveAppInfo> DriveAppInfoMap;
    314   DriveAppInfoMap drive_app_map;
    315 
    316   for (PathAndMimeTypeSet::const_iterator it = path_mime_set.begin();
    317        it != path_mime_set.end(); ++it) {
    318     const base::FilePath& file_path = it->first;
    319     const std::string& mime_type = it->second;
    320     // Return immediately if a file not on Drive is found, as Drive app tasks
    321     // work only if all files are on Drive.
    322     if (!drive::util::IsUnderDriveMountPoint(file_path))
    323       return;
    324 
    325     std::vector<drive::DriveAppInfo> app_info_list;
    326     drive_app_registry.GetAppsForFile(file_path.Extension(),
    327                                       mime_type,
    328                                       &app_info_list);
    329 
    330     if (is_first) {
    331       // For the first file, we store all the info.
    332       for (size_t j = 0; j < app_info_list.size(); ++j)
    333         drive_app_map[app_info_list[j].app_id] = app_info_list[j];
    334     } else {
    335       // For remaining files, take the intersection with the current
    336       // result, based on the app id.
    337       std::set<std::string> app_id_set;
    338       for (size_t j = 0; j < app_info_list.size(); ++j)
    339         app_id_set.insert(app_info_list[j].app_id);
    340       for (DriveAppInfoMap::iterator iter = drive_app_map.begin();
    341            iter != drive_app_map.end();) {
    342         if (app_id_set.count(iter->first) == 0) {
    343           drive_app_map.erase(iter++);
    344         } else {
    345           ++iter;
    346         }
    347       }
    348     }
    349 
    350     is_first = false;
    351   }
    352 
    353   for (DriveAppInfoMap::const_iterator iter = drive_app_map.begin();
    354        iter != drive_app_map.end(); ++iter) {
    355     const drive::DriveAppInfo& app_info = iter->second;
    356     TaskDescriptor descriptor(app_info.app_id,
    357                               TASK_TYPE_DRIVE_APP,
    358                               kDriveAppActionID);
    359     GURL icon_url = drive::util::FindPreferredIcon(
    360         app_info.app_icons,
    361         drive::util::kPreferredIconSize);
    362     result_list->push_back(
    363         FullTaskDescriptor(descriptor,
    364                            app_info.app_name,
    365                            icon_url,
    366                            false /* is_default */));
    367   }
    368 }
    369 
    370 void FindFileHandlerTasks(
    371     Profile* profile,
    372     const PathAndMimeTypeSet& path_mime_set,
    373     std::vector<FullTaskDescriptor>* result_list) {
    374   DCHECK(!path_mime_set.empty());
    375   DCHECK(result_list);
    376 
    377   ExtensionService* service = profile->GetExtensionService();
    378   if (!service)
    379     return;
    380 
    381   for (extensions::ExtensionSet::const_iterator iter =
    382            service->extensions()->begin();
    383        iter != service->extensions()->end();
    384        ++iter) {
    385     const Extension* extension = iter->get();
    386 
    387     // We don't support using hosted apps to open files.
    388     if (!extension->is_platform_app())
    389       continue;
    390 
    391     if (profile->IsOffTheRecord() &&
    392         !extensions::util::IsIncognitoEnabled(extension->id(), profile))
    393       continue;
    394 
    395     typedef std::vector<const extensions::FileHandlerInfo*> FileHandlerList;
    396     FileHandlerList file_handlers =
    397         FindFileHandlersForFiles(*extension, path_mime_set);
    398     if (file_handlers.empty())
    399       continue;
    400 
    401     for (FileHandlerList::iterator i = file_handlers.begin();
    402          i != file_handlers.end(); ++i) {
    403       std::string task_id = file_tasks::MakeTaskID(
    404           extension->id(), file_tasks::TASK_TYPE_FILE_HANDLER, (*i)->id);
    405 
    406       GURL best_icon = extensions::ExtensionIconSource::GetIconURL(
    407           extension,
    408           drive::util::kPreferredIconSize,
    409           ExtensionIconSet::MATCH_BIGGER,
    410           false,  // grayscale
    411           NULL);  // exists
    412 
    413       result_list->push_back(FullTaskDescriptor(
    414           TaskDescriptor(extension->id(),
    415                          file_tasks::TASK_TYPE_FILE_HANDLER,
    416                          (*i)->id),
    417           (*i)->title,
    418           best_icon,
    419           false /* is_default */));
    420     }
    421   }
    422 }
    423 
    424 void FindFileBrowserHandlerTasks(
    425     Profile* profile,
    426     const std::vector<GURL>& file_urls,
    427     std::vector<FullTaskDescriptor>* result_list) {
    428   DCHECK(!file_urls.empty());
    429   DCHECK(result_list);
    430 
    431   file_browser_handlers::FileBrowserHandlerList common_tasks =
    432       file_browser_handlers::FindFileBrowserHandlers(profile, file_urls);
    433   if (common_tasks.empty())
    434     return;
    435 
    436   ExtensionService* service =
    437       extensions::ExtensionSystem::Get(profile)->extension_service();
    438   for (file_browser_handlers::FileBrowserHandlerList::const_iterator iter =
    439            common_tasks.begin();
    440        iter != common_tasks.end();
    441        ++iter) {
    442     const FileBrowserHandler* handler = *iter;
    443     const std::string extension_id = handler->extension_id();
    444     const Extension* extension = service->GetExtensionById(extension_id, false);
    445     DCHECK(extension);
    446 
    447     // TODO(zelidrag): Figure out how to expose icon URL that task defined in
    448     // manifest instead of the default extension icon.
    449     const GURL icon_url = extensions::ExtensionIconSource::GetIconURL(
    450         extension,
    451         extension_misc::EXTENSION_ICON_BITTY,
    452         ExtensionIconSet::MATCH_BIGGER,
    453         false,  // grayscale
    454         NULL);  // exists
    455 
    456     result_list->push_back(FullTaskDescriptor(
    457         TaskDescriptor(extension_id,
    458                        file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER,
    459                        handler->id()),
    460         handler->title(),
    461         icon_url,
    462         false /* is_default */));
    463   }
    464 }
    465 
    466 void FindAllTypesOfTasks(
    467     Profile* profile,
    468     const drive::DriveAppRegistry* drive_app_registry,
    469     const PathAndMimeTypeSet& path_mime_set,
    470     const std::vector<GURL>& file_urls,
    471     std::vector<FullTaskDescriptor>* result_list) {
    472   DCHECK(profile);
    473   DCHECK(result_list);
    474 
    475   // Find Drive app tasks, if the drive app registry is present.
    476   if (drive_app_registry)
    477     FindDriveAppTasks(*drive_app_registry, path_mime_set, result_list);
    478 
    479   // Find and append file handler tasks. We know there aren't duplicates
    480   // because Drive apps and platform apps are entirely different kinds of
    481   // tasks.
    482   FindFileHandlerTasks(profile, path_mime_set, result_list);
    483 
    484   // Find and append file browser handler tasks. We know there aren't
    485   // duplicates because "file_browser_handlers" and "file_handlers" shouldn't
    486   // be used in the same manifest.json.
    487   FindFileBrowserHandlerTasks(profile, file_urls, result_list);
    488 
    489   // Google documents can only be handled by internal handlers.
    490   if (ContainsGoogleDocument(path_mime_set))
    491     KeepOnlyFileManagerInternalTasks(result_list);
    492 
    493   ChooseSuitableGalleryHandler(result_list);
    494   ChooseAndSetDefaultTask(*profile->GetPrefs(), path_mime_set, result_list);
    495 }
    496 
    497 void ChooseAndSetDefaultTask(const PrefService& pref_service,
    498                              const PathAndMimeTypeSet& path_mime_set,
    499                              std::vector<FullTaskDescriptor>* tasks) {
    500   // Collect the task IDs of default tasks from the preferences into a set.
    501   std::set<std::string> default_task_ids;
    502   for (PathAndMimeTypeSet::const_iterator it = path_mime_set.begin();
    503        it != path_mime_set.end(); ++it) {
    504     const base::FilePath& file_path = it->first;
    505     const std::string& mime_type = it->second;
    506     std::string task_id = file_tasks::GetDefaultTaskIdFromPrefs(
    507         pref_service, mime_type, file_path.Extension());
    508     default_task_ids.insert(task_id);
    509   }
    510 
    511   // Go through all the tasks from the beginning and see if there is any
    512   // default task. If found, pick and set it as default and return.
    513   for (size_t i = 0; i < tasks->size(); ++i) {
    514     FullTaskDescriptor* task = &tasks->at(i);
    515     DCHECK(!task->is_default());
    516     const std::string task_id = TaskDescriptorToId(task->task_descriptor());
    517     if (ContainsKey(default_task_ids, task_id)) {
    518       task->set_is_default(true);
    519       return;
    520     }
    521   }
    522 
    523   // No default tasks found. If there is any fallback file browser handler,
    524   // make it as default task, so it's selected by default.
    525   for (size_t i = 0; i < tasks->size(); ++i) {
    526     FullTaskDescriptor* task = &tasks->at(i);
    527     DCHECK(!task->is_default());
    528     if (file_browser_handlers::IsFallbackFileBrowserHandler(
    529             task->task_descriptor())) {
    530       task->set_is_default(true);
    531       return;
    532     }
    533   }
    534 }
    535 
    536 }  // namespace file_tasks
    537 }  // namespace file_manager
    538