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