Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / api / media_galleries / media_galleries_api.cc
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 // Implements the Chrome Extensions Media Galleries API.
6
7 #include "chrome/browser/extensions/api/media_galleries/media_galleries_api.h"
8
9 #include <set>
10 #include <string>
11 #include <vector>
12
13 #include "apps/app_window.h"
14 #include "apps/app_window_registry.h"
15 #include "base/callback.h"
16 #include "base/lazy_instance.h"
17 #include "base/numerics/safe_conversions.h"
18 #include "base/stl_util.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "base/values.h"
22 #include "chrome/browser/browser_process.h"
23 #include "chrome/browser/extensions/api/file_system/file_system_api.h"
24 #include "chrome/browser/extensions/blob_reader.h"
25 #include "chrome/browser/extensions/extension_tab_util.h"
26 #include "chrome/browser/media_galleries/fileapi/safe_media_metadata_parser.h"
27 #include "chrome/browser/media_galleries/gallery_watch_manager.h"
28 #include "chrome/browser/media_galleries/media_file_system_registry.h"
29 #include "chrome/browser/media_galleries/media_galleries_histograms.h"
30 #include "chrome/browser/media_galleries/media_galleries_permission_controller.h"
31 #include "chrome/browser/media_galleries/media_galleries_preferences.h"
32 #include "chrome/browser/media_galleries/media_galleries_scan_result_controller.h"
33 #include "chrome/browser/media_galleries/media_scan_manager.h"
34 #include "chrome/browser/platform_util.h"
35 #include "chrome/browser/profiles/profile.h"
36 #include "chrome/browser/ui/chrome_select_file_policy.h"
37 #include "chrome/common/extensions/api/media_galleries.h"
38 #include "chrome/common/pref_names.h"
39 #include "components/storage_monitor/storage_info.h"
40 #include "components/web_modal/web_contents_modal_dialog_manager.h"
41 #include "content/public/browser/blob_handle.h"
42 #include "content/public/browser/browser_context.h"
43 #include "content/public/browser/browser_thread.h"
44 #include "content/public/browser/child_process_security_policy.h"
45 #include "content/public/browser/render_process_host.h"
46 #include "content/public/browser/render_view_host.h"
47 #include "content/public/browser/web_contents.h"
48 #include "extensions/browser/blob_holder.h"
49 #include "extensions/browser/extension_prefs.h"
50 #include "extensions/browser/extension_system.h"
51 #include "extensions/common/extension.h"
52 #include "extensions/common/permissions/api_permission.h"
53 #include "extensions/common/permissions/media_galleries_permission.h"
54 #include "extensions/common/permissions/permissions_data.h"
55 #include "grit/generated_resources.h"
56 #include "net/base/mime_sniffer.h"
57 #include "ui/base/l10n/l10n_util.h"
58 #include "webkit/browser/blob/blob_data_handle.h"
59
60 using content::WebContents;
61 using storage_monitor::MediaStorageUtil;
62 using storage_monitor::StorageInfo;
63 using web_modal::WebContentsModalDialogManager;
64
65 namespace extensions {
66
67 namespace MediaGalleries = api::media_galleries;
68 namespace DropPermissionForMediaFileSystem =
69     MediaGalleries::DropPermissionForMediaFileSystem;
70 namespace GetMediaFileSystems = MediaGalleries::GetMediaFileSystems;
71
72 namespace {
73
74 const char kDisallowedByPolicy[] =
75     "Media Galleries API is disallowed by policy: ";
76 const char kFailedToSetGalleryPermission[] =
77     "Failed to set gallery permission.";
78 const char kInvalidGalleryId[] = "Invalid gallery id.";
79 const char kMissingEventListener[] = "Missing event listener registration.";
80 const char kNonExistentGalleryId[] = "Non-existent gallery id.";
81 const char kNoScanPermission[] = "No permission to scan.";
82
83 const char kDeviceIdKey[] = "deviceId";
84 const char kGalleryIdKey[] = "galleryId";
85 const char kIsAvailableKey[] = "isAvailable";
86 const char kIsMediaDeviceKey[] = "isMediaDevice";
87 const char kIsRemovableKey[] = "isRemovable";
88 const char kNameKey[] = "name";
89
90 const char kMetadataKey[] = "metadata";
91 const char kAttachedImagesBlobInfoKey[] = "attachedImagesBlobInfo";
92 const char kBlobUUIDKey[] = "blobUUID";
93 const char kTypeKey[] = "type";
94 const char kSizeKey[] = "size";
95
96 MediaFileSystemRegistry* media_file_system_registry() {
97   return g_browser_process->media_file_system_registry();
98 }
99
100 GalleryWatchManager* gallery_watch_manager() {
101   return media_file_system_registry()->gallery_watch_manager();
102 }
103
104 MediaScanManager* media_scan_manager() {
105   return media_file_system_registry()->media_scan_manager();
106 }
107
108 // Checks whether the MediaGalleries API is currently accessible (it may be
109 // disallowed even if an extension has the requisite permission). Then
110 // initializes the MediaGalleriesPreferences
111 bool Setup(Profile* profile, std::string* error, base::Closure callback) {
112   if (!ChromeSelectFilePolicy::FileSelectDialogsAllowed()) {
113     *error = std::string(kDisallowedByPolicy) +
114         prefs::kAllowFileSelectionDialogs;
115     return false;
116   }
117
118   MediaGalleriesPreferences* preferences =
119       media_file_system_registry()->GetPreferences(profile);
120   preferences->EnsureInitialized(callback);
121   return true;
122 }
123
124 WebContents* GetWebContents(content::RenderViewHost* rvh,
125                             Profile* profile,
126                             const std::string& app_id) {
127   WebContents* contents = WebContents::FromRenderViewHost(rvh);
128   WebContentsModalDialogManager* web_contents_modal_dialog_manager =
129       WebContentsModalDialogManager::FromWebContents(contents);
130   if (!web_contents_modal_dialog_manager) {
131     // If there is no WebContentsModalDialogManager, then this contents is
132     // probably the background page for an app. Try to find a app window to
133     // host the dialog.
134     apps::AppWindow* window = apps::AppWindowRegistry::Get(profile)
135                                   ->GetCurrentAppWindowForApp(app_id);
136     contents = window ? window->web_contents() : NULL;
137   }
138   return contents;
139 }
140
141 base::ListValue* ConstructFileSystemList(
142     content::RenderViewHost* rvh,
143     const Extension* extension,
144     const std::vector<MediaFileSystemInfo>& filesystems) {
145   if (!rvh)
146     return NULL;
147
148   MediaGalleriesPermission::CheckParam read_param(
149       MediaGalleriesPermission::kReadPermission);
150   const PermissionsData* permissions_data = extension->permissions_data();
151   bool has_read_permission = permissions_data->CheckAPIPermissionWithParam(
152       APIPermission::kMediaGalleries, &read_param);
153   MediaGalleriesPermission::CheckParam copy_to_param(
154       MediaGalleriesPermission::kCopyToPermission);
155   bool has_copy_to_permission = permissions_data->CheckAPIPermissionWithParam(
156       APIPermission::kMediaGalleries, &copy_to_param);
157   MediaGalleriesPermission::CheckParam delete_param(
158       MediaGalleriesPermission::kDeletePermission);
159   bool has_delete_permission = permissions_data->CheckAPIPermissionWithParam(
160       APIPermission::kMediaGalleries, &delete_param);
161
162   const int child_id = rvh->GetProcess()->GetID();
163   scoped_ptr<base::ListValue> list(new base::ListValue());
164   for (size_t i = 0; i < filesystems.size(); ++i) {
165     scoped_ptr<base::DictionaryValue> file_system_dict_value(
166         new base::DictionaryValue());
167
168     // Send the file system id so the renderer can create a valid FileSystem
169     // object.
170     file_system_dict_value->SetStringWithoutPathExpansion(
171         "fsid", filesystems[i].fsid);
172
173     file_system_dict_value->SetStringWithoutPathExpansion(
174         kNameKey, filesystems[i].name);
175     file_system_dict_value->SetStringWithoutPathExpansion(
176         kGalleryIdKey,
177         base::Uint64ToString(filesystems[i].pref_id));
178     if (!filesystems[i].transient_device_id.empty()) {
179       file_system_dict_value->SetStringWithoutPathExpansion(
180           kDeviceIdKey, filesystems[i].transient_device_id);
181     }
182     file_system_dict_value->SetBooleanWithoutPathExpansion(
183         kIsRemovableKey, filesystems[i].removable);
184     file_system_dict_value->SetBooleanWithoutPathExpansion(
185         kIsMediaDeviceKey, filesystems[i].media_device);
186     file_system_dict_value->SetBooleanWithoutPathExpansion(
187         kIsAvailableKey, true);
188
189     list->Append(file_system_dict_value.release());
190
191     if (filesystems[i].path.empty())
192       continue;
193
194     if (has_read_permission) {
195       content::ChildProcessSecurityPolicy* policy =
196           content::ChildProcessSecurityPolicy::GetInstance();
197       policy->GrantReadFile(child_id, filesystems[i].path);
198       if (has_delete_permission) {
199         policy->GrantDeleteFrom(child_id, filesystems[i].path);
200         if (has_copy_to_permission) {
201           policy->GrantCopyInto(child_id, filesystems[i].path);
202         }
203       }
204     }
205   }
206
207   return list.release();
208 }
209
210 bool CheckScanPermission(const extensions::Extension* extension,
211                          std::string* error) {
212   DCHECK(extension);
213   DCHECK(error);
214   MediaGalleriesPermission::CheckParam scan_param(
215       MediaGalleriesPermission::kScanPermission);
216   bool has_scan_permission =
217       extension->permissions_data()->CheckAPIPermissionWithParam(
218           APIPermission::kMediaGalleries, &scan_param);
219   if (!has_scan_permission)
220     *error = kNoScanPermission;
221   return has_scan_permission;
222 }
223
224 class SelectDirectoryDialog : public ui::SelectFileDialog::Listener,
225                               public base::RefCounted<SelectDirectoryDialog> {
226  public:
227   // Selected file path, or an empty path if the user canceled.
228   typedef base::Callback<void(const base::FilePath&)> Callback;
229
230   SelectDirectoryDialog(WebContents* web_contents, const Callback& callback)
231       : web_contents_(web_contents),
232         callback_(callback) {
233     select_file_dialog_ = ui::SelectFileDialog::Create(
234         this, new ChromeSelectFilePolicy(web_contents));
235   }
236
237   void Show(const base::FilePath& default_path) {
238     AddRef();  // Balanced in the two reachable listener outcomes.
239     select_file_dialog_->SelectFile(
240       ui::SelectFileDialog::SELECT_FOLDER,
241       l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_DIALOG_ADD_GALLERY_TITLE),
242       default_path,
243       NULL,
244       0,
245       base::FilePath::StringType(),
246       platform_util::GetTopLevel(web_contents_->GetNativeView()),
247       NULL);
248   }
249
250   // ui::SelectFileDialog::Listener implementation.
251   virtual void FileSelected(const base::FilePath& path,
252                             int index,
253                             void* params) OVERRIDE {
254     callback_.Run(path);
255     Release();  // Balanced in Show().
256   }
257
258   virtual void MultiFilesSelected(const std::vector<base::FilePath>& files,
259                                   void* params) OVERRIDE {
260     NOTREACHED() << "Should not be able to select multiple files";
261   }
262
263   virtual void FileSelectionCanceled(void* params) OVERRIDE {
264     callback_.Run(base::FilePath());
265     Release();  // Balanced in Show().
266   }
267
268  private:
269   friend class base::RefCounted<SelectDirectoryDialog>;
270   virtual ~SelectDirectoryDialog() {}
271
272   scoped_refptr<ui::SelectFileDialog> select_file_dialog_;
273   WebContents* web_contents_;
274   Callback callback_;
275
276   DISALLOW_COPY_AND_ASSIGN(SelectDirectoryDialog);
277 };
278
279 }  // namespace
280
281 MediaGalleriesEventRouter::MediaGalleriesEventRouter(
282     content::BrowserContext* context)
283     : profile_(Profile::FromBrowserContext(context)), weak_ptr_factory_(this) {
284   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
285   DCHECK(profile_);
286
287   EventRouter::Get(profile_)->RegisterObserver(
288       this, MediaGalleries::OnGalleryChanged::kEventName);
289
290   gallery_watch_manager()->AddObserver(profile_, this);
291   media_scan_manager()->AddObserver(profile_, this);
292 }
293
294 MediaGalleriesEventRouter::~MediaGalleriesEventRouter() {
295 }
296
297 void MediaGalleriesEventRouter::Shutdown() {
298   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
299   weak_ptr_factory_.InvalidateWeakPtrs();
300
301   EventRouter::Get(profile_)->UnregisterObserver(this);
302
303   gallery_watch_manager()->RemoveObserver(profile_);
304   media_scan_manager()->RemoveObserver(profile_);
305   media_scan_manager()->CancelScansForProfile(profile_);
306 }
307
308 static base::LazyInstance<
309     BrowserContextKeyedAPIFactory<MediaGalleriesEventRouter> > g_factory =
310     LAZY_INSTANCE_INITIALIZER;
311
312 // static
313 BrowserContextKeyedAPIFactory<MediaGalleriesEventRouter>*
314 MediaGalleriesEventRouter::GetFactoryInstance() {
315   return g_factory.Pointer();
316 }
317
318 // static
319 MediaGalleriesEventRouter* MediaGalleriesEventRouter::Get(
320     content::BrowserContext* context) {
321   DCHECK(media_file_system_registry()
322              ->GetPreferences(Profile::FromBrowserContext(context))
323              ->IsInitialized());
324   return BrowserContextKeyedAPIFactory<MediaGalleriesEventRouter>::Get(context);
325 }
326
327 bool MediaGalleriesEventRouter::ExtensionHasGalleryChangeListener(
328     const std::string& extension_id) const {
329   return EventRouter::Get(profile_)->ExtensionHasEventListener(
330       extension_id, MediaGalleries::OnGalleryChanged::kEventName);
331 }
332
333 bool MediaGalleriesEventRouter::ExtensionHasScanProgressListener(
334     const std::string& extension_id) const {
335   return EventRouter::Get(profile_)->ExtensionHasEventListener(
336       extension_id, MediaGalleries::OnScanProgress::kEventName);
337 }
338
339 void MediaGalleriesEventRouter::OnScanStarted(const std::string& extension_id) {
340   MediaGalleries::ScanProgressDetails details;
341   details.type = MediaGalleries::SCAN_PROGRESS_TYPE_START;
342   DispatchEventToExtension(
343       extension_id,
344       MediaGalleries::OnScanProgress::kEventName,
345       MediaGalleries::OnScanProgress::Create(details).Pass());
346 }
347
348 void MediaGalleriesEventRouter::OnScanCancelled(
349     const std::string& extension_id) {
350   MediaGalleries::ScanProgressDetails details;
351   details.type = MediaGalleries::SCAN_PROGRESS_TYPE_CANCEL;
352   DispatchEventToExtension(
353       extension_id,
354       MediaGalleries::OnScanProgress::kEventName,
355       MediaGalleries::OnScanProgress::Create(details).Pass());
356 }
357
358 void MediaGalleriesEventRouter::OnScanFinished(
359     const std::string& extension_id, int gallery_count,
360     const MediaGalleryScanResult& file_counts) {
361   media_galleries::UsageCount(media_galleries::SCAN_FINISHED);
362   MediaGalleries::ScanProgressDetails details;
363   details.type = MediaGalleries::SCAN_PROGRESS_TYPE_FINISH;
364   details.gallery_count.reset(new int(gallery_count));
365   details.audio_count.reset(new int(file_counts.audio_count));
366   details.image_count.reset(new int(file_counts.image_count));
367   details.video_count.reset(new int(file_counts.video_count));
368   DispatchEventToExtension(
369       extension_id,
370       MediaGalleries::OnScanProgress::kEventName,
371       MediaGalleries::OnScanProgress::Create(details).Pass());
372 }
373
374 void MediaGalleriesEventRouter::OnScanError(
375     const std::string& extension_id) {
376   MediaGalleries::ScanProgressDetails details;
377   details.type = MediaGalleries::SCAN_PROGRESS_TYPE_ERROR;
378   DispatchEventToExtension(
379       extension_id,
380       MediaGalleries::OnScanProgress::kEventName,
381       MediaGalleries::OnScanProgress::Create(details).Pass());
382 }
383
384 void MediaGalleriesEventRouter::DispatchEventToExtension(
385     const std::string& extension_id,
386     const std::string& event_name,
387     scoped_ptr<base::ListValue> event_args) {
388   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
389   EventRouter* router = EventRouter::Get(profile_);
390   if (!router->ExtensionHasEventListener(extension_id, event_name))
391     return;
392
393   scoped_ptr<extensions::Event> event(
394       new extensions::Event(event_name, event_args.Pass()));
395   router->DispatchEventToExtension(extension_id, event.Pass());
396 }
397
398 void MediaGalleriesEventRouter::OnGalleryChanged(
399     const std::string& extension_id, MediaGalleryPrefId gallery_id) {
400   MediaGalleries::GalleryChangeDetails details;
401   details.type = MediaGalleries::GALLERY_CHANGE_TYPE_CONTENTS_CHANGED;
402   details.gallery_id = gallery_id;
403   DispatchEventToExtension(
404       extension_id,
405       MediaGalleries::OnGalleryChanged::kEventName,
406       MediaGalleries::OnGalleryChanged::Create(details).Pass());
407 }
408
409 void MediaGalleriesEventRouter::OnGalleryWatchDropped(
410     const std::string& extension_id, MediaGalleryPrefId gallery_id) {
411   MediaGalleries::GalleryChangeDetails details;
412   details.type = MediaGalleries::GALLERY_CHANGE_TYPE_WATCH_DROPPED;
413   details.gallery_id = gallery_id;
414   DispatchEventToExtension(
415       extension_id,
416       MediaGalleries::OnGalleryChanged::kEventName,
417       MediaGalleries::OnGalleryChanged::Create(details).Pass());
418 }
419
420 void MediaGalleriesEventRouter::OnListenerRemoved(
421     const EventListenerInfo& details) {
422   if (details.event_name == MediaGalleries::OnGalleryChanged::kEventName &&
423       !ExtensionHasGalleryChangeListener(details.extension_id)) {
424     gallery_watch_manager()->RemoveAllWatches(profile_, details.extension_id);
425   }
426 }
427
428 MediaGalleriesGetMediaFileSystemsFunction::
429     ~MediaGalleriesGetMediaFileSystemsFunction() {}
430
431 bool MediaGalleriesGetMediaFileSystemsFunction::RunAsync() {
432   media_galleries::UsageCount(media_galleries::GET_MEDIA_FILE_SYSTEMS);
433   scoped_ptr<GetMediaFileSystems::Params> params(
434       GetMediaFileSystems::Params::Create(*args_));
435   EXTENSION_FUNCTION_VALIDATE(params.get());
436   MediaGalleries::GetMediaFileSystemsInteractivity interactive =
437       MediaGalleries::GET_MEDIA_FILE_SYSTEMS_INTERACTIVITY_NO;
438   if (params->details.get() && params->details->interactive != MediaGalleries::
439          GET_MEDIA_FILE_SYSTEMS_INTERACTIVITY_NONE) {
440     interactive = params->details->interactive;
441   }
442
443   return Setup(GetProfile(), &error_, base::Bind(
444       &MediaGalleriesGetMediaFileSystemsFunction::OnPreferencesInit, this,
445       interactive));
446 }
447
448 void MediaGalleriesGetMediaFileSystemsFunction::OnPreferencesInit(
449     MediaGalleries::GetMediaFileSystemsInteractivity interactive) {
450   switch (interactive) {
451     case MediaGalleries::GET_MEDIA_FILE_SYSTEMS_INTERACTIVITY_YES: {
452       // The MediaFileSystemRegistry only updates preferences for extensions
453       // that it knows are in use. Since this may be the first call to
454       // chrome.getMediaFileSystems for this extension, call
455       // GetMediaFileSystemsForExtension() here solely so that
456       // MediaFileSystemRegistry will send preference changes.
457       GetMediaFileSystemsForExtension(base::Bind(
458           &MediaGalleriesGetMediaFileSystemsFunction::AlwaysShowDialog, this));
459       return;
460     }
461     case MediaGalleries::GET_MEDIA_FILE_SYSTEMS_INTERACTIVITY_IF_NEEDED: {
462       GetMediaFileSystemsForExtension(base::Bind(
463           &MediaGalleriesGetMediaFileSystemsFunction::ShowDialogIfNoGalleries,
464           this));
465       return;
466     }
467     case MediaGalleries::GET_MEDIA_FILE_SYSTEMS_INTERACTIVITY_NO:
468       GetAndReturnGalleries();
469       return;
470     case MediaGalleries::GET_MEDIA_FILE_SYSTEMS_INTERACTIVITY_NONE:
471       NOTREACHED();
472   }
473   SendResponse(false);
474 }
475
476 void MediaGalleriesGetMediaFileSystemsFunction::AlwaysShowDialog(
477     const std::vector<MediaFileSystemInfo>& /*filesystems*/) {
478   ShowDialog();
479 }
480
481 void MediaGalleriesGetMediaFileSystemsFunction::ShowDialogIfNoGalleries(
482     const std::vector<MediaFileSystemInfo>& filesystems) {
483   if (filesystems.empty())
484     ShowDialog();
485   else
486     ReturnGalleries(filesystems);
487 }
488
489 void MediaGalleriesGetMediaFileSystemsFunction::GetAndReturnGalleries() {
490   GetMediaFileSystemsForExtension(base::Bind(
491       &MediaGalleriesGetMediaFileSystemsFunction::ReturnGalleries, this));
492 }
493
494 void MediaGalleriesGetMediaFileSystemsFunction::ReturnGalleries(
495     const std::vector<MediaFileSystemInfo>& filesystems) {
496   scoped_ptr<base::ListValue> list(
497       ConstructFileSystemList(render_view_host(), extension(), filesystems));
498   if (!list.get()) {
499     SendResponse(false);
500     return;
501   }
502
503   // The custom JS binding will use this list to create DOMFileSystem objects.
504   SetResult(list.release());
505   SendResponse(true);
506 }
507
508 void MediaGalleriesGetMediaFileSystemsFunction::ShowDialog() {
509   media_galleries::UsageCount(media_galleries::SHOW_DIALOG);
510   WebContents* contents =
511       GetWebContents(render_view_host(), GetProfile(), extension()->id());
512   if (!contents) {
513     SendResponse(false);
514     return;
515   }
516
517   // Controller will delete itself.
518   base::Closure cb = base::Bind(
519       &MediaGalleriesGetMediaFileSystemsFunction::GetAndReturnGalleries, this);
520   new MediaGalleriesPermissionController(contents, *extension(), cb);
521 }
522
523 void MediaGalleriesGetMediaFileSystemsFunction::GetMediaFileSystemsForExtension(
524     const MediaFileSystemsCallback& cb) {
525   if (!render_view_host()) {
526     cb.Run(std::vector<MediaFileSystemInfo>());
527     return;
528   }
529   MediaFileSystemRegistry* registry = media_file_system_registry();
530   DCHECK(registry->GetPreferences(GetProfile())->IsInitialized());
531   registry->GetMediaFileSystemsForExtension(
532       render_view_host(), extension(), cb);
533 }
534
535 MediaGalleriesGetAllMediaFileSystemMetadataFunction::
536     ~MediaGalleriesGetAllMediaFileSystemMetadataFunction() {}
537
538 bool MediaGalleriesGetAllMediaFileSystemMetadataFunction::RunAsync() {
539   media_galleries::UsageCount(
540       media_galleries::GET_ALL_MEDIA_FILE_SYSTEM_METADATA);
541   return Setup(GetProfile(), &error_, base::Bind(
542       &MediaGalleriesGetAllMediaFileSystemMetadataFunction::OnPreferencesInit,
543       this));
544 }
545
546 void MediaGalleriesGetAllMediaFileSystemMetadataFunction::OnPreferencesInit() {
547   MediaFileSystemRegistry* registry = media_file_system_registry();
548   MediaGalleriesPreferences* prefs = registry->GetPreferences(GetProfile());
549   DCHECK(prefs->IsInitialized());
550   MediaGalleryPrefIdSet permitted_gallery_ids =
551       prefs->GalleriesForExtension(*extension());
552
553   MediaStorageUtil::DeviceIdSet* device_ids = new MediaStorageUtil::DeviceIdSet;
554   const MediaGalleriesPrefInfoMap& galleries = prefs->known_galleries();
555   for (MediaGalleryPrefIdSet::const_iterator it = permitted_gallery_ids.begin();
556        it != permitted_gallery_ids.end(); ++it) {
557     MediaGalleriesPrefInfoMap::const_iterator gallery_it = galleries.find(*it);
558     DCHECK(gallery_it != galleries.end());
559     device_ids->insert(gallery_it->second.device_id);
560   }
561
562   MediaStorageUtil::FilterAttachedDevices(
563       device_ids,
564       base::Bind(
565           &MediaGalleriesGetAllMediaFileSystemMetadataFunction::OnGetGalleries,
566           this,
567           permitted_gallery_ids,
568           base::Owned(device_ids)));
569 }
570
571 void MediaGalleriesGetAllMediaFileSystemMetadataFunction::OnGetGalleries(
572     const MediaGalleryPrefIdSet& permitted_gallery_ids,
573     const MediaStorageUtil::DeviceIdSet* available_devices) {
574   MediaFileSystemRegistry* registry = media_file_system_registry();
575   MediaGalleriesPreferences* prefs = registry->GetPreferences(GetProfile());
576
577   base::ListValue* list = new base::ListValue();
578   const MediaGalleriesPrefInfoMap& galleries = prefs->known_galleries();
579   for (MediaGalleryPrefIdSet::const_iterator it = permitted_gallery_ids.begin();
580        it != permitted_gallery_ids.end(); ++it) {
581     MediaGalleriesPrefInfoMap::const_iterator gallery_it = galleries.find(*it);
582     DCHECK(gallery_it != galleries.end());
583     const MediaGalleryPrefInfo& gallery = gallery_it->second;
584     MediaGalleries::MediaFileSystemMetadata metadata;
585     metadata.name = base::UTF16ToUTF8(gallery.GetGalleryDisplayName());
586     metadata.gallery_id = base::Uint64ToString(gallery.pref_id);
587     metadata.is_removable = StorageInfo::IsRemovableDevice(gallery.device_id);
588     metadata.is_media_device = StorageInfo::IsMediaDevice(gallery.device_id);
589     metadata.is_available = ContainsKey(*available_devices, gallery.device_id);
590     list->Append(metadata.ToValue().release());
591   }
592
593   SetResult(list);
594   SendResponse(true);
595 }
596
597 MediaGalleriesAddUserSelectedFolderFunction::
598     ~MediaGalleriesAddUserSelectedFolderFunction() {}
599
600 bool MediaGalleriesAddUserSelectedFolderFunction::RunAsync() {
601   media_galleries::UsageCount(media_galleries::ADD_USER_SELECTED_FOLDER);
602   return Setup(GetProfile(), &error_, base::Bind(
603       &MediaGalleriesAddUserSelectedFolderFunction::OnPreferencesInit, this));
604 }
605
606 void MediaGalleriesAddUserSelectedFolderFunction::OnPreferencesInit() {
607   Profile* profile = GetProfile();
608   const std::string& app_id = extension()->id();
609   WebContents* contents = GetWebContents(render_view_host(), profile, app_id);
610   if (!contents) {
611     // When the request originated from a background page, but there is no app
612     // window open, check to see if it originated from a tab and display the
613     // dialog in that tab.
614     bool found_tab = extensions::ExtensionTabUtil::GetTabById(
615         source_tab_id(), profile, profile->IsOffTheRecord(),
616         NULL, NULL, &contents, NULL);
617     if (!found_tab || !contents) {
618       SendResponse(false);
619       return;
620     }
621   }
622
623   if (!user_gesture()) {
624     OnDirectorySelected(base::FilePath());
625     return;
626   }
627
628   base::FilePath last_used_path =
629       extensions::file_system_api::GetLastChooseEntryDirectory(
630           extensions::ExtensionPrefs::Get(profile), app_id);
631   SelectDirectoryDialog::Callback callback = base::Bind(
632       &MediaGalleriesAddUserSelectedFolderFunction::OnDirectorySelected, this);
633   scoped_refptr<SelectDirectoryDialog> select_directory_dialog =
634       new SelectDirectoryDialog(contents, callback);
635   select_directory_dialog->Show(last_used_path);
636 }
637
638 void MediaGalleriesAddUserSelectedFolderFunction::OnDirectorySelected(
639     const base::FilePath& selected_directory) {
640   if (selected_directory.empty()) {
641     // User cancelled case.
642     GetMediaFileSystemsForExtension(base::Bind(
643         &MediaGalleriesAddUserSelectedFolderFunction::ReturnGalleriesAndId,
644         this,
645         kInvalidMediaGalleryPrefId));
646     return;
647   }
648
649   extensions::file_system_api::SetLastChooseEntryDirectory(
650       extensions::ExtensionPrefs::Get(GetProfile()),
651       extension()->id(),
652       selected_directory);
653
654   MediaGalleriesPreferences* preferences =
655       media_file_system_registry()->GetPreferences(GetProfile());
656   MediaGalleryPrefId pref_id =
657       preferences->AddGalleryByPath(selected_directory,
658                                     MediaGalleryPrefInfo::kUserAdded);
659   preferences->SetGalleryPermissionForExtension(*extension(), pref_id, true);
660
661   GetMediaFileSystemsForExtension(base::Bind(
662       &MediaGalleriesAddUserSelectedFolderFunction::ReturnGalleriesAndId,
663       this,
664       pref_id));
665 }
666
667 void MediaGalleriesAddUserSelectedFolderFunction::ReturnGalleriesAndId(
668     MediaGalleryPrefId pref_id,
669     const std::vector<MediaFileSystemInfo>& filesystems) {
670   scoped_ptr<base::ListValue> list(
671       ConstructFileSystemList(render_view_host(), extension(), filesystems));
672   if (!list.get()) {
673     SendResponse(false);
674     return;
675   }
676
677   int index = -1;
678   if (pref_id != kInvalidMediaGalleryPrefId) {
679     for (size_t i = 0; i < filesystems.size(); ++i) {
680       if (filesystems[i].pref_id == pref_id) {
681         index = i;
682         break;
683       }
684     }
685   }
686   base::DictionaryValue* results = new base::DictionaryValue;
687   results->SetWithoutPathExpansion("mediaFileSystems", list.release());
688   results->SetIntegerWithoutPathExpansion("selectedFileSystemIndex", index);
689   SetResult(results);
690   SendResponse(true);
691 }
692
693 void
694 MediaGalleriesAddUserSelectedFolderFunction::GetMediaFileSystemsForExtension(
695     const MediaFileSystemsCallback& cb) {
696   if (!render_view_host()) {
697     cb.Run(std::vector<MediaFileSystemInfo>());
698     return;
699   }
700   MediaFileSystemRegistry* registry = media_file_system_registry();
701   DCHECK(registry->GetPreferences(GetProfile())->IsInitialized());
702   registry->GetMediaFileSystemsForExtension(
703       render_view_host(), extension(), cb);
704 }
705
706 MediaGalleriesDropPermissionForMediaFileSystemFunction::
707     ~MediaGalleriesDropPermissionForMediaFileSystemFunction() {}
708
709 bool MediaGalleriesDropPermissionForMediaFileSystemFunction::RunAsync() {
710   media_galleries::UsageCount(
711       media_galleries::DROP_PERMISSION_FOR_MEDIA_FILE_SYSTEM);
712
713   scoped_ptr<DropPermissionForMediaFileSystem::Params> params(
714       DropPermissionForMediaFileSystem::Params::Create(*args_));
715   EXTENSION_FUNCTION_VALIDATE(params.get());
716   MediaGalleryPrefId pref_id;
717   if (!base::StringToUint64(params->gallery_id, &pref_id)) {
718     error_ = kInvalidGalleryId;
719     return false;
720   }
721
722   base::Closure callback = base::Bind(
723       &MediaGalleriesDropPermissionForMediaFileSystemFunction::
724           OnPreferencesInit,
725       this,
726       pref_id);
727   return Setup(GetProfile(), &error_, callback);
728 }
729
730 void MediaGalleriesDropPermissionForMediaFileSystemFunction::OnPreferencesInit(
731     MediaGalleryPrefId pref_id) {
732   MediaGalleriesPreferences* preferences =
733       media_file_system_registry()->GetPreferences(GetProfile());
734   if (!ContainsKey(preferences->known_galleries(), pref_id)) {
735     error_ = kNonExistentGalleryId;
736     SendResponse(false);
737     return;
738   }
739
740   bool dropped = preferences->SetGalleryPermissionForExtension(
741       *extension(), pref_id, false);
742   if (dropped)
743     SetResult(new base::StringValue(base::Uint64ToString(pref_id)));
744   else
745     error_ = kFailedToSetGalleryPermission;
746   SendResponse(dropped);
747 }
748
749 MediaGalleriesStartMediaScanFunction::~MediaGalleriesStartMediaScanFunction() {}
750
751 bool MediaGalleriesStartMediaScanFunction::RunAsync() {
752   media_galleries::UsageCount(media_galleries::START_MEDIA_SCAN);
753   if (!CheckScanPermission(extension(), &error_)) {
754     MediaGalleriesEventRouter::Get(GetProfile())
755         ->OnScanError(extension()->id());
756     return false;
757   }
758   return Setup(GetProfile(), &error_, base::Bind(
759       &MediaGalleriesStartMediaScanFunction::OnPreferencesInit, this));
760 }
761
762 void MediaGalleriesStartMediaScanFunction::OnPreferencesInit() {
763   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
764   MediaGalleriesEventRouter* api = MediaGalleriesEventRouter::Get(GetProfile());
765   if (!api->ExtensionHasScanProgressListener(extension()->id())) {
766     error_ = kMissingEventListener;
767     SendResponse(false);
768     return;
769   }
770
771   media_scan_manager()->StartScan(GetProfile(), extension(), user_gesture());
772   SendResponse(true);
773 }
774
775 MediaGalleriesCancelMediaScanFunction::
776     ~MediaGalleriesCancelMediaScanFunction() {
777 }
778
779 bool MediaGalleriesCancelMediaScanFunction::RunAsync() {
780   media_galleries::UsageCount(media_galleries::CANCEL_MEDIA_SCAN);
781   if (!CheckScanPermission(extension(), &error_)) {
782     MediaGalleriesEventRouter::Get(GetProfile())
783         ->OnScanError(extension()->id());
784     return false;
785   }
786   return Setup(GetProfile(), &error_, base::Bind(
787       &MediaGalleriesCancelMediaScanFunction::OnPreferencesInit, this));
788 }
789
790 void MediaGalleriesCancelMediaScanFunction::OnPreferencesInit() {
791   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
792   media_scan_manager()->CancelScan(GetProfile(), extension());
793   SendResponse(true);
794 }
795
796 MediaGalleriesAddScanResultsFunction::~MediaGalleriesAddScanResultsFunction() {}
797
798 bool MediaGalleriesAddScanResultsFunction::RunAsync() {
799   media_galleries::UsageCount(media_galleries::ADD_SCAN_RESULTS);
800   if (!CheckScanPermission(extension(), &error_)) {
801     // We don't fire a scan progress error here, as it would be unintuitive.
802     return false;
803   }
804   if (!user_gesture())
805     return false;
806
807   return Setup(GetProfile(), &error_, base::Bind(
808       &MediaGalleriesAddScanResultsFunction::OnPreferencesInit, this));
809 }
810
811 MediaGalleriesScanResultController*
812 MediaGalleriesAddScanResultsFunction::MakeDialog(
813     content::WebContents* web_contents,
814     const extensions::Extension& extension,
815     const base::Closure& on_finish) {
816   // Controller will delete itself.
817   return new MediaGalleriesScanResultController(web_contents, extension,
818                                                 on_finish);
819 }
820
821 void MediaGalleriesAddScanResultsFunction::OnPreferencesInit() {
822   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
823   MediaGalleriesPreferences* preferences =
824       media_file_system_registry()->GetPreferences(GetProfile());
825   if (MediaGalleriesScanResultController::ScanResultCountForExtension(
826           preferences, extension()) == 0) {
827     GetAndReturnGalleries();
828     return;
829   }
830
831   WebContents* contents =
832       GetWebContents(render_view_host(), GetProfile(), extension()->id());
833   if (!contents) {
834     SendResponse(false);
835     return;
836   }
837
838   base::Closure cb = base::Bind(
839       &MediaGalleriesAddScanResultsFunction::GetAndReturnGalleries, this);
840   MakeDialog(contents, *extension(), cb);
841 }
842
843 void MediaGalleriesAddScanResultsFunction::GetAndReturnGalleries() {
844   if (!render_view_host()) {
845     ReturnGalleries(std::vector<MediaFileSystemInfo>());
846     return;
847   }
848   MediaFileSystemRegistry* registry = media_file_system_registry();
849   DCHECK(registry->GetPreferences(GetProfile())->IsInitialized());
850   registry->GetMediaFileSystemsForExtension(
851       render_view_host(),
852       extension(),
853       base::Bind(&MediaGalleriesAddScanResultsFunction::ReturnGalleries, this));
854 }
855
856 void MediaGalleriesAddScanResultsFunction::ReturnGalleries(
857     const std::vector<MediaFileSystemInfo>& filesystems) {
858   scoped_ptr<base::ListValue> list(
859       ConstructFileSystemList(render_view_host(), extension(), filesystems));
860   if (!list.get()) {
861     SendResponse(false);
862     return;
863   }
864
865   // The custom JS binding will use this list to create DOMFileSystem objects.
866   SetResult(list.release());
867   SendResponse(true);
868 }
869
870 MediaGalleriesGetMetadataFunction::~MediaGalleriesGetMetadataFunction() {}
871
872 bool MediaGalleriesGetMetadataFunction::RunAsync() {
873   std::string blob_uuid;
874   EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &blob_uuid));
875
876   const base::Value* options_value = NULL;
877   if (!args_->Get(1, &options_value))
878     return false;
879   scoped_ptr<MediaGalleries::MediaMetadataOptions> options =
880       MediaGalleries::MediaMetadataOptions::FromValue(*options_value);
881   if (!options)
882     return false;
883
884   return Setup(GetProfile(), &error_, base::Bind(
885       &MediaGalleriesGetMetadataFunction::OnPreferencesInit, this,
886       options->metadata_type, blob_uuid));
887 }
888
889 void MediaGalleriesGetMetadataFunction::OnPreferencesInit(
890     MediaGalleries::GetMetadataType metadata_type,
891     const std::string& blob_uuid) {
892   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
893
894   // BlobReader is self-deleting.
895   BlobReader* reader = new BlobReader(
896       GetProfile(),
897       blob_uuid,
898       base::Bind(&MediaGalleriesGetMetadataFunction::GetMetadata, this,
899                  metadata_type, blob_uuid));
900   reader->SetByteRange(0, net::kMaxBytesToSniff);
901   reader->Start();
902 }
903
904 void MediaGalleriesGetMetadataFunction::GetMetadata(
905     MediaGalleries::GetMetadataType metadata_type, const std::string& blob_uuid,
906     scoped_ptr<std::string> blob_header, int64 total_blob_length) {
907   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
908
909   std::string mime_type;
910   bool mime_type_sniffed = net::SniffMimeTypeFromLocalData(
911       blob_header->c_str(), blob_header->size(), &mime_type);
912
913   if (!mime_type_sniffed) {
914     SendResponse(false);
915     return;
916   }
917
918   if (metadata_type == MediaGalleries::GET_METADATA_TYPE_MIMETYPEONLY) {
919     MediaGalleries::MediaMetadata metadata;
920     metadata.mime_type = mime_type;
921
922     base::DictionaryValue* result_dictionary = new base::DictionaryValue;
923     result_dictionary->Set(kMetadataKey, metadata.ToValue().release());
924     SetResult(result_dictionary);
925     SendResponse(true);
926     return;
927   }
928
929   // We get attached images by default. GET_METADATA_TYPE_NONE is the default
930   // value if the caller doesn't specify the metadata type.
931   bool get_attached_images =
932       metadata_type == MediaGalleries::GET_METADATA_TYPE_ALL ||
933       metadata_type == MediaGalleries::GET_METADATA_TYPE_NONE;
934
935   scoped_refptr<metadata::SafeMediaMetadataParser> parser(
936       new metadata::SafeMediaMetadataParser(GetProfile(), blob_uuid,
937                                             total_blob_length, mime_type,
938                                             get_attached_images));
939   parser->Start(base::Bind(
940       &MediaGalleriesGetMetadataFunction::OnSafeMediaMetadataParserDone, this));
941 }
942
943 void MediaGalleriesGetMetadataFunction::OnSafeMediaMetadataParserDone(
944     bool parse_success, scoped_ptr<base::DictionaryValue> metadata_dictionary,
945     scoped_ptr<std::vector<metadata::AttachedImage> > attached_images) {
946   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
947
948   if (!parse_success) {
949     SendResponse(false);
950     return;
951   }
952
953   DCHECK(metadata_dictionary.get());
954   DCHECK(attached_images.get());
955
956   scoped_ptr<base::DictionaryValue> result_dictionary(
957       new base::DictionaryValue);
958   result_dictionary->Set(kMetadataKey, metadata_dictionary.release());
959
960   if (attached_images->empty()) {
961     SetResult(result_dictionary.release());
962     SendResponse(true);
963     return;
964   }
965
966   result_dictionary->Set(kAttachedImagesBlobInfoKey, new base::ListValue);
967   metadata::AttachedImage* first_image = &attached_images->front();
968   content::BrowserContext::CreateMemoryBackedBlob(
969       GetProfile(),
970       first_image->data.c_str(),
971       first_image->data.size(),
972       base::Bind(&MediaGalleriesGetMetadataFunction::ConstructNextBlob,
973                  this, base::Passed(&result_dictionary),
974                  base::Passed(&attached_images),
975                  base::Passed(make_scoped_ptr(new std::vector<std::string>))));
976 }
977
978 void MediaGalleriesGetMetadataFunction::ConstructNextBlob(
979     scoped_ptr<base::DictionaryValue> result_dictionary,
980     scoped_ptr<std::vector<metadata::AttachedImage> > attached_images,
981     scoped_ptr<std::vector<std::string> > blob_uuids,
982     scoped_ptr<content::BlobHandle> current_blob) {
983   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
984
985   DCHECK(result_dictionary.get());
986   DCHECK(attached_images.get());
987   DCHECK(blob_uuids.get());
988   DCHECK(current_blob.get());
989
990   DCHECK(!attached_images->empty());
991   DCHECK_LT(blob_uuids->size(), attached_images->size());
992
993   // For the newly constructed Blob, store its image's metadata and Blob UUID.
994   base::ListValue* attached_images_list = NULL;
995   result_dictionary->GetList(kAttachedImagesBlobInfoKey, &attached_images_list);
996   DCHECK(attached_images_list);
997   DCHECK_LT(attached_images_list->GetSize(), attached_images->size());
998
999   metadata::AttachedImage* current_image =
1000       &(*attached_images)[blob_uuids->size()];
1001   base::DictionaryValue* attached_image = new base::DictionaryValue;
1002   attached_image->Set(kBlobUUIDKey, new base::StringValue(
1003       current_blob->GetUUID()));
1004   attached_image->Set(kTypeKey, new base::StringValue(
1005       current_image->type));
1006   attached_image->Set(kSizeKey, new base::FundamentalValue(
1007       base::checked_cast<int>(current_image->data.size())));
1008   attached_images_list->Append(attached_image);
1009
1010   blob_uuids->push_back(current_blob->GetUUID());
1011   WebContents* contents = WebContents::FromRenderViewHost(render_view_host());
1012   extensions::BlobHolder* holder =
1013       extensions::BlobHolder::FromRenderProcessHost(
1014           contents->GetRenderProcessHost());
1015   holder->HoldBlobReference(current_blob.Pass());
1016
1017   // Construct the next Blob if necessary.
1018   if (blob_uuids->size() < attached_images->size()) {
1019     metadata::AttachedImage* next_image =
1020         &(*attached_images)[blob_uuids->size()];
1021     content::BrowserContext::CreateMemoryBackedBlob(
1022         GetProfile(),
1023         next_image->data.c_str(),
1024         next_image->data.size(),
1025         base::Bind(&MediaGalleriesGetMetadataFunction::ConstructNextBlob,
1026                    this, base::Passed(&result_dictionary),
1027                    base::Passed(&attached_images), base::Passed(&blob_uuids)));
1028     return;
1029   }
1030
1031   // All Blobs have been constructed. The renderer will take ownership.
1032   SetResult(result_dictionary.release());
1033   SetTransferredBlobUUIDs(*blob_uuids);
1034   SendResponse(true);
1035 }
1036
1037 }  // namespace extensions