Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / media_galleries / media_galleries_permission_controller.cc
blob130049af52b2160296c2583794bb44f82451808b
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;
35 namespace {
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();
56 } // namespace
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),
65 preferences_(
66 g_browser_process->media_file_system_registry()->GetPreferences(
67 GetProfile())),
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
71 // and then closed.
72 preferences_->EnsureInitialized(
73 base::Bind(&MediaGalleriesPermissionController::OnPreferencesInitialized,
74 base::Unretained(this)));
76 // Unretained is safe because |this| owns |context_menu_|.
77 context_menu_.reset(
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.
88 if (preferences_) {
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.
114 if (preferences_)
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, &copy_to_param);
135 bool has_delete_permission = permission_data->CheckAPIPermissionWithParam(
136 APIPermission::kMediaGalleries, &delete_param);
138 int id;
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;
143 else
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())
151 return true;
153 for (GalleryPermissionsMap::const_iterator iter = new_galleries_.begin();
154 iter != new_galleries_.end();
155 ++iter) {
156 if (iter->second.selected)
157 return true;
160 return false;
163 bool MediaGalleriesPermissionController::ShouldShowFolderViewer(
164 const Entry& entry) const {
165 return false;
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.
172 result.push_back(
173 l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_PERMISSION_SUGGESTIONS));
174 return result;
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);
192 if (existing) {
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);
200 return result;
203 base::string16
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),
220 default_path,
221 NULL,
223 base::FilePath::StringType(),
224 web_contents_->GetTopLevelNativeWindow(),
225 NULL);
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)
234 return;
236 iter->second.selected = selected;
237 toggled_galleries_[gallery_id] = selected;
238 return;
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
247 // redrawn.
250 void MediaGalleriesPermissionController::DidClickOpenFolderViewer(
251 GalleryDialogId gallery_id) {
252 NOTREACHED();
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.
273 if (preferences_)
274 preferences_->RemoveGalleryChangeObserver(this);
276 if (accepted)
277 SavePermissions();
279 on_finish_.Run();
281 delete this;
284 content::WebContents* MediaGalleriesPermissionController::WebContents() {
285 return web_contents_;
288 void MediaGalleriesPermissionController::FileSelected(
289 const base::FilePath& path,
290 int /*index*/,
291 void* /*params*/) {
292 // |web_contents_| is NULL in tests.
293 if (web_contents_) {
294 extensions::file_system_api::SetLastChooseEntryDirectory(
295 extensions::ExtensionPrefs::Get(GetProfile()),
296 extension_->id(),
297 path);
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();
314 return;
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();
324 return;
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())
351 return;
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())
360 return;
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())
385 return;
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();
396 ++iter) {
397 const MediaGalleryPrefInfo& gallery = iter->second;
398 if (gallery.IsBlackListedType())
399 continue;
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();
410 ++iter) {
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();
419 ++iter) {
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);
432 } else {
433 bool changed = preferences_->SetGalleryPermissionForExtension(
434 *extension_, pref_id, iter->second.selected);
435 if (changed) {
436 if (iter->second.selected) {
437 media_galleries::UsageCount(
438 media_galleries::DIALOG_PERMISSION_ADDED);
439 } else {
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)
452 continue;
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();
474 ++it) {
475 Entry& gallery = it->second;
476 for (GalleryPermissionsMap::iterator new_it = new_galleries_.begin();
477 new_it != new_galleries_.end();
478 ++new_it) {
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
482 // remove it.
483 gallery.selected = new_it->second.selected;
484 new_galleries_to_remove.insert(new_it->first);
485 break;
489 for (std::set<GalleryDialogId>::const_iterator it =
490 new_galleries_to_remove.begin();
491 it != new_galleries_to_remove.end();
492 ++it) {
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() {
533 GalleryDialogId
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())
539 return it->second;
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;
546 return result;
549 MediaGalleryPrefId
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() {}