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