1 // Copyright 2014 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_permission_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/grit/generated_resources.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 "extensions/browser/extension_prefs.h"
23 #include "extensions/common/extension.h"
24 #include "extensions/common/permissions/media_galleries_permission.h"
25 #include "extensions/common/permissions/permissions_data.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
;
32 using storage_monitor::StorageInfo
;
33 using storage_monitor::StorageMonitor
;
37 // Comparator for sorting gallery entries. Sort Removable entries above
38 // non-removable ones. Within those two groups, sort on media counts
39 // if populated, otherwise on paths.
40 bool GalleriesVectorComparator(
41 const MediaGalleriesDialogController::Entry
& a
,
42 const MediaGalleriesDialogController::Entry
& b
) {
43 if (StorageInfo::IsRemovableDevice(a
.pref_info
.device_id
) !=
44 StorageInfo::IsRemovableDevice(b
.pref_info
.device_id
)) {
45 return StorageInfo::IsRemovableDevice(a
.pref_info
.device_id
);
47 int a_media_count
= a
.pref_info
.audio_count
+ a
.pref_info
.image_count
+
48 a
.pref_info
.video_count
;
49 int b_media_count
= b
.pref_info
.audio_count
+ b
.pref_info
.image_count
+
50 b
.pref_info
.video_count
;
51 if (a_media_count
!= b_media_count
)
52 return a_media_count
> b_media_count
;
53 return a
.pref_info
.AbsolutePath() < b
.pref_info
.AbsolutePath();
58 MediaGalleriesPermissionController::MediaGalleriesPermissionController(
59 content::WebContents
* web_contents
,
60 const Extension
& extension
,
61 const base::Closure
& on_finish
)
62 : web_contents_(web_contents
),
63 extension_(&extension
),
64 on_finish_(on_finish
),
66 g_browser_process
->media_file_system_registry()->GetPreferences(
68 create_dialog_callback_(base::Bind(&MediaGalleriesDialog::Create
)) {
69 // Passing unretained pointer is safe, since the dialog controller
70 // is self-deleting, and so won't be deleted until it can be shown
72 preferences_
->EnsureInitialized(
73 base::Bind(&MediaGalleriesPermissionController::OnPreferencesInitialized
,
74 base::Unretained(this)));
76 // Unretained is safe because |this| owns |context_menu_|.
78 new MediaGalleryContextMenu(
79 base::Bind(&MediaGalleriesPermissionController::DidForgetEntry
,
80 base::Unretained(this))));
83 void MediaGalleriesPermissionController::OnPreferencesInitialized() {
84 if (StorageMonitor::GetInstance())
85 StorageMonitor::GetInstance()->AddObserver(this);
87 // |preferences_| may be NULL in tests.
89 preferences_
->AddGalleryChangeObserver(this);
90 InitializePermissions();
93 dialog_
.reset(create_dialog_callback_
.Run(this));
96 MediaGalleriesPermissionController::MediaGalleriesPermissionController(
97 const extensions::Extension
& extension
,
98 MediaGalleriesPreferences
* preferences
,
99 const CreateDialogCallback
& create_dialog_callback
,
100 const base::Closure
& on_finish
)
101 : web_contents_(NULL
),
102 extension_(&extension
),
103 on_finish_(on_finish
),
104 preferences_(preferences
),
105 create_dialog_callback_(create_dialog_callback
) {
106 OnPreferencesInitialized();
109 MediaGalleriesPermissionController::~MediaGalleriesPermissionController() {
110 if (StorageMonitor::GetInstance())
111 StorageMonitor::GetInstance()->RemoveObserver(this);
113 // |preferences_| may be NULL in tests.
115 preferences_
->RemoveGalleryChangeObserver(this);
117 if (select_folder_dialog_
.get())
118 select_folder_dialog_
->ListenerDestroyed();
121 base::string16
MediaGalleriesPermissionController::GetHeader() const {
122 return l10n_util::GetStringFUTF16(IDS_MEDIA_GALLERIES_DIALOG_HEADER
,
123 base::UTF8ToUTF16(extension_
->name()));
126 base::string16
MediaGalleriesPermissionController::GetSubtext() const {
127 extensions::MediaGalleriesPermission::CheckParam
copy_to_param(
128 extensions::MediaGalleriesPermission::kCopyToPermission
);
129 extensions::MediaGalleriesPermission::CheckParam
delete_param(
130 extensions::MediaGalleriesPermission::kDeletePermission
);
131 const extensions::PermissionsData
* permission_data
=
132 extension_
->permissions_data();
133 bool has_copy_to_permission
= permission_data
->CheckAPIPermissionWithParam(
134 APIPermission::kMediaGalleries
, ©_to_param
);
135 bool has_delete_permission
= permission_data
->CheckAPIPermissionWithParam(
136 APIPermission::kMediaGalleries
, &delete_param
);
139 if (has_copy_to_permission
)
140 id
= IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_WRITE
;
141 else if (has_delete_permission
)
142 id
= IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_DELETE
;
144 id
= IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_ONLY
;
146 return l10n_util::GetStringFUTF16(id
, base::UTF8ToUTF16(extension_
->name()));
149 bool MediaGalleriesPermissionController::IsAcceptAllowed() const {
150 if (!toggled_galleries_
.empty() || !forgotten_galleries_
.empty())
153 for (GalleryPermissionsMap::const_iterator iter
= new_galleries_
.begin();
154 iter
!= new_galleries_
.end();
156 if (iter
->second
.selected
)
163 bool MediaGalleriesPermissionController::ShouldShowFolderViewer(
164 const Entry
& entry
) const {
168 std::vector
<base::string16
>
169 MediaGalleriesPermissionController::GetSectionHeaders() const {
170 std::vector
<base::string16
> result
;
171 result
.push_back(base::string16()); // First section has no header.
173 l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_PERMISSION_SUGGESTIONS
));
177 // Note: sorts by display criterion: GalleriesVectorComparator.
178 MediaGalleriesDialogController::Entries
179 MediaGalleriesPermissionController::GetSectionEntries(size_t index
) const {
180 DCHECK_GT(2U, index
); // This dialog only has two sections.
182 bool existing
= !index
;
183 MediaGalleriesDialogController::Entries result
;
184 for (GalleryPermissionsMap::const_iterator iter
= known_galleries_
.begin();
185 iter
!= known_galleries_
.end(); ++iter
) {
186 MediaGalleryPrefId pref_id
= GetPrefId(iter
->first
);
187 if (!ContainsKey(forgotten_galleries_
, iter
->first
) &&
188 existing
== ContainsKey(pref_permitted_galleries_
, pref_id
)) {
189 result
.push_back(iter
->second
);
193 for (GalleryPermissionsMap::const_iterator iter
= new_galleries_
.begin();
194 iter
!= new_galleries_
.end(); ++iter
) {
195 result
.push_back(iter
->second
);
199 std::sort(result
.begin(), result
.end(), GalleriesVectorComparator
);
204 MediaGalleriesPermissionController::GetAuxiliaryButtonText() const {
205 return l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_DIALOG_ADD_GALLERY
);
208 // This is the 'Add Folder' button.
209 void MediaGalleriesPermissionController::DidClickAuxiliaryButton() {
210 base::FilePath default_path
=
211 extensions::file_system_api::GetLastChooseEntryDirectory(
212 extensions::ExtensionPrefs::Get(GetProfile()), extension_
->id());
213 if (default_path
.empty())
214 PathService::Get(base::DIR_USER_DESKTOP
, &default_path
);
215 select_folder_dialog_
=
216 ui::SelectFileDialog::Create(this, new ChromeSelectFilePolicy(NULL
));
217 select_folder_dialog_
->SelectFile(
218 ui::SelectFileDialog::SELECT_FOLDER
,
219 l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_DIALOG_ADD_GALLERY_TITLE
),
223 base::FilePath::StringType(),
224 web_contents_
->GetTopLevelNativeWindow(),
228 void MediaGalleriesPermissionController::DidToggleEntry(
229 GalleryDialogId gallery_id
, bool selected
) {
230 // Check known galleries.
231 GalleryPermissionsMap::iterator iter
= known_galleries_
.find(gallery_id
);
232 if (iter
!= known_galleries_
.end()) {
233 if (iter
->second
.selected
== selected
)
236 iter
->second
.selected
= selected
;
237 toggled_galleries_
[gallery_id
] = selected
;
241 iter
= new_galleries_
.find(gallery_id
);
242 if (iter
!= new_galleries_
.end())
243 iter
->second
.selected
= selected
;
245 // Don't sort -- the dialog is open, and we don't want to adjust any
246 // positions for future updates to the dialog contents until they are
250 void MediaGalleriesPermissionController::DidClickOpenFolderViewer(
251 GalleryDialogId gallery_id
) {
255 void MediaGalleriesPermissionController::DidForgetEntry(
256 GalleryDialogId gallery_id
) {
257 media_galleries::UsageCount(media_galleries::DIALOG_FORGET_GALLERY
);
258 if (!new_galleries_
.erase(gallery_id
)) {
259 DCHECK(ContainsKey(known_galleries_
, gallery_id
));
260 forgotten_galleries_
.insert(gallery_id
);
262 dialog_
->UpdateGalleries();
265 base::string16
MediaGalleriesPermissionController::GetAcceptButtonText() const {
266 return l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_DIALOG_CONFIRM
);
269 void MediaGalleriesPermissionController::DialogFinished(bool accepted
) {
270 // The dialog has finished, so there is no need to watch for more updates
271 // from |preferences_|.
272 // |preferences_| may be NULL in tests.
274 preferences_
->RemoveGalleryChangeObserver(this);
284 content::WebContents
* MediaGalleriesPermissionController::WebContents() {
285 return web_contents_
;
288 void MediaGalleriesPermissionController::FileSelected(
289 const base::FilePath
& path
,
292 // |web_contents_| is NULL in tests.
294 extensions::file_system_api::SetLastChooseEntryDirectory(
295 extensions::ExtensionPrefs::Get(GetProfile()),
300 // Try to find it in the prefs.
301 MediaGalleryPrefInfo gallery
;
302 DCHECK(preferences_
);
303 bool gallery_exists
= preferences_
->LookUpGalleryByPath(path
, &gallery
);
304 if (gallery_exists
&& !gallery
.IsBlackListedType()) {
305 // The prefs are in sync with |known_galleries_|, so it should exist in
306 // |known_galleries_| as well. User selecting a known gallery effectively
307 // just sets the gallery to permitted.
308 GalleryDialogId gallery_id
= GetDialogId(gallery
.pref_id
);
309 GalleryPermissionsMap::iterator iter
= known_galleries_
.find(gallery_id
);
310 DCHECK(iter
!= known_galleries_
.end());
311 iter
->second
.selected
= true;
312 forgotten_galleries_
.erase(gallery_id
);
313 dialog_
->UpdateGalleries();
317 // Try to find it in |new_galleries_| (user added same folder twice).
318 for (GalleryPermissionsMap::iterator iter
= new_galleries_
.begin();
319 iter
!= new_galleries_
.end(); ++iter
) {
320 if (iter
->second
.pref_info
.path
== gallery
.path
&&
321 iter
->second
.pref_info
.device_id
== gallery
.device_id
) {
322 iter
->second
.selected
= true;
323 dialog_
->UpdateGalleries();
328 // Lastly, if not found, add a new gallery to |new_galleries_|.
329 // prefId == kInvalidMediaGalleryPrefId for completely new galleries.
330 // The old prefId is retained for blacklisted galleries.
331 gallery
.pref_id
= GetDialogId(gallery
.pref_id
);
332 new_galleries_
[gallery
.pref_id
] = Entry(gallery
, true);
333 dialog_
->UpdateGalleries();
336 void MediaGalleriesPermissionController::OnRemovableStorageAttached(
337 const StorageInfo
& info
) {
338 UpdateGalleriesOnDeviceEvent(info
.device_id());
341 void MediaGalleriesPermissionController::OnRemovableStorageDetached(
342 const StorageInfo
& info
) {
343 UpdateGalleriesOnDeviceEvent(info
.device_id());
346 void MediaGalleriesPermissionController::OnPermissionAdded(
347 MediaGalleriesPreferences
* /* prefs */,
348 const std::string
& extension_id
,
349 MediaGalleryPrefId
/* pref_id */) {
350 if (extension_id
!= extension_
->id())
352 UpdateGalleriesOnPreferencesEvent();
355 void MediaGalleriesPermissionController::OnPermissionRemoved(
356 MediaGalleriesPreferences
* /* prefs */,
357 const std::string
& extension_id
,
358 MediaGalleryPrefId
/* pref_id */) {
359 if (extension_id
!= extension_
->id())
361 UpdateGalleriesOnPreferencesEvent();
364 void MediaGalleriesPermissionController::OnGalleryAdded(
365 MediaGalleriesPreferences
* /* prefs */,
366 MediaGalleryPrefId
/* pref_id */) {
367 UpdateGalleriesOnPreferencesEvent();
370 void MediaGalleriesPermissionController::OnGalleryRemoved(
371 MediaGalleriesPreferences
* /* prefs */,
372 MediaGalleryPrefId
/* pref_id */) {
373 UpdateGalleriesOnPreferencesEvent();
376 void MediaGalleriesPermissionController::OnGalleryInfoUpdated(
377 MediaGalleriesPreferences
* prefs
,
378 MediaGalleryPrefId pref_id
) {
379 DCHECK(preferences_
);
380 const MediaGalleriesPrefInfoMap
& pref_galleries
=
381 preferences_
->known_galleries();
382 MediaGalleriesPrefInfoMap::const_iterator pref_it
=
383 pref_galleries
.find(pref_id
);
384 if (pref_it
== pref_galleries
.end())
386 const MediaGalleryPrefInfo
& gallery_info
= pref_it
->second
;
387 UpdateGalleriesOnDeviceEvent(gallery_info
.device_id
);
390 void MediaGalleriesPermissionController::InitializePermissions() {
391 known_galleries_
.clear();
392 DCHECK(preferences_
);
393 const MediaGalleriesPrefInfoMap
& galleries
= preferences_
->known_galleries();
394 for (MediaGalleriesPrefInfoMap::const_iterator iter
= galleries
.begin();
395 iter
!= galleries
.end();
397 const MediaGalleryPrefInfo
& gallery
= iter
->second
;
398 if (gallery
.IsBlackListedType())
401 GalleryDialogId gallery_id
= GetDialogId(gallery
.pref_id
);
402 known_galleries_
[gallery_id
] = Entry(gallery
, false);
403 known_galleries_
[gallery_id
].pref_info
.pref_id
= gallery_id
;
406 pref_permitted_galleries_
= preferences_
->GalleriesForExtension(*extension_
);
408 for (MediaGalleryPrefIdSet::iterator iter
= pref_permitted_galleries_
.begin();
409 iter
!= pref_permitted_galleries_
.end();
411 GalleryDialogId gallery_id
= GetDialogId(*iter
);
412 DCHECK(ContainsKey(known_galleries_
, gallery_id
));
413 known_galleries_
[gallery_id
].selected
= true;
416 // Preserve state of toggled galleries.
417 for (ToggledGalleryMap::const_iterator iter
= toggled_galleries_
.begin();
418 iter
!= toggled_galleries_
.end();
420 known_galleries_
[iter
->first
].selected
= iter
->second
;
424 void MediaGalleriesPermissionController::SavePermissions() {
425 DCHECK(preferences_
);
426 media_galleries::UsageCount(media_galleries::SAVE_DIALOG
);
427 for (GalleryPermissionsMap::const_iterator iter
= known_galleries_
.begin();
428 iter
!= known_galleries_
.end(); ++iter
) {
429 MediaGalleryPrefId pref_id
= GetPrefId(iter
->first
);
430 if (ContainsKey(forgotten_galleries_
, iter
->first
)) {
431 preferences_
->ForgetGalleryById(pref_id
);
433 bool changed
= preferences_
->SetGalleryPermissionForExtension(
434 *extension_
, pref_id
, iter
->second
.selected
);
436 if (iter
->second
.selected
) {
437 media_galleries::UsageCount(
438 media_galleries::DIALOG_PERMISSION_ADDED
);
440 media_galleries::UsageCount(
441 media_galleries::DIALOG_PERMISSION_REMOVED
);
447 for (GalleryPermissionsMap::const_iterator iter
= new_galleries_
.begin();
448 iter
!= new_galleries_
.end(); ++iter
) {
449 media_galleries::UsageCount(media_galleries::DIALOG_GALLERY_ADDED
);
450 // If the user added a gallery then unchecked it, forget about it.
451 if (!iter
->second
.selected
)
454 const MediaGalleryPrefInfo
& gallery
= iter
->second
.pref_info
;
455 MediaGalleryPrefId id
= preferences_
->AddGallery(
456 gallery
.device_id
, gallery
.path
, MediaGalleryPrefInfo::kUserAdded
,
457 gallery
.volume_label
, gallery
.vendor_name
, gallery
.model_name
,
458 gallery
.total_size_in_bytes
, gallery
.last_attach_time
, 0, 0, 0);
459 preferences_
->SetGalleryPermissionForExtension(*extension_
, id
, true);
463 void MediaGalleriesPermissionController::UpdateGalleriesOnPreferencesEvent() {
464 // Merge in the permissions from |preferences_|. Afterwards,
465 // |known_galleries_| may contain galleries that no longer belong there,
466 // but the code below will put |known_galleries_| back in a consistent state.
467 InitializePermissions();
469 std::set
<GalleryDialogId
> new_galleries_to_remove
;
470 // Look for duplicate entries in |new_galleries_| in case one was added
471 // in another dialog.
472 for (GalleryPermissionsMap::iterator it
= known_galleries_
.begin();
473 it
!= known_galleries_
.end();
475 Entry
& gallery
= it
->second
;
476 for (GalleryPermissionsMap::iterator new_it
= new_galleries_
.begin();
477 new_it
!= new_galleries_
.end();
479 if (new_it
->second
.pref_info
.path
== gallery
.pref_info
.path
&&
480 new_it
->second
.pref_info
.device_id
== gallery
.pref_info
.device_id
) {
481 // Found duplicate entry. Get the existing permission from it and then
483 gallery
.selected
= new_it
->second
.selected
;
484 new_galleries_to_remove
.insert(new_it
->first
);
489 for (std::set
<GalleryDialogId
>::const_iterator it
=
490 new_galleries_to_remove
.begin();
491 it
!= new_galleries_to_remove
.end();
493 new_galleries_
.erase(*it
);
496 dialog_
->UpdateGalleries();
499 void MediaGalleriesPermissionController::UpdateGalleriesOnDeviceEvent(
500 const std::string
& device_id
) {
501 dialog_
->UpdateGalleries();
504 ui::MenuModel
* MediaGalleriesPermissionController::GetContextMenu(
505 GalleryDialogId gallery_id
) {
506 context_menu_
->set_pref_id(gallery_id
);
507 return context_menu_
.get();
510 GalleryDialogId
MediaGalleriesPermissionController::GetDialogId(
511 MediaGalleryPrefId pref_id
) {
512 return id_map_
.GetDialogId(pref_id
);
515 MediaGalleryPrefId
MediaGalleriesPermissionController::GetPrefId(
516 GalleryDialogId id
) const {
517 return id_map_
.GetPrefId(id
);
520 Profile
* MediaGalleriesPermissionController::GetProfile() {
521 return Profile::FromBrowserContext(web_contents_
->GetBrowserContext());
524 MediaGalleriesPermissionController::DialogIdMap::DialogIdMap()
525 : next_dialog_id_(1) {
526 // Dialog id of 0 is invalid, so fill the slot.
527 forward_mapping_
.push_back(kInvalidMediaGalleryPrefId
);
530 MediaGalleriesPermissionController::DialogIdMap::~DialogIdMap() {
534 MediaGalleriesPermissionController::DialogIdMap::GetDialogId(
535 MediaGalleryPrefId pref_id
) {
536 std::map
<GalleryDialogId
, MediaGalleryPrefId
>::const_iterator it
=
537 back_map_
.find(pref_id
);
538 if (it
!= back_map_
.end())
541 GalleryDialogId result
= next_dialog_id_
++;
542 DCHECK_EQ(result
, forward_mapping_
.size());
543 forward_mapping_
.push_back(pref_id
);
544 if (pref_id
!= kInvalidMediaGalleryPrefId
)
545 back_map_
[pref_id
] = result
;
550 MediaGalleriesPermissionController::DialogIdMap::GetPrefId(
551 GalleryDialogId id
) const {
552 DCHECK_LT(id
, next_dialog_id_
);
553 return forward_mapping_
[id
];
556 // MediaGalleries dialog -------------------------------------------------------
558 MediaGalleriesDialog::~MediaGalleriesDialog() {}