blob: af69c38ece22e68744a165d663ecdfa9280e1cc9 [file] [log] [blame]
// 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, &copy_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() {}