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.
TaskTypeToString(TaskType task_type)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.
StringToTaskType(const std::string & str)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.
ContainsGoogleDocument(const PathAndMimeTypeSet & path_mime_set)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.
KeepOnlyFileManagerInternalTasks(std::vector<FullTaskDescriptor> * tasks)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.
IsFallbackFileHandler(const file_tasks::TaskDescriptor & task)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
FullTaskDescriptor(const TaskDescriptor & task_descriptor,const std::string & task_title,const GURL & icon_url,bool is_default)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
UpdateDefaultTask(PrefService * pref_service,const std::string & task_id,const std::set<std::string> & suffixes,const std::set<std::string> & mime_types)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
GetDefaultTaskIdFromPrefs(const PrefService & pref_service,const std::string & mime_type,const std::string & suffix)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
MakeTaskID(const std::string & app_id,TaskType task_type,const std::string & action_id)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
TaskDescriptorToId(const TaskDescriptor & task_descriptor)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
ParseTaskID(const std::string & task_id,TaskDescriptor * task)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
ExecuteFileTask(Profile * profile,const GURL & source_url,const TaskDescriptor & task,const std::vector<FileSystemURL> & file_urls,const FileTaskFinishedCallback & done)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
FindDriveAppTasks(const drive::DriveAppRegistry & drive_app_registry,const PathAndMimeTypeSet & path_mime_set,std::vector<FullTaskDescriptor> * result_list)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
FindFileHandlerTasks(Profile * profile,const PathAndMimeTypeSet & path_mime_set,std::vector<FullTaskDescriptor> * result_list)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
FindFileBrowserHandlerTasks(Profile * profile,const std::vector<GURL> & file_urls,std::vector<FullTaskDescriptor> * result_list)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
FindAllTypesOfTasks(Profile * profile,const drive::DriveAppRegistry * drive_app_registry,const PathAndMimeTypeSet & path_mime_set,const std::vector<GURL> & file_urls,std::vector<FullTaskDescriptor> * result_list)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
ChooseAndSetDefaultTask(const PrefService & pref_service,const PathAndMimeTypeSet & path_mime_set,std::vector<FullTaskDescriptor> * tasks)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