// 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/path_service.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/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/chrome_paths.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/permissions/media_galleries_permission.h" #include "chrome/common/extensions/permissions/permissions_data.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_view.h" #include "grit/generated_resources.h" #include "ui/base/l10n/l10n_util.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 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( Profile::FromBrowserContext(web_contents_->GetBrowserContext())); // 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))); } 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 read_param( extensions::MediaGalleriesPermission::kReadPermission); extensions::MediaGalleriesPermission::CheckParam copy_to_param( extensions::MediaGalleriesPermission::kCopyToPermission); bool has_read_permission = extensions::PermissionsData::CheckAPIPermissionWithParam( extension_, APIPermission::kMediaGalleries, &read_param); bool has_copy_to_permission = extensions::PermissionsData::CheckAPIPermissionWithParam( extension_, APIPermission::kMediaGalleries, ©_to_param); int id; if (has_read_permission && has_copy_to_permission) id = IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_WRITE; else if (has_copy_to_permission) id = IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_WRITE_ONLY; 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()) || (!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()) || (!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 user_data_dir; PathService::Get(chrome::DIR_USER_DATA, &user_data_dir); 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), user_data_dir, 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; } // Check new galleries. for (GalleryPermissionsVector::iterator iter = new_galleries_.begin(); iter != new_galleries_.end(); ++iter) { if (iter->pref_info.pref_id == gallery_id) { iter->allowed = enabled; 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::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*/) { // 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. KnownGalleryPermissions::const_iterator iter = known_galleries_.find(gallery.pref_id); DCHECK(iter != known_galleries_.end()); dialog_->UpdateGallery(iter->second.pref_info, true); 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_->UpdateGallery(iter->pref_info, true); return; } } // Lastly, add a new gallery to |new_galleries_|. new_galleries_.push_back(GalleryPermission(gallery, true)); dialog_->UpdateGallery(new_galleries_.back().pref_info, true); } 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() { 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(); // If a gallery no longer belongs in |known_galleries_|, forget it in the // model/view. // If a gallery still belong in |known_galleries_|, check for a duplicate // entry in |new_galleries_|, merge its permission and remove it. Then update // the view. const MediaGalleriesPrefInfoMap& pref_galleries = preferences_->known_galleries(); MediaGalleryPrefIdSet galleries_to_forget; for (KnownGalleryPermissions::iterator it = known_galleries_.begin(); it != known_galleries_.end(); ++it) { const MediaGalleryPrefId& gallery_id = it->first; GalleryPermission& gallery = it->second; MediaGalleriesPrefInfoMap::const_iterator pref_it = pref_galleries.find(gallery_id); // Check for lingering entry that should be removed. if (pref_it == pref_galleries.end() || pref_it->second.type == MediaGalleryPrefInfo::kBlackListed) { galleries_to_forget.insert(gallery_id); dialog_->ForgetGallery(gallery.pref_info.pref_id); continue; } // Look for duplicate entries in |new_galleries_|. 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; dialog_->ForgetGallery(new_it->pref_info.pref_id); new_galleries_.erase(new_it); break; } } dialog_->UpdateGallery(gallery.pref_info, gallery.allowed); } // Remove the galleries to forget from |known_galleries_|. Doing it in the // above loop would invalidate the iterator there. for (MediaGalleryPrefIdSet::const_iterator it = galleries_to_forget.begin(); it != galleries_to_forget.end(); ++it) { known_galleries_.erase(*it); } } void MediaGalleriesDialogController::UpdateGalleriesOnDeviceEvent( const std::string& device_id) { for (KnownGalleryPermissions::iterator iter = known_galleries_.begin(); iter != known_galleries_.end(); ++iter) { if (iter->second.pref_info.device_id == device_id) dialog_->UpdateGallery(iter->second.pref_info, iter->second.allowed); } for (GalleryPermissionsVector::iterator iter = new_galleries_.begin(); iter != new_galleries_.end(); ++iter) { if (iter->pref_info.device_id == device_id) dialog_->UpdateGallery(iter->pref_info, iter->allowed); } } // MediaGalleries dialog ------------------------------------------------------- MediaGalleriesDialog::~MediaGalleriesDialog() {}