summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorvandebo@chromium.org <vandebo@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-01-27 02:42:00 +0000
committervandebo@chromium.org <vandebo@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-01-27 02:42:00 +0000
commit617d2c4035bfea87ed9adeab976f648c8a4eb8a6 (patch)
tree664bb8fcd69d50982fc21d307274ed832b1127e1
parentb756ed998c006bbec9564a22f47637e5faa75136 (diff)
downloadchromium_src-617d2c4035bfea87ed9adeab976f648c8a4eb8a6.zip
chromium_src-617d2c4035bfea87ed9adeab976f648c8a4eb8a6.tar.gz
chromium_src-617d2c4035bfea87ed9adeab976f648c8a4eb8a6.tar.bz2
Media galleries scan result dialog
Used to show locations where media files where found during a disk scan. BUG=161119 Review URL: https://codereview.chromium.org/137783027 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@247182 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/app/generated_resources.grd21
-rw-r--r--chrome/browser/extensions/api/media_galleries/media_galleries_api.cc38
-rw-r--r--chrome/browser/extensions/api/media_galleries/media_galleries_api.h7
-rw-r--r--chrome/browser/media_galleries/media_galleries_dialog_controller.cc3
-rw-r--r--chrome/browser/media_galleries/media_galleries_scan_result_dialog_controller.cc319
-rw-r--r--chrome/browser/media_galleries/media_galleries_scan_result_dialog_controller.h172
-rw-r--r--chrome/browser/media_galleries/media_galleries_scan_result_dialog_controller_unittest.cc478
-rw-r--r--chrome/browser/ui/views/extensions/media_galleries_scan_result_dialog_views.cc307
-rw-r--r--chrome/browser/ui/views/extensions/media_galleries_scan_result_dialog_views.h88
-rw-r--r--chrome/chrome_browser.gypi2
-rw-r--r--chrome/chrome_browser_ui.gypi2
-rw-r--r--chrome/chrome_tests_unit.gypi3
-rw-r--r--chrome/renderer/resources/extensions/media_galleries_custom_bindings.js15
13 files changed, 1445 insertions, 10 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index b7c3a2d..20209c1 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -15099,15 +15099,30 @@ Do you accept?
<message name="IDS_MEDIA_GALLERIES_DIALOG_HEADER" desc="Header for media gallery permissions dialog.">
Media-File Permissions for "<ph name="EXTENSION">$1<ex>Photo Editor</ex></ph>"
</message>
- <message name="IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_WRITE" desc="Explanatory text for the media gallery access permission. Indicates than the specified (by name) extension has read and write access to the listed folders.">
+ <message name="IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_WRITE" desc="Explanatory text for the media gallery access permission. Indicates that the specified (by name) extension has read and write access to the listed folders.">
"<ph name="EXTENSION">$1<ex>PhotoEditor</ex></ph>" can read and write images, video, and sound files in the checked locations.
</message>
- <message name="IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_DELETE" desc="Explanatory text for the media gallery access permission. Indicates than the specified (by name) extension has read and delete access to the listed folders.">
+ <message name="IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_DELETE" desc="Explanatory text for the media gallery access permission. Indicates that the specified (by name) extension has read and delete access to the listed folders.">
"<ph name="EXTENSION">$1<ex>PhotoEditor</ex></ph>" can read and delete images, video, and sound files in the checked locations.
</message>
- <message name="IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_ONLY" desc="Explanatory text for the media gallery access permission. Indicates than the specified (by name) extension has read-only access to the listed folders.">
+ <message name="IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_ONLY" desc="Explanatory text for the media gallery access permission. Indicates that the specified (by name) extension has read-only access to the listed folders.">
"<ph name="EXTENSION">$1<ex>PhotoEditor</ex></ph>" can read images, video, and sound files in the checked locations.
</message>
+ <message name="IDS_MEDIA_GALLERIES_SCAN_RESULT_DIALOG_HEADER" desc="Header for media gallery scan result dialog.">
+ Add folders to "<ph name="EXTENSION">$1<ex>Photo Editor</ex></ph>"?
+ </message>
+ <message name="IDS_MEDIA_GALLERIES_SCAN_RESULT_DIALOG_SUBTEXT_READ_WRITE" desc="Explanatory text for the media gallery scan result dialog. Indicates that the specified (by name) extension will have read and write access to the selected folders.">
+ "<ph name="EXTENSION">$1<ex>PhotoEditor</ex></ph>" will be able to read and write images, video, and sound files in the checked folders.
+ </message>
+ <message name="IDS_MEDIA_GALLERIES_SCAN_RESULT_DIALOG_SUBTEXT_READ_DELETE" desc="Explanatory text for the media gallery scan result dialog. Indicates that the specified (by name) extension will have read and delete access to the selected folders.">
+ "<ph name="EXTENSION">$1<ex>PhotoEditor</ex></ph>" will be able to read and delete images, video, and sound files in the checked folders.
+ </message>
+ <message name="IDS_MEDIA_GALLERIES_SCAN_RESULT_DIALOG_SUBTEXT_READ_ONLY" desc="Explanatory text for the media gallery scan result dialog. Indicates that the specified (by name) extension will have read-only access to the selected folders.">
+ "<ph name="EXTENSION">$1<ex>PhotoEditor</ex></ph>" will be able to read images, video, and sound files in the checked folders.
+ </message>
+ <message name="IDS_MEDIA_GALLERIES_SCAN_RESULT_OPEN_FOLDER_VIEW_ACCESSIBILITY_NAME" desc="Accessibility name for an icon that opens the folder view of a gallery from the media galleries scan result dialog.">
+ Show in folder
+ </message>
<message name="IDS_MEDIA_GALLERIES_UNATTACHED_LOCATIONS" desc="Header for media gallery permissions to locations not currently attached, i.e. removable devices which aren't plugged in">
Unattached locations
</message>
diff --git a/chrome/browser/extensions/api/media_galleries/media_galleries_api.cc b/chrome/browser/extensions/api/media_galleries/media_galleries_api.cc
index cff29b6..8f1c48d 100644
--- a/chrome/browser/extensions/api/media_galleries/media_galleries_api.cc
+++ b/chrome/browser/extensions/api/media_galleries/media_galleries_api.cc
@@ -25,6 +25,7 @@
#include "chrome/browser/media_galleries/media_galleries_dialog_controller.h"
#include "chrome/browser/media_galleries/media_galleries_histograms.h"
#include "chrome/browser/media_galleries/media_galleries_preferences.h"
+#include "chrome/browser/media_galleries/media_galleries_scan_result_dialog_controller.h"
#include "chrome/browser/media_galleries/media_scan_manager.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/profiles/profile.h"
@@ -647,7 +648,44 @@ bool MediaGalleriesAddScanResultsFunction::RunImpl() {
void MediaGalleriesAddScanResultsFunction::OnPreferencesInit() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ const Extension* extension = GetExtension();
+ WebContents* contents =
+ GetWebContents(render_view_host(), GetProfile(), extension->id());
+ if (!contents) {
+ SendResponse(false);
+ return;
+ }
+ // Controller will delete itself.
+ base::Closure cb = base::Bind(
+ &MediaGalleriesAddScanResultsFunction::GetAndReturnGalleries, this);
+ new MediaGalleriesScanResultDialogController(contents, *extension, cb);
+}
+
+void MediaGalleriesAddScanResultsFunction::GetAndReturnGalleries() {
+ if (!render_view_host()) {
+ ReturnGalleries(std::vector<MediaFileSystemInfo>());
+ return;
+ }
+ MediaFileSystemRegistry* registry = media_file_system_registry();
+ DCHECK(registry->GetPreferences(GetProfile())->IsInitialized());
+ registry->GetMediaFileSystemsForExtension(
+ render_view_host(), GetExtension(),
+ base::Bind(&MediaGalleriesAddScanResultsFunction::ReturnGalleries,
+ this));
+}
+
+void MediaGalleriesAddScanResultsFunction::ReturnGalleries(
+ const std::vector<MediaFileSystemInfo>& filesystems) {
+ scoped_ptr<base::ListValue> list(
+ ConstructFileSystemList(render_view_host(), GetExtension(), filesystems));
+ if (!list.get()) {
+ SendResponse(false);
+ return;
+ }
+
+ // The custom JS binding will use this list to create DOMFileSystem objects.
+ SetResult(list.release());
SendResponse(true);
}
diff --git a/chrome/browser/extensions/api/media_galleries/media_galleries_api.h b/chrome/browser/extensions/api/media_galleries/media_galleries_api.h
index d8dde4e..4e85b68 100644
--- a/chrome/browser/extensions/api/media_galleries/media_galleries_api.h
+++ b/chrome/browser/extensions/api/media_galleries/media_galleries_api.h
@@ -206,6 +206,13 @@ class MediaGalleriesAddScanResultsFunction
private:
// Bottom half for RunImpl, invoked after the preferences is initialized.
void OnPreferencesInit();
+
+ // Grabs galleries from the media file system registry and passes them to
+ // ReturnGalleries().
+ void GetAndReturnGalleries();
+
+ // Returns galleries to the caller.
+ void ReturnGalleries(const std::vector<MediaFileSystemInfo>& filesystems);
};
class MediaGalleriesGetMetadataFunction : public ChromeAsyncExtensionFunction {
diff --git a/chrome/browser/media_galleries/media_galleries_dialog_controller.cc b/chrome/browser/media_galleries/media_galleries_dialog_controller.cc
index 6d425e90..3dfbb27 100644
--- a/chrome/browser/media_galleries/media_galleries_dialog_controller.cc
+++ b/chrome/browser/media_galleries/media_galleries_dialog_controller.cc
@@ -389,9 +389,8 @@ void MediaGalleriesDialogController::InitializePermissions() {
iter != galleries.end();
++iter) {
const MediaGalleryPrefInfo& gallery = iter->second;
- if (gallery.type == MediaGalleryPrefInfo::kBlackListed) {
+ if (gallery.IsBlackListedType())
continue;
- }
known_galleries_[iter->first] = GalleryPermission(gallery, false);
}
diff --git a/chrome/browser/media_galleries/media_galleries_scan_result_dialog_controller.cc b/chrome/browser/media_galleries/media_galleries_scan_result_dialog_controller.cc
new file mode 100644
index 0000000..befcd8a
--- /dev/null
+++ b/chrome/browser/media_galleries/media_galleries_scan_result_dialog_controller.cc
@@ -0,0 +1,319 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media_galleries/media_galleries_scan_result_dialog_controller.h"
+
+#include <algorithm>
+#include <list>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/media_galleries/media_file_system_registry.h"
+#include "chrome/browser/media_galleries/media_galleries_histograms.h"
+#include "chrome/browser/platform_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/storage_monitor/storage_info.h"
+#include "chrome/browser/storage_monitor/storage_monitor.h"
+#include "chrome/common/extensions/permissions/media_galleries_permission.h"
+#include "content/public/browser/web_contents.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace {
+
+// Comparator for sorting OrderedScanResults -- more files first and then sorts
+// by absolute path.
+bool ScanResultsComparator(
+ const MediaGalleriesScanResultDialogController::ScanResult& a,
+ const MediaGalleriesScanResultDialogController::ScanResult& b) {
+ int a_media_count = a.pref_info.image_count + a.pref_info.music_count +
+ a.pref_info.video_count;
+ int b_media_count = b.pref_info.image_count + b.pref_info.music_count +
+ b.pref_info.video_count;
+ if (a_media_count == b_media_count)
+ return a.pref_info.AbsolutePath() < b.pref_info.AbsolutePath();
+ return a_media_count > b_media_count;
+}
+
+} // namespace
+
+MediaGalleriesScanResultDialogController::
+MediaGalleriesScanResultDialogController(
+ content::WebContents* web_contents,
+ const extensions::Extension& extension,
+ const base::Closure& on_finish)
+ : web_contents_(web_contents),
+ extension_(&extension),
+ on_finish_(on_finish) {
+ // TODO(vandebo): Put this in the intializer list after the cocoa version is
+ // done.
+#if defined(USE_AURA)
+ create_dialog_callback_ = base::Bind(&MediaGalleriesScanResultDialog::Create);
+#endif // USE_AURA
+ preferences_ =
+ g_browser_process->media_file_system_registry()->GetPreferences(
+ GetProfile());
+ // Passing unretained pointer is safe, since the dialog controller
+ // is self-deleting, and so won't be deleted until it can be shown
+ // and then closed.
+ preferences_->EnsureInitialized(base::Bind(
+ &MediaGalleriesScanResultDialogController::OnPreferencesInitialized,
+ base::Unretained(this)));
+}
+
+MediaGalleriesScanResultDialogController::
+MediaGalleriesScanResultDialogController(
+ const extensions::Extension& extension,
+ MediaGalleriesPreferences* preferences,
+ const CreateDialogCallback& create_dialog_callback,
+ const base::Closure& on_finish)
+ : web_contents_(NULL),
+ extension_(&extension),
+ on_finish_(on_finish),
+ preferences_(preferences),
+ create_dialog_callback_(create_dialog_callback) {
+ OnPreferencesInitialized();
+}
+
+MediaGalleriesScanResultDialogController::
+~MediaGalleriesScanResultDialogController() {
+ preferences_->RemoveGalleryChangeObserver(this);
+ if (StorageMonitor::GetInstance())
+ StorageMonitor::GetInstance()->RemoveObserver(this);
+}
+
+base::string16 MediaGalleriesScanResultDialogController::GetHeader() const {
+ return l10n_util::GetStringFUTF16(
+ IDS_MEDIA_GALLERIES_SCAN_RESULT_DIALOG_HEADER,
+ base::UTF8ToUTF16(extension_->name()));
+}
+
+base::string16 MediaGalleriesScanResultDialogController::GetSubtext() const {
+ extensions::MediaGalleriesPermission::CheckParam copy_to_param(
+ extensions::MediaGalleriesPermission::kCopyToPermission);
+ extensions::MediaGalleriesPermission::CheckParam delete_param(
+ extensions::MediaGalleriesPermission::kDeletePermission);
+ bool has_copy_to_permission =
+ extensions::PermissionsData::CheckAPIPermissionWithParam(
+ extension_, extensions::APIPermission::kMediaGalleries,
+ &copy_to_param);
+ bool has_delete_permission =
+ extensions::PermissionsData::CheckAPIPermissionWithParam(
+ extension_, extensions::APIPermission::kMediaGalleries,
+ &delete_param);
+
+ int id;
+ if (has_copy_to_permission)
+ id = IDS_MEDIA_GALLERIES_SCAN_RESULT_DIALOG_SUBTEXT_READ_WRITE;
+ else if (has_delete_permission)
+ id = IDS_MEDIA_GALLERIES_SCAN_RESULT_DIALOG_SUBTEXT_READ_DELETE;
+ else
+ id = IDS_MEDIA_GALLERIES_SCAN_RESULT_DIALOG_SUBTEXT_READ_ONLY;
+
+ return l10n_util::GetStringFUTF16(id, base::UTF8ToUTF16(extension_->name()));
+}
+
+MediaGalleriesScanResultDialogController::OrderedScanResults
+MediaGalleriesScanResultDialogController::GetGalleryList() const {
+ OrderedScanResults result;
+ result.reserve(scan_results_.size());
+ for (ScanResults::const_iterator it = scan_results_.begin();
+ it != scan_results_.end();
+ ++it) {
+ result.push_back(it->second);
+ }
+ std::sort(result.begin(), result.end(), ScanResultsComparator);
+ return result;
+}
+
+void MediaGalleriesScanResultDialogController::DidToggleGalleryId(
+ MediaGalleryPrefId pref_id, bool selected) {
+ DCHECK(ContainsKey(scan_results_, pref_id));
+ ScanResults::iterator entry = scan_results_.find(pref_id);
+ entry->second.selected = selected;
+}
+
+void MediaGalleriesScanResultDialogController::DidClickOpenFolderViewer(
+ MediaGalleryPrefId pref_id) const {
+ ScanResults::const_iterator entry = scan_results_.find(pref_id);
+ if (entry == scan_results_.end()) {
+ NOTREACHED();
+ return;
+ }
+ platform_util::OpenItem(GetProfile(), entry->second.pref_info.AbsolutePath());
+}
+
+void MediaGalleriesScanResultDialogController::DidForgetGallery(
+ MediaGalleryPrefId pref_id) {
+ results_to_remove_.insert(pref_id);
+ scan_results_.erase(pref_id);
+ dialog_->UpdateResults();
+}
+
+void MediaGalleriesScanResultDialogController::DialogFinished(bool accepted) {
+ // No longer interested in preference updates (and the below code generates
+ // some).
+ preferences_->RemoveGalleryChangeObserver(this);
+
+ if (accepted) {
+ DCHECK(preferences_);
+ for (ScanResults::const_iterator it = scan_results_.begin();
+ it != scan_results_.end();
+ ++it) {
+ if (it->second.selected) {
+ bool changed = preferences_->SetGalleryPermissionForExtension(
+ *extension_, it->first, true);
+ DCHECK(changed);
+ }
+ }
+ for (MediaGalleryPrefIdSet::const_iterator it = results_to_remove_.begin();
+ it != results_to_remove_.end();
+ ++it) {
+ preferences_->ForgetGalleryById(*it);
+ }
+ }
+
+ on_finish_.Run();
+ delete this;
+}
+
+content::WebContents* MediaGalleriesScanResultDialogController::web_contents() {
+ return web_contents_;
+}
+
+void MediaGalleriesScanResultDialogController::OnPreferencesInitialized() {
+ preferences_->AddGalleryChangeObserver(this);
+ StorageMonitor::GetInstance()->AddObserver(this);
+ UpdateFromPreferences();
+
+ // TODO(vandebo): Remove the conditional after the cocoa version is done.
+ if (!create_dialog_callback_.is_null())
+ dialog_.reset(create_dialog_callback_.Run(this));
+}
+
+void MediaGalleriesScanResultDialogController::UpdateFromPreferences() {
+ const MediaGalleriesPrefInfoMap& galleries = preferences_->known_galleries();
+ MediaGalleryPrefIdSet permitted =
+ preferences_->GalleriesForExtension(*extension_);
+
+ // Add or update any scan results that the extension doesn't already have
+ // access to or isn't in |results_to_remove_|.
+ for (MediaGalleriesPrefInfoMap::const_iterator it = galleries.begin();
+ it != galleries.end();
+ ++it) {
+ const MediaGalleryPrefInfo& gallery = it->second;
+ if (gallery.type == MediaGalleryPrefInfo::kScanResult &&
+ !ContainsKey(permitted, gallery.pref_id) &&
+ !ContainsKey(results_to_remove_, gallery.pref_id)) {
+ ScanResults::iterator existing = scan_results_.find(gallery.pref_id);
+ if (existing == scan_results_.end()) {
+ // Default to selected.
+ scan_results_[gallery.pref_id] = ScanResult(gallery, true);
+ } else {
+ // Update pref_info, in case anything has been updated.
+ existing->second.pref_info = gallery;
+ }
+ }
+ }
+
+ // Remove anything from |scan_results_| that's no longer valid or the user
+ // already has access to.
+ std::list<ScanResults::iterator> to_remove;
+ for (ScanResults::iterator it = scan_results_.begin();
+ it != scan_results_.end();
+ ++it) {
+ MediaGalleriesPrefInfoMap::const_iterator pref_gallery =
+ galleries.find(it->first);
+ if (pref_gallery == galleries.end() ||
+ pref_gallery->second.type != MediaGalleryPrefInfo::kScanResult ||
+ permitted.find(it->first) != permitted.end()) {
+ to_remove.push_back(it);
+ }
+ }
+ while (!to_remove.empty()) {
+ scan_results_.erase(to_remove.front());
+ to_remove.pop_front();
+ }
+}
+
+void MediaGalleriesScanResultDialogController::OnPreferenceUpdate(
+ const std::string& extension_id, MediaGalleryPrefId pref_id) {
+ if (extension_id == extension_->id()) {
+ const MediaGalleriesPrefInfoMap::const_iterator it =
+ preferences_->known_galleries().find(pref_id);
+ if (it == preferences_->known_galleries().end() ||
+ it->second.type == MediaGalleryPrefInfo::kScanResult ||
+ it->second.type == MediaGalleryPrefInfo::kRemovedScan) {
+ UpdateFromPreferences();
+ dialog_->UpdateResults();
+ }
+ }
+}
+
+void MediaGalleriesScanResultDialogController::OnRemovableDeviceUpdate(
+ const std::string device_id) {
+ for (ScanResults::const_iterator it = scan_results_.begin();
+ it != scan_results_.end();
+ ++it) {
+ if (it->second.pref_info.device_id == device_id) {
+ dialog_->UpdateResults();
+ return;
+ }
+ }
+}
+
+Profile* MediaGalleriesScanResultDialogController::GetProfile() const {
+ return Profile::FromBrowserContext(web_contents_->GetBrowserContext());
+}
+
+void MediaGalleriesScanResultDialogController::OnRemovableStorageAttached(
+ const StorageInfo& info) {
+ OnRemovableDeviceUpdate(info.device_id());
+}
+
+void MediaGalleriesScanResultDialogController::OnRemovableStorageDetached(
+ const StorageInfo& info) {
+ OnRemovableDeviceUpdate(info.device_id());
+}
+
+void MediaGalleriesScanResultDialogController::OnPermissionAdded(
+ MediaGalleriesPreferences* /* pref */,
+ const std::string& extension_id,
+ MediaGalleryPrefId pref_id) {
+ OnPreferenceUpdate(extension_id, pref_id);
+}
+
+void MediaGalleriesScanResultDialogController::OnPermissionRemoved(
+ MediaGalleriesPreferences* /* pref */,
+ const std::string& extension_id,
+ MediaGalleryPrefId pref_id) {
+ OnPreferenceUpdate(extension_id, pref_id);
+}
+
+void MediaGalleriesScanResultDialogController::OnGalleryAdded(
+ MediaGalleriesPreferences* /* prefs */,
+ MediaGalleryPrefId pref_id) {
+ OnPreferenceUpdate(extension_->id(), pref_id);
+}
+
+void MediaGalleriesScanResultDialogController::OnGalleryRemoved(
+ MediaGalleriesPreferences* /* prefs */,
+ MediaGalleryPrefId pref_id) {
+ OnPreferenceUpdate(extension_->id(), pref_id);
+}
+
+void MediaGalleriesScanResultDialogController::OnGalleryInfoUpdated(
+ MediaGalleriesPreferences* /* prefs */,
+ MediaGalleryPrefId pref_id) {
+ OnPreferenceUpdate(extension_->id(), pref_id);
+}
+
+// MediaGalleriesScanResultDialog ---------------------------------------------
+
+MediaGalleriesScanResultDialog::~MediaGalleriesScanResultDialog() {}
diff --git a/chrome/browser/media_galleries/media_galleries_scan_result_dialog_controller.h b/chrome/browser/media_galleries/media_galleries_scan_result_dialog_controller.h
new file mode 100644
index 0000000..28754a7
--- /dev/null
+++ b/chrome/browser/media_galleries/media_galleries_scan_result_dialog_controller.h
@@ -0,0 +1,172 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_GALLERIES_MEDIA_GALLERIES_SCAN_RESULT_DIALOG_CONTROLLER_H_
+#define CHROME_BROWSER_MEDIA_GALLERIES_MEDIA_GALLERIES_SCAN_RESULT_DIALOG_CONTROLLER_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "chrome/browser/media_galleries/media_galleries_preferences.h"
+#include "chrome/browser/storage_monitor/removable_storage_observer.h"
+
+namespace content {
+class WebContents;
+}
+
+namespace extensions {
+class Extension;
+}
+
+class MediaGalleriesScanResultDialogController;
+class Profile;
+
+// The view.
+class MediaGalleriesScanResultDialog {
+ public:
+ virtual ~MediaGalleriesScanResultDialog();
+
+ // Tell the dialog to update its display list of scan results.
+ virtual void UpdateResults() = 0;
+
+ // Constructs a platform-specific dialog owned and controlled by |controller|.
+ static MediaGalleriesScanResultDialog* Create(
+ MediaGalleriesScanResultDialogController* controller);
+};
+
+// The controller is responsible for handling the logic of the dialog and
+// interfacing with the model (i.e., MediaGalleriesPreferences). It shows
+// the dialog and owns itself.
+class MediaGalleriesScanResultDialogController
+ : public RemovableStorageObserver,
+ public MediaGalleriesPreferences::GalleryChangeObserver {
+ public:
+ struct ScanResult {
+ ScanResult(const MediaGalleryPrefInfo& pref_info, bool selected)
+ : pref_info(pref_info),
+ selected(selected) {
+ }
+ ScanResult() {}
+
+ MediaGalleryPrefInfo pref_info;
+ bool selected;
+ };
+ typedef std::vector<ScanResult> OrderedScanResults;
+
+ // The constructor creates a dialog controller which owns itself.
+ MediaGalleriesScanResultDialogController(
+ content::WebContents* web_contents,
+ const extensions::Extension& extension,
+ const base::Closure& on_finish);
+
+ // The title of the dialog view.
+ base::string16 GetHeader() const;
+
+ // Explanatory text directly below the title.
+ base::string16 GetSubtext() const;
+
+ // Get the scan results and their current selection state.
+ virtual OrderedScanResults GetGalleryList() const;
+
+ // A checkbox beside a scan result was toggled.
+ virtual void DidToggleGalleryId(MediaGalleryPrefId pref_id, bool selected);
+
+ // A folder viewer icon was clicked.
+ virtual void DidClickOpenFolderViewer(MediaGalleryPrefId pref_id) const;
+
+ // The forget command in the context menu was selected.
+ virtual void DidForgetGallery(MediaGalleryPrefId pref_id);
+
+ // The dialog is being deleted.
+ virtual void DialogFinished(bool accepted);
+
+ virtual content::WebContents* web_contents();
+
+ private:
+ friend class MediaGalleriesScanResultDialogControllerTest;
+
+ typedef std::map<MediaGalleryPrefId, ScanResult> ScanResults;
+ typedef base::Callback<MediaGalleriesScanResultDialog* (
+ MediaGalleriesScanResultDialogController*)> CreateDialogCallback;
+
+ // Used for unit tests.
+ MediaGalleriesScanResultDialogController(
+ const extensions::Extension& extension,
+ MediaGalleriesPreferences* preferences_,
+ const CreateDialogCallback& create_dialog_callback,
+ const base::Closure& on_finish);
+
+ virtual ~MediaGalleriesScanResultDialogController();
+
+ // Bottom half of constructor -- called when |preferences_| is initialized.
+ void OnPreferencesInitialized();
+
+ // Update the controller state from preferences.
+ void UpdateFromPreferences();
+
+ // Used to keep the dialog in sync with the preferences.
+ void OnPreferenceUpdate(const std::string& extension_id,
+ MediaGalleryPrefId pref_id);
+
+ // Used to keep the dialog in sync with attached and detached devices.
+ void OnRemovableDeviceUpdate(const std::string device_id);
+
+ Profile* GetProfile() const;
+
+ // RemovableStorageObserver implementation.
+ // Used to keep dialog in sync with removable device status.
+ virtual void OnRemovableStorageAttached(const StorageInfo& info) OVERRIDE;
+ virtual void OnRemovableStorageDetached(const StorageInfo& info) OVERRIDE;
+
+ // MediaGalleriesPreferences::GalleryChangeObserver implementations.
+ // Used to keep the dialog in sync when the preferences change.
+ virtual void OnPermissionAdded(MediaGalleriesPreferences* pref,
+ const std::string& extension_id,
+ MediaGalleryPrefId pref_id) OVERRIDE;
+ virtual void OnPermissionRemoved(MediaGalleriesPreferences* pref,
+ const std::string& extension_id,
+ MediaGalleryPrefId pref_id) OVERRIDE;
+ virtual void OnGalleryAdded(MediaGalleriesPreferences* pref,
+ MediaGalleryPrefId pref_id) OVERRIDE;
+ virtual void OnGalleryRemoved(MediaGalleriesPreferences* pref,
+ MediaGalleryPrefId pref_id) OVERRIDE;
+ virtual void OnGalleryInfoUpdated(MediaGalleriesPreferences* pref,
+ MediaGalleryPrefId pref_id) OVERRIDE;
+
+ // The web contents from which the request originated.
+ content::WebContents* web_contents_;
+
+ // This is just a reference, but it's assumed that it won't become invalid
+ // while the dialog is showing.
+ const extensions::Extension* extension_;
+
+ // The scan results that aren't blacklisted and this extension doesn't
+ // already have access to.
+ ScanResults scan_results_;
+
+ // The set of scan results which should be removed (blacklisted) - unless
+ // the user clicks Cancel.
+ MediaGalleryPrefIdSet results_to_remove_;
+
+ // Callback to run when the dialog closes.
+ base::Closure on_finish_;
+
+ // The model that tracks galleries and extensions' permissions.
+ // This is the authoritative source for gallery information.
+ MediaGalleriesPreferences* preferences_;
+
+ // Creates the dialog. Only changed for unit tests.
+ CreateDialogCallback create_dialog_callback_;
+
+ // The view that's showing.
+ scoped_ptr<MediaGalleriesScanResultDialog> dialog_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaGalleriesScanResultDialogController);
+};
+
+#endif // CHROME_BROWSER_MEDIA_GALLERIES_MEDIA_GALLERIES_SCAN_RESULT_DIALOG_CONTROLLER_H_
diff --git a/chrome/browser/media_galleries/media_galleries_scan_result_dialog_controller_unittest.cc b/chrome/browser/media_galleries/media_galleries_scan_result_dialog_controller_unittest.cc
new file mode 100644
index 0000000..4f48d9c
--- /dev/null
+++ b/chrome/browser/media_galleries/media_galleries_scan_result_dialog_controller_unittest.cc
@@ -0,0 +1,478 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/run_loop.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "chrome/browser/extensions/extension_system.h"
+#include "chrome/browser/extensions/test_extension_system.h"
+#include "chrome/browser/media_galleries/media_galleries_preferences.h"
+#include "chrome/browser/media_galleries/media_galleries_scan_result_dialog_controller.h"
+#include "chrome/browser/media_galleries/media_galleries_test_util.h"
+#include "chrome/browser/storage_monitor/test_storage_monitor.h"
+#include "chrome/common/extensions/permissions/media_galleries_permission.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "extensions/common/extension.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/login/user_manager.h"
+#include "chrome/browser/chromeos/settings/cros_settings.h"
+#include "chrome/browser/chromeos/settings/device_settings_service.h"
+#endif
+
+namespace {
+
+base::FilePath MakePath(std::string dir) {
+#if defined(OS_WIN)
+ return base::FilePath(FILE_PATH_LITERAL("C:\\")).AppendASCII(dir);
+#elif defined(OS_POSIX)
+ return base::FilePath(FILE_PATH_LITERAL("/")).Append(dir);
+#else
+ NOTIMPLEMENTED();
+#endif
+}
+
+MediaGalleryPrefId AddScanResult(MediaGalleriesPreferences* gallery_prefs,
+ const std::string& path, int image_count,
+ int music_count, int video_count) {
+ MediaGalleryPrefInfo gallery_info;
+ gallery_prefs->LookUpGalleryByPath(MakePath(path), &gallery_info);
+ return gallery_prefs->AddGallery(
+ gallery_info.device_id,
+ gallery_info.path,
+ MediaGalleryPrefInfo::kScanResult,
+ gallery_info.volume_label,
+ gallery_info.vendor_name,
+ gallery_info.model_name,
+ gallery_info.total_size_in_bytes,
+ gallery_info.last_attach_time,
+ image_count, music_count, video_count);
+}
+
+class MockMediaGalleriesScanResultDialog
+ : public MediaGalleriesScanResultDialog {
+ public:
+ typedef base::Callback<void(int update_count)> DialogDestroyedCallback;
+
+ explicit MockMediaGalleriesScanResultDialog(
+ const DialogDestroyedCallback& callback)
+ : update_count_(0),
+ dialog_destroyed_callback_(callback) {
+ }
+
+ virtual ~MockMediaGalleriesScanResultDialog() {
+ dialog_destroyed_callback_.Run(update_count_);
+ }
+
+ // MockMediaGalleriesScanResultDialog implementation.
+ virtual void UpdateResults() OVERRIDE {
+ update_count_++;
+ }
+
+ // Number up times UpdateResults has been called.
+ int update_count() {
+ return update_count_;
+ }
+
+ private:
+ int update_count_;
+
+ DialogDestroyedCallback dialog_destroyed_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockMediaGalleriesScanResultDialog);
+};
+
+} // namespace
+
+class MediaGalleriesScanResultDialogControllerTest : public testing::Test {
+ public:
+ MediaGalleriesScanResultDialogControllerTest()
+ : dialog_(NULL),
+ dialog_update_count_at_destruction_(0),
+ controller_(NULL),
+ profile_(new TestingProfile()),
+ weak_factory_(this) {
+ }
+
+ virtual ~MediaGalleriesScanResultDialogControllerTest() {
+ EXPECT_FALSE(controller_);
+ EXPECT_FALSE(dialog_);
+ }
+
+ virtual void SetUp() OVERRIDE {
+ ASSERT_TRUE(TestStorageMonitor::CreateAndInstall());
+
+ extensions::TestExtensionSystem* extension_system(
+ static_cast<extensions::TestExtensionSystem*>(
+ extensions::ExtensionSystem::Get(profile_.get())));
+ extension_system->CreateExtensionService(
+ CommandLine::ForCurrentProcess(), base::FilePath(), false);
+
+ gallery_prefs_.reset(new MediaGalleriesPreferences(profile_.get()));
+ base::RunLoop loop;
+ gallery_prefs_->EnsureInitialized(loop.QuitClosure());
+ loop.Run();
+
+ std::vector<std::string> read_permissions;
+ read_permissions.push_back(
+ extensions::MediaGalleriesPermission::kReadPermission);
+ extension_ = AddMediaGalleriesApp("read", read_permissions, profile_.get());
+ }
+
+ virtual void TearDown() OVERRIDE {
+ TestStorageMonitor::RemoveSingleton();
+ }
+
+ void StartDialog() {
+ ASSERT_FALSE(controller_);
+ controller_ = new MediaGalleriesScanResultDialogController(
+ *extension_.get(),
+ gallery_prefs_.get(),
+ base::Bind(
+ &MediaGalleriesScanResultDialogControllerTest::CreateMockDialog,
+ base::Unretained(this)),
+ base::Bind(
+ &MediaGalleriesScanResultDialogControllerTest::OnControllerDone,
+ base::Unretained(this)));
+ }
+
+ MediaGalleriesScanResultDialogController* controller() {
+ return controller_;
+ }
+
+ MockMediaGalleriesScanResultDialog* dialog() {
+ return dialog_;
+ }
+
+ int dialog_update_count_at_destruction() {
+ EXPECT_FALSE(dialog_);
+ return dialog_update_count_at_destruction_;
+ }
+
+ extensions::Extension* extension() {
+ return extension_.get();
+ }
+
+ MediaGalleriesPreferences* gallery_prefs() {
+ return gallery_prefs_.get();
+ }
+
+ private:
+ MediaGalleriesScanResultDialog* CreateMockDialog(
+ MediaGalleriesScanResultDialogController* controller) {
+ EXPECT_FALSE(dialog_);
+ dialog_update_count_at_destruction_ = 0;
+ dialog_ = new MockMediaGalleriesScanResultDialog(base::Bind(
+ &MediaGalleriesScanResultDialogControllerTest::OnDialogDestroyed,
+ weak_factory_.GetWeakPtr()));
+ return dialog_;
+ }
+
+ void OnDialogDestroyed(int update_count) {
+ EXPECT_TRUE(dialog_);
+ dialog_update_count_at_destruction_ = update_count;
+ dialog_ = NULL;
+ }
+
+ void OnControllerDone() {
+ controller_ = NULL;
+ }
+
+ // Needed for extension service & friends to work.
+ content::TestBrowserThreadBundle thread_bundle_;
+
+ // The dialog is owned by the controller, but this pointer should only be
+ // valid while the dialog is live within the controller.
+ MockMediaGalleriesScanResultDialog* dialog_;
+ int dialog_update_count_at_destruction_;
+
+ // The controller owns itself.
+ MediaGalleriesScanResultDialogController* controller_;
+
+ scoped_refptr<extensions::Extension> extension_;
+
+ EnsureMediaDirectoriesExists mock_gallery_locations_;
+
+#if defined OS_CHROMEOS
+ chromeos::ScopedTestDeviceSettingsService test_device_settings_service_;
+ chromeos::ScopedTestCrosSettings test_cros_settings_;
+ chromeos::ScopedTestUserManager test_user_manager_;
+#endif
+
+ TestStorageMonitor monitor_;
+ scoped_ptr<TestingProfile> profile_;
+ scoped_ptr<MediaGalleriesPreferences> gallery_prefs_;
+
+ base::WeakPtrFactory<MediaGalleriesScanResultDialogControllerTest>
+ weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaGalleriesScanResultDialogControllerTest);
+};
+
+TEST_F(MediaGalleriesScanResultDialogControllerTest, EmptyDialog) {
+ StartDialog();
+ EXPECT_TRUE(controller());
+ EXPECT_TRUE(dialog());
+ EXPECT_EQ(0U, controller()->GetGalleryList().size());
+
+ controller()->DialogFinished(true);
+ EXPECT_FALSE(controller());
+ EXPECT_FALSE(dialog());
+ EXPECT_EQ(0, dialog_update_count_at_destruction());
+}
+
+TEST_F(MediaGalleriesScanResultDialogControllerTest, AddScanResults) {
+ // Start with two scan results.
+ MediaGalleryPrefId scan1 =
+ gallery_prefs()->AddGalleryByPath(MakePath("scan1"),
+ MediaGalleryPrefInfo::kScanResult);
+ MediaGalleryPrefId scan2 =
+ gallery_prefs()->AddGalleryByPath(MakePath("scan2"),
+ MediaGalleryPrefInfo::kScanResult);
+ EXPECT_EQ(0U, gallery_prefs()->GalleriesForExtension(*extension()).size());
+
+ // Show the dialog, but cancel it.
+ StartDialog();
+ EXPECT_EQ(2U, controller()->GetGalleryList().size());
+ controller()->DialogFinished(false);
+ EXPECT_EQ(0U, gallery_prefs()->GalleriesForExtension(*extension()).size());
+
+ // Show the dialog, unselect both and accept it.
+ StartDialog();
+ EXPECT_EQ(2U, controller()->GetGalleryList().size());
+ controller()->DidToggleGalleryId(scan1, false);
+ controller()->DidToggleGalleryId(scan2, false);
+ controller()->DialogFinished(true);
+ EXPECT_EQ(0U, gallery_prefs()->GalleriesForExtension(*extension()).size());
+
+ // Show the dialog, leave one selected and accept it.
+ StartDialog();
+ EXPECT_EQ(2U, controller()->GetGalleryList().size());
+ controller()->DidToggleGalleryId(scan1, false);
+ controller()->DialogFinished(true);
+ MediaGalleryPrefIdSet permitted =
+ gallery_prefs()->GalleriesForExtension(*extension());
+ ASSERT_EQ(1U, permitted.size());
+ EXPECT_EQ(scan2, *permitted.begin());
+
+ // Show the dialog, toggle the remaining entry twice and then accept it.
+ StartDialog();
+ EXPECT_EQ(1U, controller()->GetGalleryList().size());
+ controller()->DidToggleGalleryId(scan1, false);
+ controller()->DidToggleGalleryId(scan1, true);
+ controller()->DialogFinished(true);
+ EXPECT_EQ(2U, gallery_prefs()->GalleriesForExtension(*extension()).size());
+}
+
+TEST_F(MediaGalleriesScanResultDialogControllerTest, Blacklisted) {
+ // Start with two scan results.
+ MediaGalleryPrefId scan1 =
+ gallery_prefs()->AddGalleryByPath(MakePath("scan1"),
+ MediaGalleryPrefInfo::kScanResult);
+ MediaGalleryPrefId scan2 =
+ gallery_prefs()->AddGalleryByPath(MakePath("scan2"),
+ MediaGalleryPrefInfo::kScanResult);
+ EXPECT_EQ(0U, gallery_prefs()->GalleriesForExtension(*extension()).size());
+
+ // Show the dialog, but cancel it.
+ StartDialog();
+ EXPECT_EQ(2U, controller()->GetGalleryList().size());
+ controller()->DialogFinished(false);
+ EXPECT_EQ(0U, gallery_prefs()->GalleriesForExtension(*extension()).size());
+
+ // Blacklist one and try again.
+ gallery_prefs()->ForgetGalleryById(scan2);
+ StartDialog();
+ EXPECT_EQ(1U, controller()->GetGalleryList().size());
+ controller()->DialogFinished(false);
+
+ // Adding it as a user gallery should change its type.
+ gallery_prefs()->AddGalleryByPath(MakePath("scan2"),
+ MediaGalleryPrefInfo::kUserAdded);
+ StartDialog();
+ EXPECT_EQ(1U, controller()->GetGalleryList().size());
+
+ // Blacklisting the other while the dialog is open should remove it.
+ gallery_prefs()->ForgetGalleryById(scan1);
+ EXPECT_EQ(0U, controller()->GetGalleryList().size());
+ controller()->DialogFinished(false);
+ EXPECT_EQ(0U, gallery_prefs()->GalleriesForExtension(*extension()).size());
+ EXPECT_EQ(1, dialog_update_count_at_destruction());
+}
+
+TEST_F(MediaGalleriesScanResultDialogControllerTest, PrefUpdates) {
+ MediaGalleryPrefId selected =
+ gallery_prefs()->AddGalleryByPath(MakePath("selected"),
+ MediaGalleryPrefInfo::kScanResult);
+ MediaGalleryPrefId unselected =
+ gallery_prefs()->AddGalleryByPath(MakePath("unselected"),
+ MediaGalleryPrefInfo::kScanResult);
+ MediaGalleryPrefId selected_add_permission =
+ gallery_prefs()->AddGalleryByPath(MakePath("selected_add_permission"),
+ MediaGalleryPrefInfo::kScanResult);
+ MediaGalleryPrefId unselected_add_permission =
+ gallery_prefs()->AddGalleryByPath(MakePath("unselected_add_permission"),
+ MediaGalleryPrefInfo::kScanResult);
+ MediaGalleryPrefId selected_removed =
+ gallery_prefs()->AddGalleryByPath(MakePath("selected_removed"),
+ MediaGalleryPrefInfo::kScanResult);
+ MediaGalleryPrefId unselected_removed =
+ gallery_prefs()->AddGalleryByPath(MakePath("unselected_removed"),
+ MediaGalleryPrefInfo::kScanResult);
+ MediaGalleryPrefId selected_update =
+ gallery_prefs()->AddGalleryByPath(MakePath("selected_update"),
+ MediaGalleryPrefInfo::kScanResult);
+ MediaGalleryPrefId unselected_update =
+ gallery_prefs()->AddGalleryByPath(MakePath("unselected_update"),
+ MediaGalleryPrefInfo::kScanResult);
+
+ gallery_prefs()->AddGalleryByPath(MakePath("user"),
+ MediaGalleryPrefInfo::kUserAdded);
+ gallery_prefs()->AddGalleryByPath(MakePath("auto_detected"),
+ MediaGalleryPrefInfo::kAutoDetected);
+ MediaGalleryPrefId blacklisted =
+ gallery_prefs()->AddGalleryByPath(MakePath("blacklisted"),
+ MediaGalleryPrefInfo::kAutoDetected);
+ gallery_prefs()->ForgetGalleryById(blacklisted);
+ EXPECT_EQ(0U, gallery_prefs()->GalleriesForExtension(*extension()).size());
+
+ StartDialog();
+ EXPECT_EQ(8U, controller()->GetGalleryList().size());
+ controller()->DidToggleGalleryId(unselected, false);
+ controller()->DidToggleGalleryId(unselected_add_permission, false);
+ controller()->DidToggleGalleryId(unselected_removed, false);
+ controller()->DidToggleGalleryId(unselected_update, false);
+ EXPECT_EQ(0, dialog()->update_count());
+ EXPECT_EQ(8U, controller()->GetGalleryList().size());
+
+ // Add permission.
+ gallery_prefs()->SetGalleryPermissionForExtension(*extension(),
+ unselected_add_permission,
+ true);
+ EXPECT_EQ(1, dialog()->update_count());
+ EXPECT_EQ(7U, controller()->GetGalleryList().size());
+ gallery_prefs()->SetGalleryPermissionForExtension(*extension(),
+ selected_add_permission,
+ true);
+ EXPECT_EQ(2, dialog()->update_count());
+ EXPECT_EQ(6U, controller()->GetGalleryList().size());
+
+ // Blacklist scan results.
+ gallery_prefs()->ForgetGalleryById(unselected_removed);
+ EXPECT_EQ(3, dialog()->update_count());
+ EXPECT_EQ(5U, controller()->GetGalleryList().size());
+ gallery_prefs()->ForgetGalleryById(selected_removed);
+ EXPECT_EQ(4, dialog()->update_count());
+ EXPECT_EQ(4U, controller()->GetGalleryList().size());
+
+ // Update names.
+ const MediaGalleryPrefInfo& unselected_update_info =
+ gallery_prefs()->known_galleries().find(unselected_update)->second;
+ gallery_prefs()->AddGallery(
+ unselected_update_info.device_id, base::FilePath(),
+ MediaGalleryPrefInfo::kScanResult,
+ base::ASCIIToUTF16("Updated & Unselected"),
+ base::string16(), base::string16(), 0, base::Time(), 0, 0, 0);
+ EXPECT_EQ(5, dialog()->update_count());
+ EXPECT_EQ(4U, controller()->GetGalleryList().size());
+ const MediaGalleryPrefInfo& selected_update_info =
+ gallery_prefs()->known_galleries().find(selected_update)->second;
+ gallery_prefs()->AddGallery(
+ selected_update_info.device_id, base::FilePath(),
+ MediaGalleryPrefInfo::kScanResult,
+ base::ASCIIToUTF16("Updated & Selected"),
+ base::string16(), base::string16(), 0, base::Time(), 0, 0, 0);
+ EXPECT_EQ(6, dialog()->update_count());
+ ASSERT_EQ(4U, controller()->GetGalleryList().size());
+
+ MediaGalleriesScanResultDialogController::OrderedScanResults results =
+ controller()->GetGalleryList();
+ EXPECT_EQ(selected, results[0].pref_info.pref_id);
+ EXPECT_TRUE(results[0].selected);
+ EXPECT_EQ(selected_update, results[1].pref_info.pref_id);
+ EXPECT_TRUE(results[1].selected);
+ EXPECT_EQ(base::ASCIIToUTF16("Updated & Selected"),
+ results[1].pref_info.volume_label);
+ EXPECT_EQ(unselected, results[2].pref_info.pref_id);
+ EXPECT_FALSE(results[2].selected);
+ EXPECT_EQ(unselected_update, results[3].pref_info.pref_id);
+ EXPECT_FALSE(results[3].selected);
+ EXPECT_EQ(base::ASCIIToUTF16("Updated & Unselected"),
+ results[3].pref_info.volume_label);
+
+ controller()->DialogFinished(true);
+ EXPECT_EQ(4U, gallery_prefs()->GalleriesForExtension(*extension()).size());
+ StartDialog();
+ EXPECT_EQ(2U, controller()->GetGalleryList().size());
+ controller()->DialogFinished(false);
+}
+
+TEST_F(MediaGalleriesScanResultDialogControllerTest, ForgetGallery) {
+ // Start with two scan results.
+ MediaGalleryPrefId scan1 =
+ gallery_prefs()->AddGalleryByPath(MakePath("scan1"),
+ MediaGalleryPrefInfo::kScanResult);
+ MediaGalleryPrefId scan2 =
+ gallery_prefs()->AddGalleryByPath(MakePath("scan2"),
+ MediaGalleryPrefInfo::kScanResult);
+ EXPECT_EQ(0U, gallery_prefs()->GalleriesForExtension(*extension()).size());
+
+ // Remove one and then cancel.
+ StartDialog();
+ EXPECT_EQ(2U, controller()->GetGalleryList().size());
+ controller()->DidForgetGallery(scan1);
+ controller()->DialogFinished(false);
+ EXPECT_EQ(0U, gallery_prefs()->GalleriesForExtension(*extension()).size());
+
+ // Remove one and then have it blacklisted from prefs.
+ StartDialog();
+ EXPECT_EQ(2U, controller()->GetGalleryList().size());
+ controller()->DidForgetGallery(scan1);
+ EXPECT_EQ(1, dialog()->update_count());
+ controller()->DidToggleGalleryId(scan2, false); // Uncheck the second.
+ gallery_prefs()->ForgetGalleryById(scan1);
+ controller()->DialogFinished(true);
+ EXPECT_EQ(0U, gallery_prefs()->GalleriesForExtension(*extension()).size());
+ EXPECT_EQ(2, dialog_update_count_at_destruction());
+
+ // Remove the other.
+ StartDialog();
+ EXPECT_EQ(1U, controller()->GetGalleryList().size());
+ controller()->DidForgetGallery(scan2);
+ controller()->DialogFinished(true);
+ EXPECT_EQ(0U, gallery_prefs()->GalleriesForExtension(*extension()).size());
+
+ // Check that nothing shows up.
+ StartDialog();
+ EXPECT_EQ(0U, controller()->GetGalleryList().size());
+ controller()->DialogFinished(false);
+}
+
+TEST_F(MediaGalleriesScanResultDialogControllerTest, SortOrder) {
+ // Intentionally our of order numerically and alphabetically.
+ MediaGalleryPrefId third = AddScanResult(gallery_prefs(), "third", 2, 2, 2);
+ MediaGalleryPrefId second = AddScanResult(gallery_prefs(), "second", 9, 0, 0);
+ MediaGalleryPrefId first = AddScanResult(gallery_prefs(), "first", 8, 2, 3);
+ MediaGalleryPrefId fifth = AddScanResult(gallery_prefs(), "abb", 3, 0, 0);
+ MediaGalleryPrefId fourth = AddScanResult(gallery_prefs(), "aaa", 3, 0, 0);
+
+ StartDialog();
+ MediaGalleriesScanResultDialogController::OrderedScanResults results =
+ controller()->GetGalleryList();
+ ASSERT_EQ(5U, results.size());
+ EXPECT_EQ(first, results[0].pref_info.pref_id);
+ EXPECT_EQ(second, results[1].pref_info.pref_id);
+ EXPECT_EQ(third, results[2].pref_info.pref_id);
+ EXPECT_EQ(fourth, results[3].pref_info.pref_id);
+ EXPECT_EQ(fifth, results[4].pref_info.pref_id);
+ controller()->DialogFinished(false);
+}
diff --git a/chrome/browser/ui/views/extensions/media_galleries_scan_result_dialog_views.cc b/chrome/browser/ui/views/extensions/media_galleries_scan_result_dialog_views.cc
new file mode 100644
index 0000000..6ec7293
--- /dev/null
+++ b/chrome/browser/ui/views/extensions/media_galleries_scan_result_dialog_views.cc
@@ -0,0 +1,307 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/extensions/media_galleries_scan_result_dialog_views.h"
+
+#include "chrome/browser/ui/views/constrained_window_views.h"
+#include "components/web_modal/web_contents_modal_dialog_host.h"
+#include "components/web_modal/web_contents_modal_dialog_manager.h"
+#include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
+#include "content/public/browser/web_contents.h"
+#include "grit/generated_resources.h"
+#include "grit/locale_settings.h"
+#include "grit/ui_resources.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/views/border.h"
+#include "ui/views/controls/button/checkbox.h"
+#include "ui/views/controls/button/image_button.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/controls/scroll_view.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/grid_layout.h"
+#include "ui/views/layout/layout_constants.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/window/dialog_client_view.h"
+
+typedef MediaGalleriesScanResultDialogController::OrderedScanResults
+ OrderedScanResults;
+
+namespace {
+
+// TODO(vandebo) move this to a common place.
+// Equal to the #969696 color used in spec (note WebUI color is #999).
+const SkColor kDeemphasizedTextColor = SkColorSetRGB(159, 159, 159);
+
+const int kScrollAreaHeight = 192;
+
+// This container has the right Layout() impl to use within a ScrollView.
+class ScrollableView : public views::View {
+ public:
+ ScrollableView() {}
+ virtual ~ScrollableView() {}
+
+ virtual void Layout() OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScrollableView);
+};
+
+void ScrollableView::Layout() {
+ gfx::Size pref = GetPreferredSize();
+ int width = pref.width();
+ int height = pref.height();
+ if (parent()) {
+ width = std::max(parent()->width(), width);
+ height = std::max(parent()->height(), height);
+ }
+ SetBounds(x(), y(), width, height);
+
+ views::View::Layout();
+}
+
+} // namespace
+
+MediaGalleriesScanResultDialogViews::MediaGalleriesScanResultDialogViews(
+ MediaGalleriesScanResultDialogController* controller)
+ : controller_(controller),
+ window_(NULL),
+ contents_(new views::View()),
+ accepted_(false) {
+ InitChildViews();
+
+ // Ownership of |contents_| is handed off by this call. |window_| will take
+ // care of deleting itself after calling DeleteDelegate().
+ web_modal::WebContentsModalDialogManager* web_contents_modal_dialog_manager =
+ web_modal::WebContentsModalDialogManager::FromWebContents(
+ controller->web_contents());
+ DCHECK(web_contents_modal_dialog_manager);
+ web_modal::WebContentsModalDialogManagerDelegate* modal_delegate =
+ web_contents_modal_dialog_manager->delegate();
+ DCHECK(modal_delegate);
+ window_ = views::Widget::CreateWindowAsFramelessChild(
+ this, modal_delegate->GetWebContentsModalDialogHost()->GetHostView());
+ web_contents_modal_dialog_manager->ShowDialog(window_->GetNativeView());
+}
+
+MediaGalleriesScanResultDialogViews::~MediaGalleriesScanResultDialogViews() {}
+
+void MediaGalleriesScanResultDialogViews::InitChildViews() {
+ // Outer dialog layout.
+ contents_->RemoveAllChildViews(true);
+ int dialog_content_width = views::Widget::GetLocalizedContentsWidth(
+ IDS_MEDIA_GALLERIES_DIALOG_CONTENT_WIDTH_CHARS);
+ views::GridLayout* layout = views::GridLayout::CreatePanel(contents_);
+ contents_->SetLayoutManager(layout);
+
+ int column_set_id = 0;
+ views::ColumnSet* columns = layout->AddColumnSet(column_set_id);
+ columns->AddColumn(views::GridLayout::LEADING,
+ views::GridLayout::LEADING,
+ 1,
+ views::GridLayout::FIXED,
+ dialog_content_width,
+ 0);
+
+ // Message text.
+ views::Label* subtext = new views::Label(controller_->GetSubtext());
+ subtext->SetMultiLine(true);
+ subtext->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ layout->StartRow(0, column_set_id);
+ layout->AddView(
+ subtext, 1, 1,
+ views::GridLayout::FILL, views::GridLayout::LEADING,
+ dialog_content_width, subtext->GetHeightForWidth(dialog_content_width));
+ layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
+
+ // Scrollable area for checkboxes.
+ ScrollableView* scroll_container = new ScrollableView();
+ scroll_container->SetLayoutManager(new views::BoxLayout(
+ views::BoxLayout::kVertical, 0, 0,
+ views::kRelatedControlSmallVerticalSpacing));
+ scroll_container->SetBorder(views::Border::CreateEmptyBorder(
+ views::kRelatedControlVerticalSpacing,
+ 0,
+ views::kRelatedControlVerticalSpacing,
+ 0));
+
+ // Add attached galleries checkboxes.
+ gallery_view_map_.clear();
+ OrderedScanResults scan_results = controller_->GetGalleryList();
+ for (OrderedScanResults::const_iterator it = scan_results.begin();
+ it != scan_results.end();
+ ++it) {
+ int spacing = 0;
+ if (it + 1 == scan_results.end())
+ spacing = views::kRelatedControlSmallVerticalSpacing;
+ AddOrUpdateScanResult(it->pref_info, it->selected, scroll_container,
+ spacing);
+ }
+
+ // Add the scrollable area to the outer dialog view. It will squeeze against
+ // the title/subtitle and buttons to occupy all available space in the dialog.
+ views::ScrollView* scroll_view =
+ views::ScrollView::CreateScrollViewWithBorder();
+ scroll_view->SetContents(scroll_container);
+ layout->StartRowWithPadding(1, column_set_id,
+ 0, views::kRelatedControlVerticalSpacing);
+ layout->AddView(scroll_view, 1, 1,
+ views::GridLayout::FILL, views::GridLayout::FILL,
+ dialog_content_width, kScrollAreaHeight);
+}
+
+void MediaGalleriesScanResultDialogViews::UpdateResults() {
+ InitChildViews();
+ contents_->Layout();
+}
+
+bool MediaGalleriesScanResultDialogViews::AddOrUpdateScanResult(
+ const MediaGalleryPrefInfo& gallery,
+ bool selected,
+ views::View* container,
+ int trailing_vertical_space) {
+ base::string16 label = gallery.GetGalleryDisplayName();
+ base::string16 tooltip_text = gallery.GetGalleryTooltip();
+ base::string16 details = gallery.GetGalleryAdditionalDetails();
+ bool is_attached = gallery.IsGalleryAvailable();
+
+ GalleryViewMap::iterator it = gallery_view_map_.find(gallery.pref_id);
+ if (it != gallery_view_map_.end()) {
+ views::Checkbox* checkbox = it->second.checkbox;
+ checkbox->SetChecked(selected);
+ checkbox->SetText(label);
+ checkbox->SetElideBehavior(views::Label::ELIDE_IN_MIDDLE);
+ checkbox->SetTooltipText(tooltip_text);
+ // Replace the details string.
+ it->second.folder_viewer_button->SetVisible(is_attached);
+ it->second.secondary_text->SetText(details);
+ return false;
+ }
+
+ views::Checkbox* checkbox = new views::Checkbox(label);
+ checkbox->set_listener(this);
+ checkbox->SetTooltipText(tooltip_text);
+
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ views::ImageButton* folder_viewer_button = new views::ImageButton(this);
+ folder_viewer_button->SetImage(views::ImageButton::STATE_NORMAL,
+ rb.GetImageSkiaNamed(IDR_OAK));
+ folder_viewer_button->SetAccessibleName(l10n_util::GetStringUTF16(
+ IDS_MEDIA_GALLERIES_SCAN_RESULT_OPEN_FOLDER_VIEW_ACCESSIBILITY_NAME));
+ folder_viewer_button->SetFocusable(true);
+ folder_viewer_button->SetVisible(is_attached);
+
+ views::Label* secondary_text = new views::Label(details);
+ secondary_text->SetTooltipText(tooltip_text);
+ secondary_text->SetEnabledColor(kDeemphasizedTextColor);
+ secondary_text->SetTooltipText(tooltip_text);
+ secondary_text->SetBorder(views::Border::CreateEmptyBorder(
+ 0,
+ views::kRelatedControlSmallHorizontalSpacing,
+ 0,
+ views::kRelatedControlSmallHorizontalSpacing));
+
+ views::View* checkbox_view = new views::View();
+ checkbox_view->SetBorder(views::Border::CreateEmptyBorder(
+ 0, views::kPanelHorizMargin, trailing_vertical_space, 0));
+ checkbox_view->SetLayoutManager(
+ new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
+ checkbox_view->AddChildView(checkbox);
+ checkbox_view->AddChildView(folder_viewer_button);
+ checkbox_view->AddChildView(secondary_text);
+
+ container->AddChildView(checkbox_view);
+
+ checkbox->SetChecked(selected);
+ gallery_view_map_[gallery.pref_id].checkbox = checkbox;
+ gallery_view_map_[gallery.pref_id].folder_viewer_button =
+ folder_viewer_button;
+ gallery_view_map_[gallery.pref_id].secondary_text = secondary_text;
+
+ return true;
+}
+
+base::string16 MediaGalleriesScanResultDialogViews::GetWindowTitle() const {
+ return controller_->GetHeader();
+}
+
+void MediaGalleriesScanResultDialogViews::DeleteDelegate() {
+ controller_->DialogFinished(accepted_);
+}
+
+views::Widget* MediaGalleriesScanResultDialogViews::GetWidget() {
+ return contents_->GetWidget();
+}
+
+const views::Widget* MediaGalleriesScanResultDialogViews::GetWidget() const {
+ return contents_->GetWidget();
+}
+
+views::View* MediaGalleriesScanResultDialogViews::GetContentsView() {
+ return contents_;
+}
+
+base::string16 MediaGalleriesScanResultDialogViews::GetDialogButtonLabel(
+ ui::DialogButton button) const {
+ return l10n_util::GetStringUTF16(button == ui::DIALOG_BUTTON_OK ?
+ IDS_MEDIA_GALLERIES_DIALOG_CONFIRM :
+ IDS_MEDIA_GALLERIES_DIALOG_CANCEL);
+}
+
+ui::ModalType MediaGalleriesScanResultDialogViews::GetModalType() const {
+#if defined(USE_ASH)
+ return ui::MODAL_TYPE_CHILD;
+#else
+ return views::WidgetDelegate::GetModalType();
+#endif
+}
+
+bool MediaGalleriesScanResultDialogViews::Cancel() {
+ return true;
+}
+
+bool MediaGalleriesScanResultDialogViews::Accept() {
+ accepted_ = true;
+
+ return true;
+}
+
+// TODO(wittman): Remove this override once we move to the new style frame view
+// on all dialogs.
+views::NonClientFrameView*
+MediaGalleriesScanResultDialogViews::CreateNonClientFrameView(
+ views::Widget* widget) {
+ return CreateConstrainedStyleNonClientFrameView(
+ widget,
+ controller_->web_contents()->GetBrowserContext());
+}
+
+void MediaGalleriesScanResultDialogViews::ButtonPressed(
+ views::Button* sender,
+ const ui::Event& event) {
+ GetWidget()->client_view()->AsDialogClientView()->UpdateDialogButtons();
+
+ for (GalleryViewMap::const_iterator it = gallery_view_map_.begin();
+ it != gallery_view_map_.end(); ++it) {
+ if (sender == it->second.checkbox) {
+ controller_->DidToggleGalleryId(it->first,
+ it->second.checkbox->checked());
+ return;
+ }
+ if (sender == it->second.folder_viewer_button) {
+ controller_->DidClickOpenFolderViewer(it->first);
+ return;
+ }
+ }
+}
+
+// MediaGalleriesScanResultDialogViewsController -------------------------------
+
+// static
+MediaGalleriesScanResultDialog* MediaGalleriesScanResultDialog::Create(
+ MediaGalleriesScanResultDialogController* controller) {
+ return new MediaGalleriesScanResultDialogViews(controller);
+}
diff --git a/chrome/browser/ui/views/extensions/media_galleries_scan_result_dialog_views.h b/chrome/browser/ui/views/extensions/media_galleries_scan_result_dialog_views.h
new file mode 100644
index 0000000..7baf467
--- /dev/null
+++ b/chrome/browser/ui/views/extensions/media_galleries_scan_result_dialog_views.h
@@ -0,0 +1,88 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_EXTENSIONS_MEDIA_GALLERIES_SCAN_RESULT_DIALOG_VIEWS_H_
+#define CHROME_BROWSER_UI_VIEWS_EXTENSIONS_MEDIA_GALLERIES_SCAN_RESULT_DIALOG_VIEWS_H_
+
+#include <map>
+
+#include "base/compiler_specific.h"
+#include "chrome/browser/media_galleries/media_galleries_scan_result_dialog_controller.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/window/dialog_delegate.h"
+
+namespace views {
+class Checkbox;
+class ImageButton;
+class Label;
+class Widget;
+}
+
+// The media galleries scan result view for Views. It will immediately show
+// upon construction.
+class MediaGalleriesScanResultDialogViews
+ : public MediaGalleriesScanResultDialog,
+ public views::ButtonListener,
+ public views::DialogDelegate {
+ public:
+ explicit MediaGalleriesScanResultDialogViews(
+ MediaGalleriesScanResultDialogController* controller);
+ virtual ~MediaGalleriesScanResultDialogViews();
+
+ // MediaGalleriesScanResultDialog implementation:
+ virtual void UpdateResults() OVERRIDE;
+
+ // views::DialogDelegate implementation:
+ virtual base::string16 GetWindowTitle() const OVERRIDE;
+ virtual void DeleteDelegate() OVERRIDE;
+ virtual views::Widget* GetWidget() OVERRIDE;
+ virtual const views::Widget* GetWidget() const OVERRIDE;
+ virtual views::View* GetContentsView() OVERRIDE;
+ virtual base::string16 GetDialogButtonLabel(
+ ui::DialogButton button) const OVERRIDE;
+ virtual ui::ModalType GetModalType() const OVERRIDE;
+ virtual bool Cancel() OVERRIDE;
+ virtual bool Accept() OVERRIDE;
+ virtual views::NonClientFrameView* CreateNonClientFrameView(
+ views::Widget* widget) OVERRIDE;
+
+ // views::ButtonListener implementation:
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) OVERRIDE;
+
+ private:
+ struct GalleryEntry {
+ views::Checkbox* checkbox;
+ views::ImageButton* folder_viewer_button;
+ views::Label* secondary_text;
+ };
+ typedef std::map<MediaGalleryPrefId, GalleryEntry> GalleryViewMap;
+
+ void InitChildViews();
+
+ // Adds a checkbox or updates an existing checkbox. Returns true if a new one
+ // was added.
+ bool AddOrUpdateScanResult(const MediaGalleryPrefInfo& gallery,
+ bool selected,
+ views::View* container,
+ int trailing_vertical_space);
+
+ MediaGalleriesScanResultDialogController* controller_;
+
+ // The containing window (a weak pointer).
+ views::Widget* window_;
+
+ // The contents of the dialog. Owned by |window_|'s RootView.
+ views::View* contents_;
+
+ // A map from media gallery ID to the view elements for each gallery.
+ GalleryViewMap gallery_view_map_;
+
+ // True if the user has pressed accept.
+ bool accepted_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaGalleriesScanResultDialogViews);
+};
+
+#endif // CHROME_BROWSER_UI_VIEWS_EXTENSIONS_MEDIA_GALLERIES_SCAN_RESULT_DIALOG_VIEWS_H_
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 0fffe8d..f5ea815 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -1165,6 +1165,8 @@
'browser/media_galleries/media_galleries_preferences.h',
'browser/media_galleries/media_galleries_preferences_factory.cc',
'browser/media_galleries/media_galleries_preferences_factory.h',
+ 'browser/media_galleries/media_galleries_scan_result_dialog_controller.cc',
+ 'browser/media_galleries/media_galleries_scan_result_dialog_controller.h',
'browser/media_galleries/media_scan_manager.cc',
'browser/media_galleries/media_scan_manager.h',
'browser/media_galleries/media_scan_manager_observer.h',
diff --git a/chrome/chrome_browser_ui.gypi b/chrome/chrome_browser_ui.gypi
index a4dbb98..1d2ab06 100644
--- a/chrome/chrome_browser_ui.gypi
+++ b/chrome/chrome_browser_ui.gypi
@@ -1835,6 +1835,8 @@
'browser/ui/views/extensions/extension_view_views.h',
'browser/ui/views/extensions/media_galleries_dialog_views.cc',
'browser/ui/views/extensions/media_galleries_dialog_views.h',
+ 'browser/ui/views/extensions/media_galleries_scan_result_dialog_views.cc',
+ 'browser/ui/views/extensions/media_galleries_scan_result_dialog_views.h',
'browser/ui/views/external_protocol_dialog.cc',
'browser/ui/views/external_protocol_dialog.h',
'browser/ui/views/find_bar_host.cc',
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index 30b3fe1..0d422bd 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -1032,9 +1032,10 @@
'browser/media_galleries/media_file_system_registry_unittest.cc',
'browser/media_galleries/media_galleries_dialog_controller_mock.cc',
'browser/media_galleries/media_galleries_dialog_controller_mock.h',
- 'browser/media_galleries/media_galleries_permissions_unittest.cc',
'browser/media_galleries/media_galleries_dialog_controller_unittest.cc',
+ 'browser/media_galleries/media_galleries_permissions_unittest.cc',
'browser/media_galleries/media_galleries_preferences_unittest.cc',
+ 'browser/media_galleries/media_galleries_scan_result_dialog_controller_unittest.cc',
'browser/media_galleries/win/mtp_device_object_enumerator_unittest.cc',
'browser/metrics/compression_utils_unittest.cc',
'browser/metrics/metrics_log_unittest.cc',
diff --git a/chrome/renderer/resources/extensions/media_galleries_custom_bindings.js b/chrome/renderer/resources/extensions/media_galleries_custom_bindings.js
index 68a17b0..2d9116d 100644
--- a/chrome/renderer/resources/extensions/media_galleries_custom_bindings.js
+++ b/chrome/renderer/resources/extensions/media_galleries_custom_bindings.js
@@ -31,8 +31,9 @@ function createFileSystemObjectsAndUpdateMetadata(response) {
binding.registerCustomHook(function(bindingsAPI, extensionId) {
var apiFunctions = bindingsAPI.apiFunctions;
- // getMediaFileSystems uses a custom callback so that it can instantiate and
- // return an array of file system objects.
+ // getMediaFileSystems, addUserSelectedFolder, and addScanResults use a
+ // custom callback so that they can instantiate and return an array of file
+ // system objects.
apiFunctions.setCustomCallback('getMediaFileSystems',
function(name, request, response) {
var result = createFileSystemObjectsAndUpdateMetadata(response);
@@ -41,8 +42,14 @@ binding.registerCustomHook(function(bindingsAPI, extensionId) {
request.callback = null;
});
- // addUserSelectedFolder uses a custom callback so that it can instantiate
- // and return an array of file system objects.
+ apiFunctions.setCustomCallback('addScanResults',
+ function(name, request, response) {
+ var result = createFileSystemObjectsAndUpdateMetadata(response);
+ if (request.callback)
+ request.callback(result);
+ request.callback = null;
+ });
+
apiFunctions.setCustomCallback('addUserSelectedFolder',
function(name, request, response) {
var fileSystems = [];