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