Popular sites on the NTP: check that experiment group StartsWith (rather than IS...
[chromium-blink-merge.git] / chrome / browser / chromeos / file_manager / file_tasks.cc
blob66619f6065f9a562dc7a55636812bfe582c6bad5
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.
5 #include "chrome/browser/chromeos/file_manager/file_tasks.h"
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/string_split.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/extensions/extension_tab_util.h"
20 #include "chrome/browser/extensions/extension_util.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/ui/extensions/application_launch.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_manager_private.h"
26 #include "chrome/common/extensions/extension_constants.h"
27 #include "chrome/common/pref_names.h"
28 #include "chromeos/chromeos_switches.h"
29 #include "components/drive/drive_api_util.h"
30 #include "components/drive/drive_app_registry.h"
31 #include "components/mime_util/mime_util.h"
32 #include "extensions/browser/extension_host.h"
33 #include "extensions/browser/extension_registry.h"
34 #include "extensions/browser/extension_system.h"
35 #include "extensions/browser/extension_util.h"
36 #include "extensions/common/constants.h"
37 #include "extensions/common/extension_set.h"
38 #include "storage/browser/fileapi/file_system_url.h"
40 using extensions::Extension;
41 using extensions::app_file_handler_util::FindFileHandlersForFiles;
42 using storage::FileSystemURL;
44 namespace file_manager {
45 namespace file_tasks {
47 namespace {
49 // The values "file" and "app" are confusing, but cannot be changed easily as
50 // these are used in default task IDs stored in preferences.
51 const char kFileBrowserHandlerTaskType[] = "file";
52 const char kFileHandlerTaskType[] = "app";
53 const char kDriveAppTaskType[] = "drive";
55 // Drive apps always use the action ID.
56 const char kDriveAppActionID[] = "open-with";
58 // Converts a TaskType to a string.
59 std::string TaskTypeToString(TaskType task_type) {
60 switch (task_type) {
61 case TASK_TYPE_FILE_BROWSER_HANDLER:
62 return kFileBrowserHandlerTaskType;
63 case TASK_TYPE_FILE_HANDLER:
64 return kFileHandlerTaskType;
65 case TASK_TYPE_DRIVE_APP:
66 return kDriveAppTaskType;
67 case TASK_TYPE_UNKNOWN:
68 break;
70 NOTREACHED();
71 return "";
74 // Converts a string to a TaskType. Returns TASK_TYPE_UNKNOWN on error.
75 TaskType StringToTaskType(const std::string& str) {
76 if (str == kFileBrowserHandlerTaskType)
77 return TASK_TYPE_FILE_BROWSER_HANDLER;
78 if (str == kFileHandlerTaskType)
79 return TASK_TYPE_FILE_HANDLER;
80 if (str == kDriveAppTaskType)
81 return TASK_TYPE_DRIVE_APP;
82 return TASK_TYPE_UNKNOWN;
85 // Legacy Drive task extension prefix, used by CrackTaskID.
86 const char kDriveTaskExtensionPrefix[] = "drive-app:";
87 const size_t kDriveTaskExtensionPrefixLength =
88 arraysize(kDriveTaskExtensionPrefix) - 1;
90 // Returns true if path_mime_set contains a Google document.
91 bool ContainsGoogleDocument(const PathAndMimeTypeSet& path_mime_set) {
92 for (PathAndMimeTypeSet::const_iterator iter = path_mime_set.begin();
93 iter != path_mime_set.end(); ++iter) {
94 if (drive::util::HasHostedDocumentExtension(iter->first))
95 return true;
97 return false;
100 // Leaves tasks handled by the file manger itself as is and removes all others.
101 void 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]);
107 tasks->swap(filtered);
110 // Returns true if the given task is a handler by built-in apps like Files.app
111 // itself or QuickOffice etc. They are used as the initial default app.
112 bool IsFallbackFileHandler(const file_tasks::TaskDescriptor& task) {
113 if (task.task_type != file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER &&
114 task.task_type != file_tasks::TASK_TYPE_FILE_HANDLER)
115 return false;
117 const char* const kBuiltInApps[] = {
118 kFileManagerAppId,
119 kVideoPlayerAppId,
120 kGalleryAppId,
121 extension_misc::kQuickOfficeComponentExtensionId,
122 extension_misc::kQuickOfficeInternalExtensionId,
123 extension_misc::kQuickOfficeExtensionId,
126 for (size_t i = 0; i < arraysize(kBuiltInApps); ++i) {
127 if (task.app_id == kBuiltInApps[i])
128 return true;
130 return false;
133 } // namespace
135 FullTaskDescriptor::FullTaskDescriptor(
136 const TaskDescriptor& task_descriptor,
137 const std::string& task_title,
138 const GURL& icon_url,
139 bool is_default,
140 bool is_generic_file_handler)
141 : task_descriptor_(task_descriptor),
142 task_title_(task_title),
143 icon_url_(icon_url),
144 is_default_(is_default),
145 is_generic_file_handler_(is_generic_file_handler) {
148 void UpdateDefaultTask(PrefService* pref_service,
149 const std::string& task_id,
150 const std::set<std::string>& suffixes,
151 const std::set<std::string>& mime_types) {
152 if (!pref_service)
153 return;
155 if (!mime_types.empty()) {
156 DictionaryPrefUpdate mime_type_pref(pref_service,
157 prefs::kDefaultTasksByMimeType);
158 for (std::set<std::string>::const_iterator iter = mime_types.begin();
159 iter != mime_types.end(); ++iter) {
160 base::StringValue* value = new base::StringValue(task_id);
161 mime_type_pref->SetWithoutPathExpansion(*iter, value);
165 if (!suffixes.empty()) {
166 DictionaryPrefUpdate mime_type_pref(pref_service,
167 prefs::kDefaultTasksBySuffix);
168 for (std::set<std::string>::const_iterator iter = suffixes.begin();
169 iter != suffixes.end(); ++iter) {
170 base::StringValue* value = new base::StringValue(task_id);
171 // Suffixes are case insensitive.
172 std::string lower_suffix = base::ToLowerASCII(*iter);
173 mime_type_pref->SetWithoutPathExpansion(lower_suffix, value);
178 std::string GetDefaultTaskIdFromPrefs(const PrefService& pref_service,
179 const std::string& mime_type,
180 const std::string& suffix) {
181 VLOG(1) << "Looking for default for MIME type: " << mime_type
182 << " and suffix: " << suffix;
183 std::string task_id;
184 if (!mime_type.empty()) {
185 const base::DictionaryValue* mime_task_prefs =
186 pref_service.GetDictionary(prefs::kDefaultTasksByMimeType);
187 DCHECK(mime_task_prefs);
188 LOG_IF(ERROR, !mime_task_prefs) << "Unable to open MIME type prefs";
189 if (mime_task_prefs &&
190 mime_task_prefs->GetStringWithoutPathExpansion(mime_type, &task_id)) {
191 VLOG(1) << "Found MIME default handler: " << task_id;
192 return task_id;
196 const base::DictionaryValue* suffix_task_prefs =
197 pref_service.GetDictionary(prefs::kDefaultTasksBySuffix);
198 DCHECK(suffix_task_prefs);
199 LOG_IF(ERROR, !suffix_task_prefs) << "Unable to open suffix prefs";
200 std::string lower_suffix = base::ToLowerASCII(suffix);
201 if (suffix_task_prefs)
202 suffix_task_prefs->GetStringWithoutPathExpansion(lower_suffix, &task_id);
203 VLOG_IF(1, !task_id.empty()) << "Found suffix default handler: " << task_id;
204 return task_id;
207 std::string MakeTaskID(const std::string& app_id,
208 TaskType task_type,
209 const std::string& action_id) {
210 return base::StringPrintf("%s|%s|%s",
211 app_id.c_str(),
212 TaskTypeToString(task_type).c_str(),
213 action_id.c_str());
216 std::string TaskDescriptorToId(const TaskDescriptor& task_descriptor) {
217 return MakeTaskID(task_descriptor.app_id,
218 task_descriptor.task_type,
219 task_descriptor.action_id);
222 bool ParseTaskID(const std::string& task_id, TaskDescriptor* task) {
223 DCHECK(task);
225 std::vector<std::string> result = base::SplitString(
226 task_id, "|", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
228 // Parse a legacy task ID that only contain two parts. Drive tasks are
229 // identified by a prefix "drive-app:" on the extension ID. The legacy task
230 // IDs can be stored in preferences.
231 if (result.size() == 2) {
232 if (base::StartsWith(result[0], kDriveTaskExtensionPrefix,
233 base::CompareCase::SENSITIVE)) {
234 task->task_type = TASK_TYPE_DRIVE_APP;
235 task->app_id = result[0].substr(kDriveTaskExtensionPrefixLength);
236 } else {
237 task->task_type = TASK_TYPE_FILE_BROWSER_HANDLER;
238 task->app_id = result[0];
241 task->action_id = result[1];
243 return true;
246 if (result.size() != 3)
247 return false;
249 TaskType task_type = StringToTaskType(result[1]);
250 if (task_type == TASK_TYPE_UNKNOWN)
251 return false;
253 task->app_id = result[0];
254 task->task_type = task_type;
255 task->action_id = result[2];
257 return true;
260 bool ExecuteFileTask(Profile* profile,
261 const GURL& source_url,
262 const TaskDescriptor& task,
263 const std::vector<FileSystemURL>& file_urls,
264 const FileTaskFinishedCallback& done) {
265 // drive::FileTaskExecutor is responsible to handle drive tasks.
266 if (task.task_type == TASK_TYPE_DRIVE_APP) {
267 DCHECK_EQ(kDriveAppActionID, task.action_id);
268 drive::FileTaskExecutor* executor =
269 new drive::FileTaskExecutor(profile, task.app_id);
270 executor->Execute(file_urls, done);
271 return true;
274 // Get the extension.
275 const Extension* extension = extensions::ExtensionRegistry::Get(
276 profile)->enabled_extensions().GetByID(task.app_id);
277 if (!extension)
278 return false;
280 // Execute the task.
281 if (task.task_type == TASK_TYPE_FILE_BROWSER_HANDLER) {
282 return file_browser_handlers::ExecuteFileBrowserHandler(
283 profile,
284 extension,
285 task.action_id,
286 file_urls,
287 done);
288 } else if (task.task_type == TASK_TYPE_FILE_HANDLER) {
289 std::vector<base::FilePath> paths;
290 for (size_t i = 0; i != file_urls.size(); ++i)
291 paths.push_back(file_urls[i].path());
292 apps::LaunchPlatformAppWithFileHandler(
293 profile, extension, task.action_id, paths);
294 if (!done.is_null())
295 done.Run(extensions::api::file_manager_private::TASK_RESULT_MESSAGE_SENT);
296 return true;
298 NOTREACHED();
299 return false;
302 void FindDriveAppTasks(
303 const drive::DriveAppRegistry& drive_app_registry,
304 const PathAndMimeTypeSet& path_mime_set,
305 std::vector<FullTaskDescriptor>* result_list) {
306 DCHECK(result_list);
308 bool is_first = true;
309 typedef std::map<std::string, drive::DriveAppInfo> DriveAppInfoMap;
310 DriveAppInfoMap drive_app_map;
312 for (PathAndMimeTypeSet::const_iterator it = path_mime_set.begin();
313 it != path_mime_set.end(); ++it) {
314 const base::FilePath& file_path = it->first;
315 const std::string& mime_type = it->second;
316 // Return immediately if a file not on Drive is found, as Drive app tasks
317 // work only if all files are on Drive.
318 if (!drive::util::IsUnderDriveMountPoint(file_path))
319 return;
321 std::vector<drive::DriveAppInfo> app_info_list;
322 drive_app_registry.GetAppsForFile(file_path.Extension(),
323 mime_type,
324 &app_info_list);
326 if (is_first) {
327 // For the first file, we store all the info.
328 for (size_t j = 0; j < app_info_list.size(); ++j)
329 drive_app_map[app_info_list[j].app_id] = app_info_list[j];
330 } else {
331 // For remaining files, take the intersection with the current
332 // result, based on the app id.
333 std::set<std::string> app_id_set;
334 for (size_t j = 0; j < app_info_list.size(); ++j)
335 app_id_set.insert(app_info_list[j].app_id);
336 for (DriveAppInfoMap::iterator iter = drive_app_map.begin();
337 iter != drive_app_map.end();) {
338 if (app_id_set.count(iter->first) == 0) {
339 drive_app_map.erase(iter++);
340 } else {
341 ++iter;
346 is_first = false;
349 for (DriveAppInfoMap::const_iterator iter = drive_app_map.begin();
350 iter != drive_app_map.end(); ++iter) {
351 const drive::DriveAppInfo& app_info = iter->second;
352 TaskDescriptor descriptor(app_info.app_id,
353 TASK_TYPE_DRIVE_APP,
354 kDriveAppActionID);
355 GURL icon_url = drive::util::FindPreferredIcon(
356 app_info.app_icons,
357 drive::util::kPreferredIconSize);
358 result_list->push_back(
359 FullTaskDescriptor(descriptor,
360 app_info.app_name,
361 icon_url,
362 false /* is_default */,
363 false /* is_generic_file_handler */));
367 bool IsGoodMatchFileHandler(
368 const extensions::FileHandlerInfo& file_handler_info,
369 const PathAndMimeTypeSet& path_mime_set) {
370 if (file_handler_info.extensions.count("*") > 0 ||
371 file_handler_info.types.count("*") > 0 ||
372 file_handler_info.types.count("*/*") > 0)
373 return false;
375 // If text/* file handler matches with unsupported text mime type, we don't
376 // regard it as good match.
377 if (file_handler_info.types.count("text/*")) {
378 for (const auto& path_mime : path_mime_set) {
379 if (mime_util::IsUnsupportedTextMimeType(path_mime.second))
380 return false;
384 return true;
387 void FindFileHandlerTasks(
388 Profile* profile,
389 const PathAndMimeTypeSet& path_mime_set,
390 std::vector<FullTaskDescriptor>* result_list) {
391 DCHECK(!path_mime_set.empty());
392 DCHECK(result_list);
394 const extensions::ExtensionSet& enabled_extensions =
395 extensions::ExtensionRegistry::Get(profile)->enabled_extensions();
397 for (extensions::ExtensionSet::const_iterator iter =
398 enabled_extensions.begin();
399 iter != enabled_extensions.end();
400 ++iter) {
401 const Extension* extension = iter->get();
403 // Check that the extension can be launched via an event. This includes all
404 // platform apps plus whitelisted extensions.
405 if (!CanLaunchViaEvent(extension))
406 continue;
408 // Ephemeral apps cannot be file handlers.
409 if (extensions::util::IsEphemeralApp(extension->id(), profile))
410 continue;
412 if (profile->IsOffTheRecord() &&
413 !extensions::util::IsIncognitoEnabled(extension->id(), profile))
414 continue;
416 typedef std::vector<const extensions::FileHandlerInfo*> FileHandlerList;
417 FileHandlerList file_handlers =
418 FindFileHandlersForFiles(*extension, path_mime_set);
419 if (file_handlers.empty())
420 continue;
422 // If the new ZIP unpacker is disabled, then hide its handlers, so we don't
423 // show both the legacy one and the new one in Files app for ZIP files.
424 if (extension->id() == extension_misc::kZIPUnpackerExtensionId &&
425 base::CommandLine::ForCurrentProcess()->HasSwitch(
426 chromeos::switches::kDisableNewZIPUnpacker)) {
427 continue;
430 // Show the first good matching handler of each app. If there doesn't exist
431 // such handler, show the first matching handler of the app.
432 const extensions::FileHandlerInfo* file_handler = file_handlers.front();
433 for (auto handler : file_handlers) {
434 if (IsGoodMatchFileHandler(*handler, path_mime_set)) {
435 file_handler = handler;
436 break;
440 std::string task_id = file_tasks::MakeTaskID(
441 extension->id(), file_tasks::TASK_TYPE_FILE_HANDLER, file_handler->id);
443 GURL best_icon = extensions::ExtensionIconSource::GetIconURL(
444 extension,
445 drive::util::kPreferredIconSize,
446 ExtensionIconSet::MATCH_BIGGER,
447 false, // grayscale
448 NULL); // exists
450 // If file handler doesn't match as good match, regards it as generic file
451 // handler.
452 const bool is_generic_file_handler =
453 !IsGoodMatchFileHandler(*file_handler, path_mime_set);
454 result_list->push_back(FullTaskDescriptor(
455 TaskDescriptor(extension->id(), file_tasks::TASK_TYPE_FILE_HANDLER,
456 file_handler->id),
457 extension->name(), best_icon, false /* is_default */,
458 is_generic_file_handler));
462 void FindFileBrowserHandlerTasks(
463 Profile* profile,
464 const std::vector<GURL>& file_urls,
465 std::vector<FullTaskDescriptor>* result_list) {
466 DCHECK(!file_urls.empty());
467 DCHECK(result_list);
469 file_browser_handlers::FileBrowserHandlerList common_tasks =
470 file_browser_handlers::FindFileBrowserHandlers(profile, file_urls);
471 if (common_tasks.empty())
472 return;
474 const extensions::ExtensionSet& enabled_extensions =
475 extensions::ExtensionRegistry::Get(profile)->enabled_extensions();
476 for (file_browser_handlers::FileBrowserHandlerList::const_iterator iter =
477 common_tasks.begin();
478 iter != common_tasks.end();
479 ++iter) {
480 const FileBrowserHandler* handler = *iter;
481 const std::string extension_id = handler->extension_id();
482 const Extension* extension = enabled_extensions.GetByID(extension_id);
483 DCHECK(extension);
485 // TODO(zelidrag): Figure out how to expose icon URL that task defined in
486 // manifest instead of the default extension icon.
487 const GURL icon_url = extensions::ExtensionIconSource::GetIconURL(
488 extension,
489 extension_misc::EXTENSION_ICON_BITTY,
490 ExtensionIconSet::MATCH_BIGGER,
491 false, // grayscale
492 NULL); // exists
494 result_list->push_back(FullTaskDescriptor(
495 TaskDescriptor(extension_id,
496 file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER,
497 handler->id()),
498 handler->title(),
499 icon_url,
500 false /* is_default */,
501 false /* is_generic_file_handler */));
505 void FindAllTypesOfTasks(
506 Profile* profile,
507 const drive::DriveAppRegistry* drive_app_registry,
508 const PathAndMimeTypeSet& path_mime_set,
509 const std::vector<GURL>& file_urls,
510 std::vector<FullTaskDescriptor>* result_list) {
511 DCHECK(profile);
512 DCHECK(result_list);
514 // Find Drive app tasks, if the drive app registry is present.
515 if (drive_app_registry)
516 FindDriveAppTasks(*drive_app_registry, path_mime_set, result_list);
518 // Find and append file handler tasks. We know there aren't duplicates
519 // because Drive apps and platform apps are entirely different kinds of
520 // tasks.
521 FindFileHandlerTasks(profile, path_mime_set, result_list);
523 // Find and append file browser handler tasks. We know there aren't
524 // duplicates because "file_browser_handlers" and "file_handlers" shouldn't
525 // be used in the same manifest.json.
526 FindFileBrowserHandlerTasks(profile, file_urls, result_list);
528 // Google documents can only be handled by internal handlers.
529 if (ContainsGoogleDocument(path_mime_set))
530 KeepOnlyFileManagerInternalTasks(result_list);
532 ChooseAndSetDefaultTask(*profile->GetPrefs(), path_mime_set, result_list);
535 void ChooseAndSetDefaultTask(const PrefService& pref_service,
536 const PathAndMimeTypeSet& path_mime_set,
537 std::vector<FullTaskDescriptor>* tasks) {
538 // Collect the task IDs of default tasks from the preferences into a set.
539 std::set<std::string> default_task_ids;
540 for (PathAndMimeTypeSet::const_iterator it = path_mime_set.begin();
541 it != path_mime_set.end(); ++it) {
542 const base::FilePath& file_path = it->first;
543 const std::string& mime_type = it->second;
544 std::string task_id = file_tasks::GetDefaultTaskIdFromPrefs(
545 pref_service, mime_type, file_path.Extension());
546 default_task_ids.insert(task_id);
549 // Go through all the tasks from the beginning and see if there is any
550 // default task. If found, pick and set it as default and return.
551 for (size_t i = 0; i < tasks->size(); ++i) {
552 FullTaskDescriptor* task = &tasks->at(i);
553 DCHECK(!task->is_default());
554 const std::string task_id = TaskDescriptorToId(task->task_descriptor());
555 if (ContainsKey(default_task_ids, task_id)) {
556 task->set_is_default(true);
557 return;
561 // No default tasks found. If there is any fallback file browser handler,
562 // make it as default task, so it's selected by default.
563 for (size_t i = 0; i < tasks->size(); ++i) {
564 FullTaskDescriptor* task = &tasks->at(i);
565 DCHECK(!task->is_default());
566 if (IsFallbackFileHandler(task->task_descriptor())) {
567 task->set_is_default(true);
568 return;
573 } // namespace file_tasks
574 } // namespace file_manager