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/media_galleries/media_gallery_context_menu.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/ui/chrome_select_file_policy.h"
18 #include "chrome/common/extensions/permissions/media_galleries_permission.h"
19 #include "components/storage_monitor/storage_info.h"
20 #include "components/storage_monitor/storage_monitor.h"
21 #include "content/public/browser/web_contents.h"
22 #include "content/public/browser/web_contents_view.h"
23 #include "extensions/browser/extension_prefs.h"
24 #include "extensions/common/extension.h"
25 #include "extensions/common/permissions/permissions_data.h"
26 #include "grit/generated_resources.h"
27 #include "ui/base/l10n/l10n_util.h"
28 #include "ui/base/models/simple_menu_model.h"
29 #include "ui/base/text/bytes_formatting.h"
31 using extensions::APIPermission;
32 using extensions::Extension;
36 // Comparator for sorting GalleryPermissionsVector -- sorts
37 // allowed galleries low, and then sorts by absolute path.
38 bool GalleriesVectorComparator(
39 const MediaGalleriesDialogController::GalleryPermission& a,
40 const MediaGalleriesDialogController::GalleryPermission& b) {
41 if (a.allowed && !b.allowed)
43 if (!a.allowed && b.allowed)
46 return a.pref_info.AbsolutePath() < b.pref_info.AbsolutePath();
51 MediaGalleriesDialogController::MediaGalleriesDialogController(
52 content::WebContents* web_contents,
53 const Extension& extension,
54 const base::Closure& on_finish)
55 : web_contents_(web_contents),
56 extension_(&extension),
57 on_finish_(on_finish),
59 g_browser_process->media_file_system_registry()->GetPreferences(
61 create_dialog_callback_(base::Bind(&MediaGalleriesDialog::Create)) {
62 // Passing unretained pointer is safe, since the dialog controller
63 // is self-deleting, and so won't be deleted until it can be shown
65 preferences_->EnsureInitialized(
66 base::Bind(&MediaGalleriesDialogController::OnPreferencesInitialized,
67 base::Unretained(this)));
69 // Unretained is safe because |this| owns |context_menu_|.
71 new MediaGalleryContextMenu(
72 base::Bind(&MediaGalleriesDialogController::DidForgetGallery,
73 base::Unretained(this))));
76 void MediaGalleriesDialogController::OnPreferencesInitialized() {
77 if (StorageMonitor::GetInstance())
78 StorageMonitor::GetInstance()->AddObserver(this);
80 // |preferences_| may be NULL in tests.
82 preferences_->AddGalleryChangeObserver(this);
83 InitializePermissions();
86 dialog_.reset(create_dialog_callback_.Run(this));
89 MediaGalleriesDialogController::MediaGalleriesDialogController(
90 const extensions::Extension& extension,
91 MediaGalleriesPreferences* preferences,
92 const CreateDialogCallback& create_dialog_callback,
93 const base::Closure& on_finish)
94 : web_contents_(NULL),
95 extension_(&extension),
96 on_finish_(on_finish),
97 preferences_(preferences),
98 create_dialog_callback_(create_dialog_callback) {
99 OnPreferencesInitialized();
102 MediaGalleriesDialogController::~MediaGalleriesDialogController() {
103 if (StorageMonitor::GetInstance())
104 StorageMonitor::GetInstance()->RemoveObserver(this);
106 // |preferences_| may be NULL in tests.
108 preferences_->RemoveGalleryChangeObserver(this);
110 if (select_folder_dialog_.get())
111 select_folder_dialog_->ListenerDestroyed();
114 base::string16 MediaGalleriesDialogController::GetHeader() const {
115 return l10n_util::GetStringFUTF16(IDS_MEDIA_GALLERIES_DIALOG_HEADER,
116 base::UTF8ToUTF16(extension_->name()));
119 base::string16 MediaGalleriesDialogController::GetSubtext() const {
120 extensions::MediaGalleriesPermission::CheckParam copy_to_param(
121 extensions::MediaGalleriesPermission::kCopyToPermission);
122 extensions::MediaGalleriesPermission::CheckParam delete_param(
123 extensions::MediaGalleriesPermission::kDeletePermission);
124 bool has_copy_to_permission =
125 extensions::PermissionsData::CheckAPIPermissionWithParam(
126 extension_, APIPermission::kMediaGalleries, ©_to_param);
127 bool has_delete_permission =
128 extensions::PermissionsData::CheckAPIPermissionWithParam(
129 extension_, APIPermission::kMediaGalleries, &delete_param);
132 if (has_copy_to_permission)
133 id = IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_WRITE;
134 else if (has_delete_permission)
135 id = IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_DELETE;
137 id = IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_ONLY;
139 return l10n_util::GetStringFUTF16(id, base::UTF8ToUTF16(extension_->name()));
142 base::string16 MediaGalleriesDialogController::GetUnattachedLocationsHeader()
144 return l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_UNATTACHED_LOCATIONS);
147 // TODO(gbillock): Call this something a bit more connected to the
148 // messaging in the dialog.
149 bool MediaGalleriesDialogController::HasPermittedGalleries() const {
150 for (KnownGalleryPermissions::const_iterator iter = known_galleries_.begin();
151 iter != known_galleries_.end(); ++iter) {
152 if (iter->second.allowed)
156 // Do this? Views did.
157 if (new_galleries_.size() > 0)
163 // Note: sorts by display criterion: GalleriesVectorComparator.
164 void MediaGalleriesDialogController::FillPermissions(
166 MediaGalleriesDialogController::GalleryPermissionsVector* permissions)
168 for (KnownGalleryPermissions::const_iterator iter = known_galleries_.begin();
169 iter != known_galleries_.end(); ++iter) {
170 if (!ContainsKey(forgotten_gallery_ids_, iter->first) &&
171 attached == iter->second.pref_info.IsGalleryAvailable()) {
172 permissions->push_back(iter->second);
175 for (GalleryPermissionsVector::const_iterator iter = new_galleries_.begin();
176 iter != new_galleries_.end(); ++iter) {
177 if (attached == iter->pref_info.IsGalleryAvailable()) {
178 permissions->push_back(*iter);
182 std::sort(permissions->begin(), permissions->end(),
183 GalleriesVectorComparator);
186 MediaGalleriesDialogController::GalleryPermissionsVector
187 MediaGalleriesDialogController::AttachedPermissions() const {
188 GalleryPermissionsVector attached;
189 FillPermissions(true, &attached);
193 MediaGalleriesDialogController::GalleryPermissionsVector
194 MediaGalleriesDialogController::UnattachedPermissions() const {
195 GalleryPermissionsVector unattached;
196 FillPermissions(false, &unattached);
200 void MediaGalleriesDialogController::OnAddFolderClicked() {
201 base::FilePath default_path =
202 extensions::file_system_api::GetLastChooseEntryDirectory(
203 extensions::ExtensionPrefs::Get(GetProfile()), extension_->id());
204 if (default_path.empty())
205 PathService::Get(base::DIR_USER_DESKTOP, &default_path);
206 select_folder_dialog_ =
207 ui::SelectFileDialog::Create(this, new ChromeSelectFilePolicy(NULL));
208 select_folder_dialog_->SelectFile(
209 ui::SelectFileDialog::SELECT_FOLDER,
210 l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_DIALOG_ADD_GALLERY_TITLE),
214 base::FilePath::StringType(),
215 web_contents_->GetView()->GetTopLevelNativeWindow(),
219 void MediaGalleriesDialogController::DidToggleGalleryId(
220 MediaGalleryPrefId gallery_id,
222 // Check known galleries.
223 KnownGalleryPermissions::iterator iter =
224 known_galleries_.find(gallery_id);
225 if (iter != known_galleries_.end()) {
226 if (iter->second.allowed == enabled)
229 iter->second.allowed = enabled;
230 if (ContainsKey(toggled_galleries_, gallery_id))
231 toggled_galleries_.erase(gallery_id);
233 toggled_galleries_.insert(gallery_id);
237 // Don't sort -- the dialog is open, and we don't want to adjust any
238 // positions for future updates to the dialog contents until they are
242 void MediaGalleriesDialogController::DidToggleNewGallery(
243 const MediaGalleryPrefInfo& gallery,
245 for (GalleryPermissionsVector::iterator iter = new_galleries_.begin();
246 iter != new_galleries_.end(); ++iter) {
247 if (iter->pref_info.path == gallery.path &&
248 iter->pref_info.device_id == gallery.device_id) {
249 iter->allowed = enabled;
255 void MediaGalleriesDialogController::DidForgetGallery(
256 MediaGalleryPrefId pref_id) {
257 // TODO(scr): remove from new_galleries_ if it's in there. Should
258 // new_galleries be a set? Why don't new_galleries allow context clicking?
259 DCHECK(ContainsKey(known_galleries_, pref_id));
260 forgotten_gallery_ids_.insert(pref_id);
261 dialog_->UpdateGalleries();
264 void MediaGalleriesDialogController::DialogFinished(bool accepted) {
265 // The dialog has finished, so there is no need to watch for more updates
266 // from |preferences_|.
267 // |preferences_| may be NULL in tests.
269 preferences_->RemoveGalleryChangeObserver(this);
279 content::WebContents* MediaGalleriesDialogController::web_contents() {
280 return web_contents_;
283 void MediaGalleriesDialogController::FileSelected(const base::FilePath& path,
286 extensions::file_system_api::SetLastChooseEntryDirectory(
287 extensions::ExtensionPrefs::Get(GetProfile()),
291 // Try to find it in the prefs.
292 MediaGalleryPrefInfo gallery;
293 DCHECK(preferences_);
294 bool gallery_exists = preferences_->LookUpGalleryByPath(path, &gallery);
295 if (gallery_exists && !gallery.IsBlackListedType()) {
296 // The prefs are in sync with |known_galleries_|, so it should exist in
297 // |known_galleries_| as well. User selecting a known gallery effectively
298 // just sets the gallery to permitted.
299 DCHECK(ContainsKey(known_galleries_, gallery.pref_id));
300 forgotten_gallery_ids_.erase(gallery.pref_id);
301 dialog_->UpdateGalleries();
305 // Try to find it in |new_galleries_| (user added same folder twice).
306 for (GalleryPermissionsVector::iterator iter = new_galleries_.begin();
307 iter != new_galleries_.end(); ++iter) {
308 if (iter->pref_info.path == gallery.path &&
309 iter->pref_info.device_id == gallery.device_id) {
310 iter->allowed = true;
311 dialog_->UpdateGalleries();
316 // Lastly, if not found, add a new gallery to |new_galleries_|.
317 // Note that it will have prefId = kInvalidMediaGalleryPrefId.
318 new_galleries_.push_back(GalleryPermission(gallery, true));
319 dialog_->UpdateGalleries();
322 void MediaGalleriesDialogController::OnRemovableStorageAttached(
323 const StorageInfo& info) {
324 UpdateGalleriesOnDeviceEvent(info.device_id());
327 void MediaGalleriesDialogController::OnRemovableStorageDetached(
328 const StorageInfo& info) {
329 UpdateGalleriesOnDeviceEvent(info.device_id());
332 void MediaGalleriesDialogController::OnPermissionAdded(
333 MediaGalleriesPreferences* /* prefs */,
334 const std::string& extension_id,
335 MediaGalleryPrefId /* pref_id */) {
336 if (extension_id != extension_->id())
338 UpdateGalleriesOnPreferencesEvent();
341 void MediaGalleriesDialogController::OnPermissionRemoved(
342 MediaGalleriesPreferences* /* prefs */,
343 const std::string& extension_id,
344 MediaGalleryPrefId /* pref_id */) {
345 if (extension_id != extension_->id())
347 UpdateGalleriesOnPreferencesEvent();
350 void MediaGalleriesDialogController::OnGalleryAdded(
351 MediaGalleriesPreferences* /* prefs */,
352 MediaGalleryPrefId /* pref_id */) {
353 UpdateGalleriesOnPreferencesEvent();
356 void MediaGalleriesDialogController::OnGalleryRemoved(
357 MediaGalleriesPreferences* /* prefs */,
358 MediaGalleryPrefId /* pref_id */) {
359 UpdateGalleriesOnPreferencesEvent();
362 void MediaGalleriesDialogController::OnGalleryInfoUpdated(
363 MediaGalleriesPreferences* prefs,
364 MediaGalleryPrefId pref_id) {
365 DCHECK(preferences_);
366 const MediaGalleriesPrefInfoMap& pref_galleries =
367 preferences_->known_galleries();
368 MediaGalleriesPrefInfoMap::const_iterator pref_it =
369 pref_galleries.find(pref_id);
370 if (pref_it == pref_galleries.end())
372 const MediaGalleryPrefInfo& gallery_info = pref_it->second;
373 UpdateGalleriesOnDeviceEvent(gallery_info.device_id);
376 void MediaGalleriesDialogController::InitializePermissions() {
377 known_galleries_.clear();
378 DCHECK(preferences_);
379 const MediaGalleriesPrefInfoMap& galleries = preferences_->known_galleries();
380 for (MediaGalleriesPrefInfoMap::const_iterator iter = galleries.begin();
381 iter != galleries.end();
383 const MediaGalleryPrefInfo& gallery = iter->second;
384 if (gallery.IsBlackListedType())
387 known_galleries_[iter->first] = GalleryPermission(gallery, false);
390 MediaGalleryPrefIdSet permitted =
391 preferences_->GalleriesForExtension(*extension_);
393 for (MediaGalleryPrefIdSet::iterator iter = permitted.begin();
394 iter != permitted.end(); ++iter) {
395 if (ContainsKey(toggled_galleries_, *iter))
397 DCHECK(ContainsKey(known_galleries_, *iter));
398 known_galleries_[*iter].allowed = true;
402 void MediaGalleriesDialogController::SavePermissions() {
403 DCHECK(preferences_);
404 media_galleries::UsageCount(media_galleries::SAVE_DIALOG);
405 for (KnownGalleryPermissions::const_iterator iter = known_galleries_.begin();
406 iter != known_galleries_.end(); ++iter) {
407 if (ContainsKey(forgotten_gallery_ids_, iter->first)) {
408 preferences_->ForgetGalleryById(iter->first);
410 bool changed = preferences_->SetGalleryPermissionForExtension(
411 *extension_, iter->first, iter->second.allowed);
413 if (iter->second.allowed) {
414 media_galleries::UsageCount(
415 media_galleries::DIALOG_PERMISSION_ADDED);
417 media_galleries::UsageCount(
418 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::GetContextMenu(
476 MediaGalleryPrefId id) {
477 context_menu_->set_pref_id(id);
478 return context_menu_.get();
481 Profile* MediaGalleriesDialogController::GetProfile() {
482 return Profile::FromBrowserContext(web_contents_->GetBrowserContext());
485 // MediaGalleries dialog -------------------------------------------------------
487 MediaGalleriesDialog::~MediaGalleriesDialog() {}