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 "components/storage_monitor/storage_info.h"
19 #include "components/storage_monitor/storage_monitor.h"
20 #include "content/public/browser/web_contents.h"
21 #include "extensions/browser/extension_prefs.h"
22 #include "extensions/common/extension.h"
23 #include "extensions/common/permissions/media_galleries_permission.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
;
32 using storage_monitor::StorageInfo
;
33 using storage_monitor::StorageMonitor
;
37 // Comparator for sorting GalleryPermissionsVector -- sorts
38 // allowed galleries low, and then sorts by absolute path.
39 bool GalleriesVectorComparator(
40 const MediaGalleriesDialogController::GalleryPermission
& a
,
41 const MediaGalleriesDialogController::GalleryPermission
& b
) {
42 if (a
.allowed
&& !b
.allowed
)
44 if (!a
.allowed
&& b
.allowed
)
47 return a
.pref_info
.AbsolutePath() < b
.pref_info
.AbsolutePath();
52 MediaGalleriesDialogController::MediaGalleriesDialogController(
53 content::WebContents
* web_contents
,
54 const Extension
& extension
,
55 const base::Closure
& on_finish
)
56 : web_contents_(web_contents
),
57 extension_(&extension
),
58 on_finish_(on_finish
),
60 g_browser_process
->media_file_system_registry()->GetPreferences(
62 create_dialog_callback_(base::Bind(&MediaGalleriesDialog::Create
)) {
63 // Passing unretained pointer is safe, since the dialog controller
64 // is self-deleting, and so won't be deleted until it can be shown
66 preferences_
->EnsureInitialized(
67 base::Bind(&MediaGalleriesDialogController::OnPreferencesInitialized
,
68 base::Unretained(this)));
70 // Unretained is safe because |this| owns |context_menu_|.
72 new MediaGalleryContextMenu(
73 base::Bind(&MediaGalleriesDialogController::DidForgetGallery
,
74 base::Unretained(this))));
77 void MediaGalleriesDialogController::OnPreferencesInitialized() {
78 if (StorageMonitor::GetInstance())
79 StorageMonitor::GetInstance()->AddObserver(this);
81 // |preferences_| may be NULL in tests.
83 preferences_
->AddGalleryChangeObserver(this);
84 InitializePermissions();
87 dialog_
.reset(create_dialog_callback_
.Run(this));
90 MediaGalleriesDialogController::MediaGalleriesDialogController(
91 const extensions::Extension
& extension
,
92 MediaGalleriesPreferences
* preferences
,
93 const CreateDialogCallback
& create_dialog_callback
,
94 const base::Closure
& on_finish
)
95 : web_contents_(NULL
),
96 extension_(&extension
),
97 on_finish_(on_finish
),
98 preferences_(preferences
),
99 create_dialog_callback_(create_dialog_callback
) {
100 OnPreferencesInitialized();
103 MediaGalleriesDialogController::~MediaGalleriesDialogController() {
104 if (StorageMonitor::GetInstance())
105 StorageMonitor::GetInstance()->RemoveObserver(this);
107 // |preferences_| may be NULL in tests.
109 preferences_
->RemoveGalleryChangeObserver(this);
111 if (select_folder_dialog_
.get())
112 select_folder_dialog_
->ListenerDestroyed();
115 base::string16
MediaGalleriesDialogController::GetHeader() const {
116 return l10n_util::GetStringFUTF16(IDS_MEDIA_GALLERIES_DIALOG_HEADER
,
117 base::UTF8ToUTF16(extension_
->name()));
120 base::string16
MediaGalleriesDialogController::GetSubtext() const {
121 extensions::MediaGalleriesPermission::CheckParam
copy_to_param(
122 extensions::MediaGalleriesPermission::kCopyToPermission
);
123 extensions::MediaGalleriesPermission::CheckParam
delete_param(
124 extensions::MediaGalleriesPermission::kDeletePermission
);
125 bool has_copy_to_permission
=
126 extensions::PermissionsData::CheckAPIPermissionWithParam(
127 extension_
, APIPermission::kMediaGalleries
, ©_to_param
);
128 bool has_delete_permission
=
129 extensions::PermissionsData::CheckAPIPermissionWithParam(
130 extension_
, APIPermission::kMediaGalleries
, &delete_param
);
133 if (has_copy_to_permission
)
134 id
= IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_WRITE
;
135 else if (has_delete_permission
)
136 id
= IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_DELETE
;
138 id
= IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_ONLY
;
140 return l10n_util::GetStringFUTF16(id
, base::UTF8ToUTF16(extension_
->name()));
143 base::string16
MediaGalleriesDialogController::GetUnattachedLocationsHeader()
145 return l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_UNATTACHED_LOCATIONS
);
148 bool MediaGalleriesDialogController::IsAcceptAllowed() const {
149 if (!toggled_galleries_
.empty() || !forgotten_galleries_
.empty())
152 for (GalleryPermissionsMap::const_iterator iter
= new_galleries_
.begin();
153 iter
!= new_galleries_
.end();
155 if (iter
->second
.allowed
)
162 // Note: sorts by display criterion: GalleriesVectorComparator.
163 MediaGalleriesDialogController::GalleryPermissionsVector
164 MediaGalleriesDialogController::FillPermissions(bool attached
) const {
165 GalleryPermissionsVector result
;
166 for (GalleryPermissionsMap::const_iterator iter
= known_galleries_
.begin();
167 iter
!= known_galleries_
.end(); ++iter
) {
168 if (!ContainsKey(forgotten_galleries_
, iter
->first
) &&
169 attached
== iter
->second
.pref_info
.IsGalleryAvailable()) {
170 result
.push_back(iter
->second
);
173 for (GalleryPermissionsMap::const_iterator iter
= new_galleries_
.begin();
174 iter
!= new_galleries_
.end(); ++iter
) {
175 if (attached
== iter
->second
.pref_info
.IsGalleryAvailable()) {
176 result
.push_back(iter
->second
);
180 std::sort(result
.begin(), result
.end(), GalleriesVectorComparator
);
184 MediaGalleriesDialogController::GalleryPermissionsVector
185 MediaGalleriesDialogController::AttachedPermissions() const {
186 return FillPermissions(true);
189 MediaGalleriesDialogController::GalleryPermissionsVector
190 MediaGalleriesDialogController::UnattachedPermissions() const {
191 return FillPermissions(false);
194 void MediaGalleriesDialogController::OnAddFolderClicked() {
195 base::FilePath default_path
=
196 extensions::file_system_api::GetLastChooseEntryDirectory(
197 extensions::ExtensionPrefs::Get(GetProfile()), extension_
->id());
198 if (default_path
.empty())
199 PathService::Get(base::DIR_USER_DESKTOP
, &default_path
);
200 select_folder_dialog_
=
201 ui::SelectFileDialog::Create(this, new ChromeSelectFilePolicy(NULL
));
202 select_folder_dialog_
->SelectFile(
203 ui::SelectFileDialog::SELECT_FOLDER
,
204 l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_DIALOG_ADD_GALLERY_TITLE
),
208 base::FilePath::StringType(),
209 web_contents_
->GetTopLevelNativeWindow(),
213 void MediaGalleriesDialogController::DidToggleGallery(
214 GalleryDialogId gallery_id
, bool enabled
) {
215 // Check known galleries.
216 GalleryPermissionsMap::iterator iter
= known_galleries_
.find(gallery_id
);
217 if (iter
!= known_galleries_
.end()) {
218 if (iter
->second
.allowed
== enabled
)
221 iter
->second
.allowed
= enabled
;
222 if (ContainsKey(toggled_galleries_
, gallery_id
))
223 toggled_galleries_
.erase(gallery_id
);
225 toggled_galleries_
.insert(gallery_id
);
229 iter
= new_galleries_
.find(gallery_id
);
230 if (iter
!= new_galleries_
.end())
231 iter
->second
.allowed
= enabled
;
233 // Don't sort -- the dialog is open, and we don't want to adjust any
234 // positions for future updates to the dialog contents until they are
238 void MediaGalleriesDialogController::DidForgetGallery(
239 GalleryDialogId gallery_id
) {
240 media_galleries::UsageCount(media_galleries::DIALOG_FORGET_GALLERY
);
241 if (!new_galleries_
.erase(gallery_id
)) {
242 DCHECK(ContainsKey(known_galleries_
, gallery_id
));
243 forgotten_galleries_
.insert(gallery_id
);
245 dialog_
->UpdateGalleries();
248 void MediaGalleriesDialogController::DialogFinished(bool accepted
) {
249 // The dialog has finished, so there is no need to watch for more updates
250 // from |preferences_|.
251 // |preferences_| may be NULL in tests.
253 preferences_
->RemoveGalleryChangeObserver(this);
263 content::WebContents
* MediaGalleriesDialogController::web_contents() {
264 return web_contents_
;
267 void MediaGalleriesDialogController::FileSelected(const base::FilePath
& path
,
270 extensions::file_system_api::SetLastChooseEntryDirectory(
271 extensions::ExtensionPrefs::Get(GetProfile()),
275 // Try to find it in the prefs.
276 MediaGalleryPrefInfo gallery
;
277 DCHECK(preferences_
);
278 bool gallery_exists
= preferences_
->LookUpGalleryByPath(path
, &gallery
);
279 if (gallery_exists
&& !gallery
.IsBlackListedType()) {
280 // The prefs are in sync with |known_galleries_|, so it should exist in
281 // |known_galleries_| as well. User selecting a known gallery effectively
282 // just sets the gallery to permitted.
283 GalleryDialogId gallery_id
= GetDialogId(gallery
.pref_id
);
284 GalleryPermissionsMap::iterator iter
= known_galleries_
.find(gallery_id
);
285 DCHECK(iter
!= known_galleries_
.end());
286 iter
->second
.allowed
= true;
287 forgotten_galleries_
.erase(gallery_id
);
288 dialog_
->UpdateGalleries();
292 // Try to find it in |new_galleries_| (user added same folder twice).
293 for (GalleryPermissionsMap::iterator iter
= new_galleries_
.begin();
294 iter
!= new_galleries_
.end(); ++iter
) {
295 if (iter
->second
.pref_info
.path
== gallery
.path
&&
296 iter
->second
.pref_info
.device_id
== gallery
.device_id
) {
297 iter
->second
.allowed
= true;
298 dialog_
->UpdateGalleries();
303 // Lastly, if not found, add a new gallery to |new_galleries_|.
304 // Note that it will have prefId = kInvalidMediaGalleryPrefId.
305 GalleryDialogId gallery_id
= GetDialogId(gallery
.pref_id
);
306 new_galleries_
[gallery_id
] = GalleryPermission(gallery_id
, gallery
, true);
307 dialog_
->UpdateGalleries();
310 void MediaGalleriesDialogController::OnRemovableStorageAttached(
311 const StorageInfo
& info
) {
312 UpdateGalleriesOnDeviceEvent(info
.device_id());
315 void MediaGalleriesDialogController::OnRemovableStorageDetached(
316 const StorageInfo
& info
) {
317 UpdateGalleriesOnDeviceEvent(info
.device_id());
320 void MediaGalleriesDialogController::OnPermissionAdded(
321 MediaGalleriesPreferences
* /* prefs */,
322 const std::string
& extension_id
,
323 MediaGalleryPrefId
/* pref_id */) {
324 if (extension_id
!= extension_
->id())
326 UpdateGalleriesOnPreferencesEvent();
329 void MediaGalleriesDialogController::OnPermissionRemoved(
330 MediaGalleriesPreferences
* /* prefs */,
331 const std::string
& extension_id
,
332 MediaGalleryPrefId
/* pref_id */) {
333 if (extension_id
!= extension_
->id())
335 UpdateGalleriesOnPreferencesEvent();
338 void MediaGalleriesDialogController::OnGalleryAdded(
339 MediaGalleriesPreferences
* /* prefs */,
340 MediaGalleryPrefId
/* pref_id */) {
341 UpdateGalleriesOnPreferencesEvent();
344 void MediaGalleriesDialogController::OnGalleryRemoved(
345 MediaGalleriesPreferences
* /* prefs */,
346 MediaGalleryPrefId
/* pref_id */) {
347 UpdateGalleriesOnPreferencesEvent();
350 void MediaGalleriesDialogController::OnGalleryInfoUpdated(
351 MediaGalleriesPreferences
* prefs
,
352 MediaGalleryPrefId pref_id
) {
353 DCHECK(preferences_
);
354 const MediaGalleriesPrefInfoMap
& pref_galleries
=
355 preferences_
->known_galleries();
356 MediaGalleriesPrefInfoMap::const_iterator pref_it
=
357 pref_galleries
.find(pref_id
);
358 if (pref_it
== pref_galleries
.end())
360 const MediaGalleryPrefInfo
& gallery_info
= pref_it
->second
;
361 UpdateGalleriesOnDeviceEvent(gallery_info
.device_id
);
364 void MediaGalleriesDialogController::InitializePermissions() {
365 known_galleries_
.clear();
366 DCHECK(preferences_
);
367 const MediaGalleriesPrefInfoMap
& galleries
= preferences_
->known_galleries();
368 for (MediaGalleriesPrefInfoMap::const_iterator iter
= galleries
.begin();
369 iter
!= galleries
.end();
371 const MediaGalleryPrefInfo
& gallery
= iter
->second
;
372 if (gallery
.IsBlackListedType())
375 GalleryDialogId gallery_id
= GetDialogId(gallery
.pref_id
);
376 known_galleries_
[gallery_id
] =
377 GalleryPermission(gallery_id
, gallery
, false);
380 MediaGalleryPrefIdSet permitted
=
381 preferences_
->GalleriesForExtension(*extension_
);
383 for (MediaGalleryPrefIdSet::iterator iter
= permitted
.begin();
384 iter
!= permitted
.end(); ++iter
) {
385 GalleryDialogId gallery_id
= GetDialogId(*iter
);
386 if (ContainsKey(toggled_galleries_
, gallery_id
))
388 DCHECK(ContainsKey(known_galleries_
, gallery_id
));
389 known_galleries_
[gallery_id
].allowed
= true;
393 void MediaGalleriesDialogController::SavePermissions() {
394 DCHECK(preferences_
);
395 media_galleries::UsageCount(media_galleries::SAVE_DIALOG
);
396 for (GalleryPermissionsMap::const_iterator iter
= known_galleries_
.begin();
397 iter
!= known_galleries_
.end(); ++iter
) {
398 MediaGalleryPrefId pref_id
= iter
->second
.pref_info
.pref_id
;
399 if (ContainsKey(forgotten_galleries_
, iter
->first
)) {
400 preferences_
->ForgetGalleryById(pref_id
);
402 bool changed
= preferences_
->SetGalleryPermissionForExtension(
403 *extension_
, pref_id
, iter
->second
.allowed
);
405 if (iter
->second
.allowed
) {
406 media_galleries::UsageCount(
407 media_galleries::DIALOG_PERMISSION_ADDED
);
409 media_galleries::UsageCount(
410 media_galleries::DIALOG_PERMISSION_REMOVED
);
416 for (GalleryPermissionsMap::const_iterator iter
= new_galleries_
.begin();
417 iter
!= new_galleries_
.end(); ++iter
) {
418 media_galleries::UsageCount(media_galleries::DIALOG_GALLERY_ADDED
);
419 // If the user added a gallery then unchecked it, forget about it.
420 if (!iter
->second
.allowed
)
423 const MediaGalleryPrefInfo
& gallery
= iter
->second
.pref_info
;
424 MediaGalleryPrefId id
= preferences_
->AddGallery(
425 gallery
.device_id
, gallery
.path
, MediaGalleryPrefInfo::kUserAdded
,
426 gallery
.volume_label
, gallery
.vendor_name
, gallery
.model_name
,
427 gallery
.total_size_in_bytes
, gallery
.last_attach_time
, 0, 0, 0);
428 preferences_
->SetGalleryPermissionForExtension(*extension_
, id
, true);
432 void MediaGalleriesDialogController::UpdateGalleriesOnPreferencesEvent() {
433 // Merge in the permissions from |preferences_|. Afterwards,
434 // |known_galleries_| may contain galleries that no longer belong there,
435 // but the code below will put |known_galleries_| back in a consistent state.
436 InitializePermissions();
438 std::set
<GalleryDialogId
> new_galleries_to_remove
;
439 // Look for duplicate entries in |new_galleries_| in case one was added
440 // in another dialog.
441 for (GalleryPermissionsMap::iterator it
= known_galleries_
.begin();
442 it
!= known_galleries_
.end();
444 GalleryPermission
& gallery
= it
->second
;
445 for (GalleryPermissionsMap::iterator new_it
= new_galleries_
.begin();
446 new_it
!= new_galleries_
.end();
448 if (new_it
->second
.pref_info
.path
== gallery
.pref_info
.path
&&
449 new_it
->second
.pref_info
.device_id
== gallery
.pref_info
.device_id
) {
450 // Found duplicate entry. Get the existing permission from it and then
452 gallery
.allowed
= new_it
->second
.allowed
;
453 new_galleries_to_remove
.insert(new_it
->first
);
458 for (std::set
<GalleryDialogId
>::const_iterator it
=
459 new_galleries_to_remove
.begin();
460 it
!= new_galleries_to_remove
.end();
462 new_galleries_
.erase(*it
);
465 dialog_
->UpdateGalleries();
468 void MediaGalleriesDialogController::UpdateGalleriesOnDeviceEvent(
469 const std::string
& device_id
) {
470 dialog_
->UpdateGalleries();
473 ui::MenuModel
* MediaGalleriesDialogController::GetContextMenu(
474 GalleryDialogId gallery_id
) {
475 context_menu_
->set_pref_id(gallery_id
);
476 return context_menu_
.get();
479 GalleryDialogId
MediaGalleriesDialogController::GetDialogId(
480 MediaGalleryPrefId pref_id
) {
481 return id_map_
.GetDialogId(pref_id
);
484 Profile
* MediaGalleriesDialogController::GetProfile() {
485 return Profile::FromBrowserContext(web_contents_
->GetBrowserContext());
488 MediaGalleriesDialogController::DialogIdMap::DialogIdMap()
489 : next_dialog_id_(1) {
492 MediaGalleriesDialogController::DialogIdMap::~DialogIdMap() {
496 MediaGalleriesDialogController::DialogIdMap::GetDialogId(
497 MediaGalleryPrefId pref_id
) {
498 std::map
<GalleryDialogId
, MediaGalleryPrefId
>::const_iterator it
=
499 mapping_
.find(pref_id
);
500 if (it
!= mapping_
.end())
503 GalleryDialogId result
= next_dialog_id_
++;
504 if (pref_id
!= kInvalidMediaGalleryPrefId
)
505 mapping_
[pref_id
] = result
;
509 // MediaGalleries dialog -------------------------------------------------------
511 MediaGalleriesDialog::~MediaGalleriesDialog() {}