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