// Copyright (c) 2012 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_dialog_controller.h" #include "base/base_paths.h" #include "base/path_service.h" #include "base/stl_util.h" #include "base/strings/utf_string_conversions.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/extensions/api/file_system/file_system_api.h" #include "chrome/browser/extensions/extension_prefs.h" #include "chrome/browser/media_galleries/media_file_system_registry.h" #include "chrome/browser/media_galleries/media_galleries_histograms.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/browser/ui/chrome_select_file_policy.h" #include "chrome/common/extensions/permissions/media_galleries_permission.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_view.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" #include "ui/base/models/simple_menu_model.h" #include "ui/base/text/bytes_formatting.h" using extensions::APIPermission; using extensions::Extension; namespace { // Comparator for sorting GalleryPermissionsVector -- sorts // allowed galleries low, and then sorts by absolute path. bool GalleriesVectorComparator( const MediaGalleriesDialogController::GalleryPermission& a, const MediaGalleriesDialogController::GalleryPermission& b) { if (a.allowed && !b.allowed) return true; if (!a.allowed && b.allowed) return false; return a.pref_info.AbsolutePath() < b.pref_info.AbsolutePath(); } } // namespace class GalleryContextMenuModel : public ui::SimpleMenuModel::Delegate { public: explicit GalleryContextMenuModel(MediaGalleriesDialogController* controller) : controller_(controller), id_(kInvalidMediaGalleryPrefId) {} virtual ~GalleryContextMenuModel() {} void set_media_gallery_pref_id(MediaGalleryPrefId id) { id_ = id; } virtual bool IsCommandIdChecked(int command_id) const OVERRIDE { return false; } virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE { return true; } virtual bool IsCommandIdVisible(int command_id) const OVERRIDE { return true; } virtual bool GetAcceleratorForCommandId( int command_id, ui::Accelerator* accelerator) OVERRIDE { return false; } virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE { controller_->DidForgetGallery(id_); } private: MediaGalleriesDialogController* controller_; MediaGalleryPrefId id_; }; MediaGalleriesDialogController::MediaGalleriesDialogController( content::WebContents* web_contents, const Extension& extension, const base::Closure& on_finish) : web_contents_(web_contents), extension_(&extension), on_finish_(on_finish) { 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(&MediaGalleriesDialogController::OnPreferencesInitialized, base::Unretained(this))); gallery_menu_model_.reset(new GalleryContextMenuModel(this)); ui::SimpleMenuModel* menu_model = new ui::SimpleMenuModel(gallery_menu_model_.get()); menu_model->AddItem( 1, l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_DIALOG_DELETE)); context_menu_model_.reset(menu_model); } void MediaGalleriesDialogController::OnPreferencesInitialized() { InitializePermissions(); dialog_.reset(MediaGalleriesDialog::Create(this)); StorageMonitor::GetInstance()->AddObserver(this); preferences_->AddGalleryChangeObserver(this); } MediaGalleriesDialogController::MediaGalleriesDialogController( const extensions::Extension& extension) : web_contents_(NULL), extension_(&extension), preferences_(NULL) {} MediaGalleriesDialogController::~MediaGalleriesDialogController() { if (StorageMonitor::GetInstance()) StorageMonitor::GetInstance()->RemoveObserver(this); if (select_folder_dialog_.get()) select_folder_dialog_->ListenerDestroyed(); } string16 MediaGalleriesDialogController::GetHeader() const { return l10n_util::GetStringFUTF16(IDS_MEDIA_GALLERIES_DIALOG_HEADER, UTF8ToUTF16(extension_->name())); } string16 MediaGalleriesDialogController::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_, APIPermission::kMediaGalleries, ©_to_param); bool has_delete_permission = extensions::PermissionsData::CheckAPIPermissionWithParam( extension_, APIPermission::kMediaGalleries, &delete_param); int id; if (has_copy_to_permission) id = IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_WRITE; else if (has_delete_permission) id = IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_DELETE; else id = IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_ONLY; return l10n_util::GetStringFUTF16(id, UTF8ToUTF16(extension_->name())); } string16 MediaGalleriesDialogController::GetUnattachedLocationsHeader() const { return l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_UNATTACHED_LOCATIONS); } // TODO(gbillock): Call this something a bit more connected to the // messaging in the dialog. bool MediaGalleriesDialogController::HasPermittedGalleries() const { for (KnownGalleryPermissions::const_iterator iter = known_galleries_.begin(); iter != known_galleries_.end(); ++iter) { if (iter->second.allowed) return true; } // Do this? Views did. if (new_galleries_.size() > 0) return true; return false; } // Note: sorts by display criterion: GalleriesVectorComparator. void MediaGalleriesDialogController::FillPermissions( bool attached, MediaGalleriesDialogController::GalleryPermissionsVector* permissions) const { for (KnownGalleryPermissions::const_iterator iter = known_galleries_.begin(); iter != known_galleries_.end(); ++iter) { if (attached == iter->second.pref_info.IsGalleryAvailable()) permissions->push_back(iter->second); } for (GalleryPermissionsVector::const_iterator iter = new_galleries_.begin(); iter != new_galleries_.end(); ++iter) { if (attached == iter->pref_info.IsGalleryAvailable()) permissions->push_back(*iter); } std::sort(permissions->begin(), permissions->end(), GalleriesVectorComparator); } MediaGalleriesDialogController::GalleryPermissionsVector MediaGalleriesDialogController::AttachedPermissions() const { GalleryPermissionsVector attached; FillPermissions(true, &attached); return attached; } MediaGalleriesDialogController::GalleryPermissionsVector MediaGalleriesDialogController::UnattachedPermissions() const { GalleryPermissionsVector unattached; FillPermissions(false, &unattached); return unattached; } void MediaGalleriesDialogController::OnAddFolderClicked() { base::FilePath default_path = extensions::file_system_api::GetLastChooseEntryDirectory( extensions::ExtensionPrefs::Get(GetProfile()), extension_->id()); if (default_path.empty()) PathService::Get(base::DIR_USER_DESKTOP, &default_path); select_folder_dialog_ = ui::SelectFileDialog::Create(this, new ChromeSelectFilePolicy(NULL)); select_folder_dialog_->SelectFile( ui::SelectFileDialog::SELECT_FOLDER, l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_DIALOG_ADD_GALLERY_TITLE), default_path, NULL, 0, base::FilePath::StringType(), web_contents_->GetView()->GetTopLevelNativeWindow(), NULL); } void MediaGalleriesDialogController::DidToggleGalleryId( MediaGalleryPrefId gallery_id, bool enabled) { // Check known galleries. KnownGalleryPermissions::iterator iter = known_galleries_.find(gallery_id); if (iter != known_galleries_.end()) { if (iter->second.allowed == enabled) return; iter->second.allowed = enabled; if (ContainsKey(toggled_galleries_, gallery_id)) toggled_galleries_.erase(gallery_id); else toggled_galleries_.insert(gallery_id); return; } // Don't sort -- the dialog is open, and we don't want to adjust any // positions for future updates to the dialog contents until they are // redrawn. } void MediaGalleriesDialogController::DidToggleNewGallery( const MediaGalleryPrefInfo& gallery, bool enabled) { for (GalleryPermissionsVector::iterator iter = new_galleries_.begin(); iter != new_galleries_.end(); ++iter) { if (iter->pref_info.path == gallery.path && iter->pref_info.device_id == gallery.device_id) { iter->allowed = enabled; return; } } } void MediaGalleriesDialogController::DidForgetGallery( MediaGalleryPrefId pref_id) { DCHECK(preferences_); preferences_->ForgetGalleryById(pref_id); } void MediaGalleriesDialogController::DialogFinished(bool accepted) { // The dialog has finished, so there is no need to watch for more updates // from |preferences_|. Do this here and not in the dtor since this is the // only non-test code path that deletes |this|. The test ctor never adds // this observer in the first place. preferences_->RemoveGalleryChangeObserver(this); if (accepted) SavePermissions(); on_finish_.Run(); delete this; } content::WebContents* MediaGalleriesDialogController::web_contents() { return web_contents_; } void MediaGalleriesDialogController::FileSelected(const base::FilePath& path, int /*index*/, void* /*params*/) { extensions::file_system_api::SetLastChooseEntryDirectory( extensions::ExtensionPrefs::Get(GetProfile()), extension_->id(), path); // Try to find it in the prefs. MediaGalleryPrefInfo gallery; bool gallery_exists = preferences_->LookUpGalleryByPath(path, &gallery); if (gallery_exists && gallery.type != MediaGalleryPrefInfo::kBlackListed) { // The prefs are in sync with |known_galleries_|, so it should exist in // |known_galleries_| as well. User selecting a known gallery effectively // just sets the gallery to permitted. DCHECK(ContainsKey(known_galleries_, gallery.pref_id)); dialog_->UpdateGalleries(); return; } // Try to find it in |new_galleries_| (user added same folder twice). for (GalleryPermissionsVector::iterator iter = new_galleries_.begin(); iter != new_galleries_.end(); ++iter) { if (iter->pref_info.path == gallery.path && iter->pref_info.device_id == gallery.device_id) { iter->allowed = true; dialog_->UpdateGalleries(); return; } } // Lastly, if not found, add a new gallery to |new_galleries_|. // Note that it will have prefId = kInvalidMediaGalleryPrefId. new_galleries_.push_back(GalleryPermission(gallery, true)); dialog_->UpdateGalleries(); } void MediaGalleriesDialogController::OnRemovableStorageAttached( const StorageInfo& info) { UpdateGalleriesOnDeviceEvent(info.device_id()); } void MediaGalleriesDialogController::OnRemovableStorageDetached( const StorageInfo& info) { UpdateGalleriesOnDeviceEvent(info.device_id()); } void MediaGalleriesDialogController::OnPermissionAdded( MediaGalleriesPreferences* /* prefs */, const std::string& extension_id, MediaGalleryPrefId /* pref_id */) { if (extension_id != extension_->id()) return; UpdateGalleriesOnPreferencesEvent(); } void MediaGalleriesDialogController::OnPermissionRemoved( MediaGalleriesPreferences* /* prefs */, const std::string& extension_id, MediaGalleryPrefId /* pref_id */) { if (extension_id != extension_->id()) return; UpdateGalleriesOnPreferencesEvent(); } void MediaGalleriesDialogController::OnGalleryAdded( MediaGalleriesPreferences* /* prefs */, MediaGalleryPrefId /* pref_id */) { UpdateGalleriesOnPreferencesEvent(); } void MediaGalleriesDialogController::OnGalleryRemoved( MediaGalleriesPreferences* /* prefs */, MediaGalleryPrefId /* pref_id */) { UpdateGalleriesOnPreferencesEvent(); } void MediaGalleriesDialogController::OnGalleryInfoUpdated( MediaGalleriesPreferences* prefs, MediaGalleryPrefId pref_id) { const MediaGalleriesPrefInfoMap& pref_galleries = preferences_->known_galleries(); MediaGalleriesPrefInfoMap::const_iterator pref_it = pref_galleries.find(pref_id); if (pref_it == pref_galleries.end()) return; const MediaGalleryPrefInfo& gallery_info = pref_it->second; UpdateGalleriesOnDeviceEvent(gallery_info.device_id); } void MediaGalleriesDialogController::InitializePermissions() { known_galleries_.clear(); const MediaGalleriesPrefInfoMap& galleries = preferences_->known_galleries(); for (MediaGalleriesPrefInfoMap::const_iterator iter = galleries.begin(); iter != galleries.end(); ++iter) { const MediaGalleryPrefInfo& gallery = iter->second; if (gallery.type == MediaGalleryPrefInfo::kBlackListed) { continue; } known_galleries_[iter->first] = GalleryPermission(gallery, false); } MediaGalleryPrefIdSet permitted = preferences_->GalleriesForExtension(*extension_); for (MediaGalleryPrefIdSet::iterator iter = permitted.begin(); iter != permitted.end(); ++iter) { if (ContainsKey(toggled_galleries_, *iter)) continue; DCHECK(ContainsKey(known_galleries_, *iter)); known_galleries_[*iter].allowed = true; } } void MediaGalleriesDialogController::SavePermissions() { media_galleries::UsageCount(media_galleries::SAVE_DIALOG); for (KnownGalleryPermissions::const_iterator iter = known_galleries_.begin(); iter != known_galleries_.end(); ++iter) { bool changed = preferences_->SetGalleryPermissionForExtension( *extension_, iter->first, iter->second.allowed); if (changed) { if (iter->second.allowed) media_galleries::UsageCount(media_galleries::DIALOG_PERMISSION_ADDED); else media_galleries::UsageCount(media_galleries::DIALOG_PERMISSION_REMOVED); } } for (GalleryPermissionsVector::const_iterator iter = new_galleries_.begin(); iter != new_galleries_.end(); ++iter) { media_galleries::UsageCount(media_galleries::DIALOG_GALLERY_ADDED); // If the user added a gallery then unchecked it, forget about it. if (!iter->allowed) continue; // TODO(gbillock): Should be adding volume metadata during FileSelected. const MediaGalleryPrefInfo& gallery = iter->pref_info; MediaGalleryPrefId id = preferences_->AddGallery( gallery.device_id, gallery.path, true, gallery.volume_label, gallery.vendor_name, gallery.model_name, gallery.total_size_in_bytes, gallery.last_attach_time); preferences_->SetGalleryPermissionForExtension(*extension_, id, true); } } void MediaGalleriesDialogController::UpdateGalleriesOnPreferencesEvent() { // Merge in the permissions from |preferences_|. Afterwards, // |known_galleries_| may contain galleries that no longer belong there, // but the code below will put |known_galleries_| back in a consistent state. InitializePermissions(); // Look for duplicate entries in |new_galleries_| in case one was added // in another dialog. for (KnownGalleryPermissions::iterator it = known_galleries_.begin(); it != known_galleries_.end(); ++it) { GalleryPermission& gallery = it->second; for (GalleryPermissionsVector::iterator new_it = new_galleries_.begin(); new_it != new_galleries_.end(); ++new_it) { if (new_it->pref_info.path == gallery.pref_info.path && new_it->pref_info.device_id == gallery.pref_info.device_id) { // Found duplicate entry. Get the existing permission from it and then // remove it. gallery.allowed = new_it->allowed; new_galleries_.erase(new_it); break; } } } dialog_->UpdateGalleries(); } void MediaGalleriesDialogController::UpdateGalleriesOnDeviceEvent( const std::string& device_id) { dialog_->UpdateGalleries(); } ui::MenuModel* MediaGalleriesDialogController::GetContextMenuModel( MediaGalleryPrefId id) { gallery_menu_model_->set_media_gallery_pref_id(id); return context_menu_model_.get(); } Profile* MediaGalleriesDialogController::GetProfile() { return Profile::FromBrowserContext(web_contents_->GetBrowserContext()); } // MediaGalleries dialog ------------------------------------------------------- MediaGalleriesDialog::~MediaGalleriesDialog() {}