| // 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_permission_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/media_galleries/media_file_system_registry.h" |
| #include "chrome/browser/media_galleries/media_galleries_histograms.h" |
| #include "chrome/browser/media_galleries/media_gallery_context_menu.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/chrome_select_file_policy.h" |
| #include "components/storage_monitor/storage_info.h" |
| #include "components/storage_monitor/storage_monitor.h" |
| #include "content/public/browser/web_contents.h" |
| #include "extensions/browser/extension_prefs.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/permissions/media_galleries_permission.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; |
| using storage_monitor::StorageInfo; |
| using storage_monitor::StorageMonitor; |
| |
| namespace { |
| |
| // Comparator for sorting gallery entries. Sort Removable entries above |
| // non-removable ones. Within those two groups, sort on media counts |
| // if populated, otherwise on paths. |
| bool GalleriesVectorComparator( |
| const MediaGalleriesDialogController::Entry& a, |
| const MediaGalleriesDialogController::Entry& b) { |
| if (StorageInfo::IsRemovableDevice(a.pref_info.device_id) != |
| StorageInfo::IsRemovableDevice(b.pref_info.device_id)) { |
| return StorageInfo::IsRemovableDevice(a.pref_info.device_id); |
| } |
| int a_media_count = a.pref_info.audio_count + a.pref_info.image_count + |
| a.pref_info.video_count; |
| int b_media_count = b.pref_info.audio_count + b.pref_info.image_count + |
| b.pref_info.video_count; |
| if (a_media_count != b_media_count) |
| return a_media_count > b_media_count; |
| return a.pref_info.AbsolutePath() < b.pref_info.AbsolutePath(); |
| } |
| |
| } // namespace |
| |
| MediaGalleriesPermissionController::MediaGalleriesPermissionController( |
| 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())), |
| create_dialog_callback_(base::Bind(&MediaGalleriesDialog::Create)) { |
| // 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(&MediaGalleriesPermissionController::OnPreferencesInitialized, |
| base::Unretained(this))); |
| |
| // Unretained is safe because |this| owns |context_menu_|. |
| context_menu_.reset( |
| new MediaGalleryContextMenu( |
| base::Bind(&MediaGalleriesPermissionController::DidForgetEntry, |
| base::Unretained(this)))); |
| } |
| |
| void MediaGalleriesPermissionController::OnPreferencesInitialized() { |
| if (StorageMonitor::GetInstance()) |
| StorageMonitor::GetInstance()->AddObserver(this); |
| |
| // |preferences_| may be NULL in tests. |
| if (preferences_) { |
| preferences_->AddGalleryChangeObserver(this); |
| InitializePermissions(); |
| } |
| |
| dialog_.reset(create_dialog_callback_.Run(this)); |
| } |
| |
| MediaGalleriesPermissionController::MediaGalleriesPermissionController( |
| 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(); |
| } |
| |
| MediaGalleriesPermissionController::~MediaGalleriesPermissionController() { |
| if (StorageMonitor::GetInstance()) |
| StorageMonitor::GetInstance()->RemoveObserver(this); |
| |
| // |preferences_| may be NULL in tests. |
| if (preferences_) |
| preferences_->RemoveGalleryChangeObserver(this); |
| |
| if (select_folder_dialog_.get()) |
| select_folder_dialog_->ListenerDestroyed(); |
| } |
| |
| base::string16 MediaGalleriesPermissionController::GetHeader() const { |
| return l10n_util::GetStringFUTF16(IDS_MEDIA_GALLERIES_DIALOG_HEADER, |
| base::UTF8ToUTF16(extension_->name())); |
| } |
| |
| base::string16 MediaGalleriesPermissionController::GetSubtext() const { |
| extensions::MediaGalleriesPermission::CheckParam copy_to_param( |
| extensions::MediaGalleriesPermission::kCopyToPermission); |
| extensions::MediaGalleriesPermission::CheckParam delete_param( |
| extensions::MediaGalleriesPermission::kDeletePermission); |
| const extensions::PermissionsData* permission_data = |
| extension_->permissions_data(); |
| bool has_copy_to_permission = permission_data->CheckAPIPermissionWithParam( |
| APIPermission::kMediaGalleries, ©_to_param); |
| bool has_delete_permission = permission_data->CheckAPIPermissionWithParam( |
| 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, base::UTF8ToUTF16(extension_->name())); |
| } |
| |
| bool MediaGalleriesPermissionController::IsAcceptAllowed() const { |
| if (!toggled_galleries_.empty() || !forgotten_galleries_.empty()) |
| return true; |
| |
| for (GalleryPermissionsMap::const_iterator iter = new_galleries_.begin(); |
| iter != new_galleries_.end(); |
| ++iter) { |
| if (iter->second.selected) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool MediaGalleriesPermissionController::ShouldShowFolderViewer( |
| const Entry& entry) const { |
| return false; |
| } |
| |
| std::vector<base::string16> |
| MediaGalleriesPermissionController::GetSectionHeaders() const { |
| std::vector<base::string16> result; |
| result.push_back(base::string16()); // First section has no header. |
| result.push_back( |
| l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_PERMISSION_SUGGESTIONS)); |
| return result; |
| } |
| |
| // Note: sorts by display criterion: GalleriesVectorComparator. |
| MediaGalleriesDialogController::Entries |
| MediaGalleriesPermissionController::GetSectionEntries(size_t index) const { |
| DCHECK_GT(2U, index); // This dialog only has two sections. |
| |
| bool existing = !index; |
| MediaGalleriesDialogController::Entries result; |
| for (GalleryPermissionsMap::const_iterator iter = known_galleries_.begin(); |
| iter != known_galleries_.end(); ++iter) { |
| MediaGalleryPrefId pref_id = GetPrefId(iter->first); |
| if (!ContainsKey(forgotten_galleries_, iter->first) && |
| existing == ContainsKey(pref_permitted_galleries_, pref_id)) { |
| result.push_back(iter->second); |
| } |
| } |
| if (existing) { |
| for (GalleryPermissionsMap::const_iterator iter = new_galleries_.begin(); |
| iter != new_galleries_.end(); ++iter) { |
| result.push_back(iter->second); |
| } |
| } |
| |
| std::sort(result.begin(), result.end(), GalleriesVectorComparator); |
| return result; |
| } |
| |
| base::string16 |
| MediaGalleriesPermissionController::GetAuxiliaryButtonText() const { |
| return l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_DIALOG_ADD_GALLERY); |
| } |
| |
| // This is the 'Add Folder' button. |
| void MediaGalleriesPermissionController::DidClickAuxiliaryButton() { |
| 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_->GetTopLevelNativeWindow(), |
| NULL); |
| } |
| |
| void MediaGalleriesPermissionController::DidToggleEntry( |
| GalleryDialogId gallery_id, bool selected) { |
| // Check known galleries. |
| GalleryPermissionsMap::iterator iter = known_galleries_.find(gallery_id); |
| if (iter != known_galleries_.end()) { |
| if (iter->second.selected == selected) |
| return; |
| |
| iter->second.selected = selected; |
| toggled_galleries_[gallery_id] = selected; |
| return; |
| } |
| |
| iter = new_galleries_.find(gallery_id); |
| if (iter != new_galleries_.end()) |
| iter->second.selected = selected; |
| |
| // 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 MediaGalleriesPermissionController::DidClickOpenFolderViewer( |
| GalleryDialogId gallery_id) { |
| NOTREACHED(); |
| } |
| |
| void MediaGalleriesPermissionController::DidForgetEntry( |
| GalleryDialogId gallery_id) { |
| media_galleries::UsageCount(media_galleries::DIALOG_FORGET_GALLERY); |
| if (!new_galleries_.erase(gallery_id)) { |
| DCHECK(ContainsKey(known_galleries_, gallery_id)); |
| forgotten_galleries_.insert(gallery_id); |
| } |
| dialog_->UpdateGalleries(); |
| } |
| |
| base::string16 MediaGalleriesPermissionController::GetAcceptButtonText() const { |
| return l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_DIALOG_CONFIRM); |
| } |
| |
| void MediaGalleriesPermissionController::DialogFinished(bool accepted) { |
| // The dialog has finished, so there is no need to watch for more updates |
| // from |preferences_|. |
| // |preferences_| may be NULL in tests. |
| if (preferences_) |
| preferences_->RemoveGalleryChangeObserver(this); |
| |
| if (accepted) |
| SavePermissions(); |
| |
| on_finish_.Run(); |
| |
| delete this; |
| } |
| |
| content::WebContents* MediaGalleriesPermissionController::WebContents() { |
| return web_contents_; |
| } |
| |
| void MediaGalleriesPermissionController::FileSelected( |
| const base::FilePath& path, |
| int /*index*/, |
| void* /*params*/) { |
| // |web_contents_| is NULL in tests. |
| if (web_contents_) { |
| extensions::file_system_api::SetLastChooseEntryDirectory( |
| extensions::ExtensionPrefs::Get(GetProfile()), |
| extension_->id(), |
| path); |
| } |
| |
| // Try to find it in the prefs. |
| MediaGalleryPrefInfo gallery; |
| DCHECK(preferences_); |
| bool gallery_exists = preferences_->LookUpGalleryByPath(path, &gallery); |
| if (gallery_exists && !gallery.IsBlackListedType()) { |
| // 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. |
| GalleryDialogId gallery_id = GetDialogId(gallery.pref_id); |
| GalleryPermissionsMap::iterator iter = known_galleries_.find(gallery_id); |
| DCHECK(iter != known_galleries_.end()); |
| iter->second.selected = true; |
| forgotten_galleries_.erase(gallery_id); |
| dialog_->UpdateGalleries(); |
| return; |
| } |
| |
| // Try to find it in |new_galleries_| (user added same folder twice). |
| for (GalleryPermissionsMap::iterator iter = new_galleries_.begin(); |
| iter != new_galleries_.end(); ++iter) { |
| if (iter->second.pref_info.path == gallery.path && |
| iter->second.pref_info.device_id == gallery.device_id) { |
| iter->second.selected = true; |
| dialog_->UpdateGalleries(); |
| return; |
| } |
| } |
| |
| // Lastly, if not found, add a new gallery to |new_galleries_|. |
| // prefId == kInvalidMediaGalleryPrefId for completely new galleries. |
| // The old prefId is retained for blacklisted galleries. |
| gallery.pref_id = GetDialogId(gallery.pref_id); |
| new_galleries_[gallery.pref_id] = Entry(gallery, true); |
| dialog_->UpdateGalleries(); |
| } |
| |
| void MediaGalleriesPermissionController::OnRemovableStorageAttached( |
| const StorageInfo& info) { |
| UpdateGalleriesOnDeviceEvent(info.device_id()); |
| } |
| |
| void MediaGalleriesPermissionController::OnRemovableStorageDetached( |
| const StorageInfo& info) { |
| UpdateGalleriesOnDeviceEvent(info.device_id()); |
| } |
| |
| void MediaGalleriesPermissionController::OnPermissionAdded( |
| MediaGalleriesPreferences* /* prefs */, |
| const std::string& extension_id, |
| MediaGalleryPrefId /* pref_id */) { |
| if (extension_id != extension_->id()) |
| return; |
| UpdateGalleriesOnPreferencesEvent(); |
| } |
| |
| void MediaGalleriesPermissionController::OnPermissionRemoved( |
| MediaGalleriesPreferences* /* prefs */, |
| const std::string& extension_id, |
| MediaGalleryPrefId /* pref_id */) { |
| if (extension_id != extension_->id()) |
| return; |
| UpdateGalleriesOnPreferencesEvent(); |
| } |
| |
| void MediaGalleriesPermissionController::OnGalleryAdded( |
| MediaGalleriesPreferences* /* prefs */, |
| MediaGalleryPrefId /* pref_id */) { |
| UpdateGalleriesOnPreferencesEvent(); |
| } |
| |
| void MediaGalleriesPermissionController::OnGalleryRemoved( |
| MediaGalleriesPreferences* /* prefs */, |
| MediaGalleryPrefId /* pref_id */) { |
| UpdateGalleriesOnPreferencesEvent(); |
| } |
| |
| void MediaGalleriesPermissionController::OnGalleryInfoUpdated( |
| MediaGalleriesPreferences* prefs, |
| MediaGalleryPrefId pref_id) { |
| DCHECK(preferences_); |
| 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 MediaGalleriesPermissionController::InitializePermissions() { |
| known_galleries_.clear(); |
| DCHECK(preferences_); |
| const MediaGalleriesPrefInfoMap& galleries = preferences_->known_galleries(); |
| for (MediaGalleriesPrefInfoMap::const_iterator iter = galleries.begin(); |
| iter != galleries.end(); |
| ++iter) { |
| const MediaGalleryPrefInfo& gallery = iter->second; |
| if (gallery.IsBlackListedType()) |
| continue; |
| |
| GalleryDialogId gallery_id = GetDialogId(gallery.pref_id); |
| known_galleries_[gallery_id] = Entry(gallery, false); |
| known_galleries_[gallery_id].pref_info.pref_id = gallery_id; |
| } |
| |
| pref_permitted_galleries_ = preferences_->GalleriesForExtension(*extension_); |
| |
| for (MediaGalleryPrefIdSet::iterator iter = pref_permitted_galleries_.begin(); |
| iter != pref_permitted_galleries_.end(); |
| ++iter) { |
| GalleryDialogId gallery_id = GetDialogId(*iter); |
| DCHECK(ContainsKey(known_galleries_, gallery_id)); |
| known_galleries_[gallery_id].selected = true; |
| } |
| |
| // Preserve state of toggled galleries. |
| for (ToggledGalleryMap::const_iterator iter = toggled_galleries_.begin(); |
| iter != toggled_galleries_.end(); |
| ++iter) { |
| known_galleries_[iter->first].selected = iter->second; |
| } |
| } |
| |
| void MediaGalleriesPermissionController::SavePermissions() { |
| DCHECK(preferences_); |
| media_galleries::UsageCount(media_galleries::SAVE_DIALOG); |
| for (GalleryPermissionsMap::const_iterator iter = known_galleries_.begin(); |
| iter != known_galleries_.end(); ++iter) { |
| MediaGalleryPrefId pref_id = GetPrefId(iter->first); |
| if (ContainsKey(forgotten_galleries_, iter->first)) { |
| preferences_->ForgetGalleryById(pref_id); |
| } else { |
| bool changed = preferences_->SetGalleryPermissionForExtension( |
| *extension_, pref_id, iter->second.selected); |
| if (changed) { |
| if (iter->second.selected) { |
| media_galleries::UsageCount( |
| media_galleries::DIALOG_PERMISSION_ADDED); |
| } else { |
| media_galleries::UsageCount( |
| media_galleries::DIALOG_PERMISSION_REMOVED); |
| } |
| } |
| } |
| } |
| |
| for (GalleryPermissionsMap::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->second.selected) |
| continue; |
| |
| const MediaGalleryPrefInfo& gallery = iter->second.pref_info; |
| MediaGalleryPrefId id = preferences_->AddGallery( |
| gallery.device_id, gallery.path, MediaGalleryPrefInfo::kUserAdded, |
| gallery.volume_label, gallery.vendor_name, gallery.model_name, |
| gallery.total_size_in_bytes, gallery.last_attach_time, 0, 0, 0); |
| preferences_->SetGalleryPermissionForExtension(*extension_, id, true); |
| } |
| } |
| |
| void MediaGalleriesPermissionController::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(); |
| |
| std::set<GalleryDialogId> new_galleries_to_remove; |
| // Look for duplicate entries in |new_galleries_| in case one was added |
| // in another dialog. |
| for (GalleryPermissionsMap::iterator it = known_galleries_.begin(); |
| it != known_galleries_.end(); |
| ++it) { |
| Entry& gallery = it->second; |
| for (GalleryPermissionsMap::iterator new_it = new_galleries_.begin(); |
| new_it != new_galleries_.end(); |
| ++new_it) { |
| if (new_it->second.pref_info.path == gallery.pref_info.path && |
| new_it->second.pref_info.device_id == gallery.pref_info.device_id) { |
| // Found duplicate entry. Get the existing permission from it and then |
| // remove it. |
| gallery.selected = new_it->second.selected; |
| new_galleries_to_remove.insert(new_it->first); |
| break; |
| } |
| } |
| } |
| for (std::set<GalleryDialogId>::const_iterator it = |
| new_galleries_to_remove.begin(); |
| it != new_galleries_to_remove.end(); |
| ++it) { |
| new_galleries_.erase(*it); |
| } |
| |
| dialog_->UpdateGalleries(); |
| } |
| |
| void MediaGalleriesPermissionController::UpdateGalleriesOnDeviceEvent( |
| const std::string& device_id) { |
| dialog_->UpdateGalleries(); |
| } |
| |
| ui::MenuModel* MediaGalleriesPermissionController::GetContextMenu( |
| GalleryDialogId gallery_id) { |
| context_menu_->set_pref_id(gallery_id); |
| return context_menu_.get(); |
| } |
| |
| GalleryDialogId MediaGalleriesPermissionController::GetDialogId( |
| MediaGalleryPrefId pref_id) { |
| return id_map_.GetDialogId(pref_id); |
| } |
| |
| MediaGalleryPrefId MediaGalleriesPermissionController::GetPrefId( |
| GalleryDialogId id) const { |
| return id_map_.GetPrefId(id); |
| } |
| |
| Profile* MediaGalleriesPermissionController::GetProfile() { |
| return Profile::FromBrowserContext(web_contents_->GetBrowserContext()); |
| } |
| |
| MediaGalleriesPermissionController::DialogIdMap::DialogIdMap() |
| : next_dialog_id_(1) { |
| // Dialog id of 0 is invalid, so fill the slot. |
| forward_mapping_.push_back(kInvalidMediaGalleryPrefId); |
| } |
| |
| MediaGalleriesPermissionController::DialogIdMap::~DialogIdMap() { |
| } |
| |
| GalleryDialogId |
| MediaGalleriesPermissionController::DialogIdMap::GetDialogId( |
| MediaGalleryPrefId pref_id) { |
| std::map<GalleryDialogId, MediaGalleryPrefId>::const_iterator it = |
| back_map_.find(pref_id); |
| if (it != back_map_.end()) |
| return it->second; |
| |
| GalleryDialogId result = next_dialog_id_++; |
| DCHECK_EQ(result, forward_mapping_.size()); |
| forward_mapping_.push_back(pref_id); |
| if (pref_id != kInvalidMediaGalleryPrefId) |
| back_map_[pref_id] = result; |
| return result; |
| } |
| |
| MediaGalleryPrefId |
| MediaGalleriesPermissionController::DialogIdMap::GetPrefId( |
| GalleryDialogId id) const { |
| DCHECK_LT(id, next_dialog_id_); |
| return forward_mapping_[id]; |
| } |
| |
| // MediaGalleries dialog ------------------------------------------------------- |
| |
| MediaGalleriesDialog::~MediaGalleriesDialog() {} |