1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/media_galleries/media_galleries_dialog_controller.h"
7 #include "base/base_paths.h"
8 #include "base/path_service.h"
9 #include "base/stl_util.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/browser/browser_process.h"
12 #include "chrome/browser/extensions/api/file_system/file_system_api.h"
13 #include "chrome/browser/media_galleries/media_file_system_registry.h"
14 #include "chrome/browser/media_galleries/media_galleries_histograms.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/storage_monitor/storage_info.h"
17 #include "chrome/browser/storage_monitor/storage_monitor.h"
18 #include "chrome/browser/ui/chrome_select_file_policy.h"
19 #include "chrome/common/extensions/permissions/media_galleries_permission.h"
20 #include "content/public/browser/web_contents.h"
21 #include "content/public/browser/web_contents_view.h"
22 #include "extensions/browser/extension_prefs.h"
23 #include "extensions/common/extension.h"
24 #include "extensions/common/permissions/permissions_data.h"
25 #include "grit/generated_resources.h"
26 #include "ui/base/l10n/l10n_util.h"
27 #include "ui/base/models/simple_menu_model.h"
28 #include "ui/base/text/bytes_formatting.h"
30 using extensions::APIPermission;
31 using extensions::Extension;
35 // Comparator for sorting GalleryPermissionsVector -- sorts
36 // allowed galleries low, and then sorts by absolute path.
37 bool GalleriesVectorComparator(
38 const MediaGalleriesDialogController::GalleryPermission& a,
39 const MediaGalleriesDialogController::GalleryPermission& b) {
40 if (a.allowed && !b.allowed)
42 if (!a.allowed && b.allowed)
45 return a.pref_info.AbsolutePath() < b.pref_info.AbsolutePath();
50 class GalleryContextMenuModel : public ui::SimpleMenuModel::Delegate {
52 explicit GalleryContextMenuModel(MediaGalleriesDialogController* controller)
53 : controller_(controller), id_(kInvalidMediaGalleryPrefId) {}
54 virtual ~GalleryContextMenuModel() {}
56 void set_media_gallery_pref_id(MediaGalleryPrefId id) {
60 virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
63 virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
66 virtual bool IsCommandIdVisible(int command_id) const OVERRIDE {
70 virtual bool GetAcceleratorForCommandId(
71 int command_id, ui::Accelerator* accelerator) OVERRIDE {
75 virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
76 controller_->DidForgetGallery(id_);
80 MediaGalleriesDialogController* controller_;
81 MediaGalleryPrefId id_;
84 MediaGalleriesDialogController::MediaGalleriesDialogController(
85 content::WebContents* web_contents,
86 const Extension& extension,
87 const base::Closure& on_finish)
88 : web_contents_(web_contents),
89 extension_(&extension),
90 on_finish_(on_finish) {
92 g_browser_process->media_file_system_registry()->GetPreferences(
94 // Passing unretained pointer is safe, since the dialog controller
95 // is self-deleting, and so won't be deleted until it can be shown
97 preferences_->EnsureInitialized(
98 base::Bind(&MediaGalleriesDialogController::OnPreferencesInitialized,
99 base::Unretained(this)));
101 gallery_menu_model_.reset(new GalleryContextMenuModel(this));
102 ui::SimpleMenuModel* menu_model =
103 new ui::SimpleMenuModel(gallery_menu_model_.get());
105 1, l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_DIALOG_DELETE));
106 context_menu_model_.reset(menu_model);
109 void MediaGalleriesDialogController::OnPreferencesInitialized() {
110 InitializePermissions();
112 dialog_.reset(MediaGalleriesDialog::Create(this));
114 StorageMonitor::GetInstance()->AddObserver(this);
116 preferences_->AddGalleryChangeObserver(this);
119 MediaGalleriesDialogController::MediaGalleriesDialogController(
120 const extensions::Extension& extension)
121 : web_contents_(NULL),
122 extension_(&extension),
123 preferences_(NULL) {}
125 MediaGalleriesDialogController::~MediaGalleriesDialogController() {
126 if (StorageMonitor::GetInstance())
127 StorageMonitor::GetInstance()->RemoveObserver(this);
129 if (select_folder_dialog_.get())
130 select_folder_dialog_->ListenerDestroyed();
133 base::string16 MediaGalleriesDialogController::GetHeader() const {
134 return l10n_util::GetStringFUTF16(IDS_MEDIA_GALLERIES_DIALOG_HEADER,
135 base::UTF8ToUTF16(extension_->name()));
138 base::string16 MediaGalleriesDialogController::GetSubtext() const {
139 extensions::MediaGalleriesPermission::CheckParam copy_to_param(
140 extensions::MediaGalleriesPermission::kCopyToPermission);
141 extensions::MediaGalleriesPermission::CheckParam delete_param(
142 extensions::MediaGalleriesPermission::kDeletePermission);
143 bool has_copy_to_permission =
144 extensions::PermissionsData::CheckAPIPermissionWithParam(
145 extension_, APIPermission::kMediaGalleries, ©_to_param);
146 bool has_delete_permission =
147 extensions::PermissionsData::CheckAPIPermissionWithParam(
148 extension_, APIPermission::kMediaGalleries, &delete_param);
151 if (has_copy_to_permission)
152 id = IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_WRITE;
153 else if (has_delete_permission)
154 id = IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_DELETE;
156 id = IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_ONLY;
158 return l10n_util::GetStringFUTF16(id, base::UTF8ToUTF16(extension_->name()));
161 base::string16 MediaGalleriesDialogController::GetUnattachedLocationsHeader()
163 return l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_UNATTACHED_LOCATIONS);
166 // TODO(gbillock): Call this something a bit more connected to the
167 // messaging in the dialog.
168 bool MediaGalleriesDialogController::HasPermittedGalleries() const {
169 for (KnownGalleryPermissions::const_iterator iter = known_galleries_.begin();
170 iter != known_galleries_.end(); ++iter) {
171 if (iter->second.allowed)
175 // Do this? Views did.
176 if (new_galleries_.size() > 0)
182 // Note: sorts by display criterion: GalleriesVectorComparator.
183 void MediaGalleriesDialogController::FillPermissions(
185 MediaGalleriesDialogController::GalleryPermissionsVector* permissions)
187 for (KnownGalleryPermissions::const_iterator iter = known_galleries_.begin();
188 iter != known_galleries_.end(); ++iter) {
189 if (attached == iter->second.pref_info.IsGalleryAvailable())
190 permissions->push_back(iter->second);
192 for (GalleryPermissionsVector::const_iterator iter = new_galleries_.begin();
193 iter != new_galleries_.end(); ++iter) {
194 if (attached == iter->pref_info.IsGalleryAvailable())
195 permissions->push_back(*iter);
198 std::sort(permissions->begin(), permissions->end(),
199 GalleriesVectorComparator);
202 MediaGalleriesDialogController::GalleryPermissionsVector
203 MediaGalleriesDialogController::AttachedPermissions() const {
204 GalleryPermissionsVector attached;
205 FillPermissions(true, &attached);
209 MediaGalleriesDialogController::GalleryPermissionsVector
210 MediaGalleriesDialogController::UnattachedPermissions() const {
211 GalleryPermissionsVector unattached;
212 FillPermissions(false, &unattached);
216 void MediaGalleriesDialogController::OnAddFolderClicked() {
217 base::FilePath default_path =
218 extensions::file_system_api::GetLastChooseEntryDirectory(
219 extensions::ExtensionPrefs::Get(GetProfile()), extension_->id());
220 if (default_path.empty())
221 PathService::Get(base::DIR_USER_DESKTOP, &default_path);
222 select_folder_dialog_ =
223 ui::SelectFileDialog::Create(this, new ChromeSelectFilePolicy(NULL));
224 select_folder_dialog_->SelectFile(
225 ui::SelectFileDialog::SELECT_FOLDER,
226 l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_DIALOG_ADD_GALLERY_TITLE),
230 base::FilePath::StringType(),
231 web_contents_->GetView()->GetTopLevelNativeWindow(),
235 void MediaGalleriesDialogController::DidToggleGalleryId(
236 MediaGalleryPrefId gallery_id,
238 // Check known galleries.
239 KnownGalleryPermissions::iterator iter =
240 known_galleries_.find(gallery_id);
241 if (iter != known_galleries_.end()) {
242 if (iter->second.allowed == enabled)
245 iter->second.allowed = enabled;
246 if (ContainsKey(toggled_galleries_, gallery_id))
247 toggled_galleries_.erase(gallery_id);
249 toggled_galleries_.insert(gallery_id);
253 // Don't sort -- the dialog is open, and we don't want to adjust any
254 // positions for future updates to the dialog contents until they are
258 void MediaGalleriesDialogController::DidToggleNewGallery(
259 const MediaGalleryPrefInfo& gallery,
261 for (GalleryPermissionsVector::iterator iter = new_galleries_.begin();
262 iter != new_galleries_.end(); ++iter) {
263 if (iter->pref_info.path == gallery.path &&
264 iter->pref_info.device_id == gallery.device_id) {
265 iter->allowed = enabled;
271 void MediaGalleriesDialogController::DidForgetGallery(
272 MediaGalleryPrefId pref_id) {
273 DCHECK(preferences_);
274 preferences_->ForgetGalleryById(pref_id);
277 void MediaGalleriesDialogController::DialogFinished(bool accepted) {
278 // The dialog has finished, so there is no need to watch for more updates
279 // from |preferences_|. Do this here and not in the dtor since this is the
280 // only non-test code path that deletes |this|. The test ctor never adds
281 // this observer in the first place.
282 preferences_->RemoveGalleryChangeObserver(this);
291 content::WebContents* MediaGalleriesDialogController::web_contents() {
292 return web_contents_;
295 void MediaGalleriesDialogController::FileSelected(const base::FilePath& path,
298 extensions::file_system_api::SetLastChooseEntryDirectory(
299 extensions::ExtensionPrefs::Get(GetProfile()),
303 // Try to find it in the prefs.
304 MediaGalleryPrefInfo gallery;
305 bool gallery_exists = preferences_->LookUpGalleryByPath(path, &gallery);
306 if (gallery_exists && !gallery.IsBlackListedType()) {
307 // The prefs are in sync with |known_galleries_|, so it should exist in
308 // |known_galleries_| as well. User selecting a known gallery effectively
309 // just sets the gallery to permitted.
310 DCHECK(ContainsKey(known_galleries_, gallery.pref_id));
311 dialog_->UpdateGalleries();
315 // Try to find it in |new_galleries_| (user added same folder twice).
316 for (GalleryPermissionsVector::iterator iter = new_galleries_.begin();
317 iter != new_galleries_.end(); ++iter) {
318 if (iter->pref_info.path == gallery.path &&
319 iter->pref_info.device_id == gallery.device_id) {
320 iter->allowed = true;
321 dialog_->UpdateGalleries();
326 // Lastly, if not found, add a new gallery to |new_galleries_|.
327 // Note that it will have prefId = kInvalidMediaGalleryPrefId.
328 new_galleries_.push_back(GalleryPermission(gallery, true));
329 dialog_->UpdateGalleries();
332 void MediaGalleriesDialogController::OnRemovableStorageAttached(
333 const StorageInfo& info) {
334 UpdateGalleriesOnDeviceEvent(info.device_id());
337 void MediaGalleriesDialogController::OnRemovableStorageDetached(
338 const StorageInfo& info) {
339 UpdateGalleriesOnDeviceEvent(info.device_id());
342 void MediaGalleriesDialogController::OnPermissionAdded(
343 MediaGalleriesPreferences* /* prefs */,
344 const std::string& extension_id,
345 MediaGalleryPrefId /* pref_id */) {
346 if (extension_id != extension_->id())
348 UpdateGalleriesOnPreferencesEvent();
351 void MediaGalleriesDialogController::OnPermissionRemoved(
352 MediaGalleriesPreferences* /* prefs */,
353 const std::string& extension_id,
354 MediaGalleryPrefId /* pref_id */) {
355 if (extension_id != extension_->id())
357 UpdateGalleriesOnPreferencesEvent();
360 void MediaGalleriesDialogController::OnGalleryAdded(
361 MediaGalleriesPreferences* /* prefs */,
362 MediaGalleryPrefId /* pref_id */) {
363 UpdateGalleriesOnPreferencesEvent();
366 void MediaGalleriesDialogController::OnGalleryRemoved(
367 MediaGalleriesPreferences* /* prefs */,
368 MediaGalleryPrefId /* pref_id */) {
369 UpdateGalleriesOnPreferencesEvent();
372 void MediaGalleriesDialogController::OnGalleryInfoUpdated(
373 MediaGalleriesPreferences* prefs,
374 MediaGalleryPrefId pref_id) {
375 const MediaGalleriesPrefInfoMap& pref_galleries =
376 preferences_->known_galleries();
377 MediaGalleriesPrefInfoMap::const_iterator pref_it =
378 pref_galleries.find(pref_id);
379 if (pref_it == pref_galleries.end())
381 const MediaGalleryPrefInfo& gallery_info = pref_it->second;
382 UpdateGalleriesOnDeviceEvent(gallery_info.device_id);
385 void MediaGalleriesDialogController::InitializePermissions() {
386 known_galleries_.clear();
387 const MediaGalleriesPrefInfoMap& galleries = preferences_->known_galleries();
388 for (MediaGalleriesPrefInfoMap::const_iterator iter = galleries.begin();
389 iter != galleries.end();
391 const MediaGalleryPrefInfo& gallery = iter->second;
392 if (gallery.IsBlackListedType())
395 known_galleries_[iter->first] = GalleryPermission(gallery, false);
398 MediaGalleryPrefIdSet permitted =
399 preferences_->GalleriesForExtension(*extension_);
401 for (MediaGalleryPrefIdSet::iterator iter = permitted.begin();
402 iter != permitted.end(); ++iter) {
403 if (ContainsKey(toggled_galleries_, *iter))
405 DCHECK(ContainsKey(known_galleries_, *iter));
406 known_galleries_[*iter].allowed = true;
410 void MediaGalleriesDialogController::SavePermissions() {
411 media_galleries::UsageCount(media_galleries::SAVE_DIALOG);
412 for (KnownGalleryPermissions::const_iterator iter = known_galleries_.begin();
413 iter != known_galleries_.end(); ++iter) {
414 bool changed = preferences_->SetGalleryPermissionForExtension(
415 *extension_, iter->first, iter->second.allowed);
417 if (iter->second.allowed)
418 media_galleries::UsageCount(media_galleries::DIALOG_PERMISSION_ADDED);
420 media_galleries::UsageCount(media_galleries::DIALOG_PERMISSION_REMOVED);
424 for (GalleryPermissionsVector::const_iterator iter = new_galleries_.begin();
425 iter != new_galleries_.end(); ++iter) {
426 media_galleries::UsageCount(media_galleries::DIALOG_GALLERY_ADDED);
427 // If the user added a gallery then unchecked it, forget about it.
431 // TODO(gbillock): Should be adding volume metadata during FileSelected.
432 const MediaGalleryPrefInfo& gallery = iter->pref_info;
433 MediaGalleryPrefId id = preferences_->AddGallery(
434 gallery.device_id, gallery.path, MediaGalleryPrefInfo::kUserAdded,
435 gallery.volume_label, gallery.vendor_name, gallery.model_name,
436 gallery.total_size_in_bytes, gallery.last_attach_time, 0, 0, 0);
437 preferences_->SetGalleryPermissionForExtension(*extension_, id, true);
441 void MediaGalleriesDialogController::UpdateGalleriesOnPreferencesEvent() {
442 // Merge in the permissions from |preferences_|. Afterwards,
443 // |known_galleries_| may contain galleries that no longer belong there,
444 // but the code below will put |known_galleries_| back in a consistent state.
445 InitializePermissions();
447 // Look for duplicate entries in |new_galleries_| in case one was added
448 // in another dialog.
449 for (KnownGalleryPermissions::iterator it = known_galleries_.begin();
450 it != known_galleries_.end();
452 GalleryPermission& gallery = it->second;
453 for (GalleryPermissionsVector::iterator new_it = new_galleries_.begin();
454 new_it != new_galleries_.end();
456 if (new_it->pref_info.path == gallery.pref_info.path &&
457 new_it->pref_info.device_id == gallery.pref_info.device_id) {
458 // Found duplicate entry. Get the existing permission from it and then
460 gallery.allowed = new_it->allowed;
461 new_galleries_.erase(new_it);
467 dialog_->UpdateGalleries();
470 void MediaGalleriesDialogController::UpdateGalleriesOnDeviceEvent(
471 const std::string& device_id) {
472 dialog_->UpdateGalleries();
475 ui::MenuModel* MediaGalleriesDialogController::GetContextMenuModel(
476 MediaGalleryPrefId id) {
477 gallery_menu_model_->set_media_gallery_pref_id(id);
478 return context_menu_model_.get();
481 Profile* MediaGalleriesDialogController::GetProfile() {
482 return Profile::FromBrowserContext(web_contents_->GetBrowserContext());
485 // MediaGalleries dialog -------------------------------------------------------
487 MediaGalleriesDialog::~MediaGalleriesDialog() {}