OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/media_galleries/media_galleries_dialog_controller.h" | 5 #include "chrome/browser/media_galleries/media_galleries_dialog_controller.h" |
6 | 6 |
7 #include "base/base_paths.h" | 7 MediaGalleriesDialogController::MediaGalleriesDialogController() {} |
8 #include "base/path_service.h" | 8 MediaGalleriesDialogController::~MediaGalleriesDialogController() {} |
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" | |
29 | |
30 using extensions::APIPermission; | |
31 using extensions::Extension; | |
32 using storage_monitor::StorageInfo; | |
33 using storage_monitor::StorageMonitor; | |
34 | |
35 namespace { | |
36 | |
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) | |
43 return true; | |
44 if (!a.allowed && b.allowed) | |
45 return false; | |
46 | |
47 return a.pref_info.AbsolutePath() < b.pref_info.AbsolutePath(); | |
48 } | |
49 | |
50 } // namespace | |
51 | |
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), | |
59 preferences_( | |
60 g_browser_process->media_file_system_registry()->GetPreferences( | |
61 GetProfile())), | |
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 | |
65 // and then closed. | |
66 preferences_->EnsureInitialized( | |
67 base::Bind(&MediaGalleriesDialogController::OnPreferencesInitialized, | |
68 base::Unretained(this))); | |
69 | |
70 // Unretained is safe because |this| owns |context_menu_|. | |
71 context_menu_.reset( | |
72 new MediaGalleryContextMenu( | |
73 base::Bind(&MediaGalleriesDialogController::DidForgetGallery, | |
74 base::Unretained(this)))); | |
75 } | |
76 | |
77 void MediaGalleriesDialogController::OnPreferencesInitialized() { | |
78 if (StorageMonitor::GetInstance()) | |
79 StorageMonitor::GetInstance()->AddObserver(this); | |
80 | |
81 // |preferences_| may be NULL in tests. | |
82 if (preferences_) { | |
83 preferences_->AddGalleryChangeObserver(this); | |
84 InitializePermissions(); | |
85 } | |
86 | |
87 dialog_.reset(create_dialog_callback_.Run(this)); | |
88 } | |
89 | |
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(); | |
101 } | |
102 | |
103 MediaGalleriesDialogController::~MediaGalleriesDialogController() { | |
104 if (StorageMonitor::GetInstance()) | |
105 StorageMonitor::GetInstance()->RemoveObserver(this); | |
106 | |
107 // |preferences_| may be NULL in tests. | |
108 if (preferences_) | |
109 preferences_->RemoveGalleryChangeObserver(this); | |
110 | |
111 if (select_folder_dialog_.get()) | |
112 select_folder_dialog_->ListenerDestroyed(); | |
113 } | |
114 | |
115 base::string16 MediaGalleriesDialogController::GetHeader() const { | |
116 return l10n_util::GetStringFUTF16(IDS_MEDIA_GALLERIES_DIALOG_HEADER, | |
117 base::UTF8ToUTF16(extension_->name())); | |
118 } | |
119 | |
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 const extensions::PermissionsData* permissions_data = | |
126 extension_->permissions_data(); | |
127 bool has_copy_to_permission = permissions_data->CheckAPIPermissionWithParam( | |
128 APIPermission::kMediaGalleries, ©_to_param); | |
129 bool has_delete_permission = permissions_data->CheckAPIPermissionWithParam( | |
130 APIPermission::kMediaGalleries, &delete_param); | |
131 | |
132 int id; | |
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; | |
137 else | |
138 id = IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_ONLY; | |
139 | |
140 return l10n_util::GetStringFUTF16(id, base::UTF8ToUTF16(extension_->name())); | |
141 } | |
142 | |
143 base::string16 MediaGalleriesDialogController::GetUnattachedLocationsHeader() | |
144 const { | |
145 return l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_UNATTACHED_LOCATIONS); | |
146 } | |
147 | |
148 bool MediaGalleriesDialogController::IsAcceptAllowed() const { | |
149 if (!toggled_galleries_.empty() || !forgotten_galleries_.empty()) | |
150 return true; | |
151 | |
152 for (GalleryPermissionsMap::const_iterator iter = new_galleries_.begin(); | |
153 iter != new_galleries_.end(); | |
154 ++iter) { | |
155 if (iter->second.allowed) | |
156 return true; | |
157 } | |
158 | |
159 return false; | |
160 } | |
161 | |
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); | |
171 } | |
172 } | |
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); | |
177 } | |
178 } | |
179 | |
180 std::sort(result.begin(), result.end(), GalleriesVectorComparator); | |
181 return result; | |
182 } | |
183 | |
184 MediaGalleriesDialogController::GalleryPermissionsVector | |
185 MediaGalleriesDialogController::AttachedPermissions() const { | |
186 return FillPermissions(true); | |
187 } | |
188 | |
189 MediaGalleriesDialogController::GalleryPermissionsVector | |
190 MediaGalleriesDialogController::UnattachedPermissions() const { | |
191 return FillPermissions(false); | |
192 } | |
193 | |
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), | |
205 default_path, | |
206 NULL, | |
207 0, | |
208 base::FilePath::StringType(), | |
209 web_contents_->GetTopLevelNativeWindow(), | |
210 NULL); | |
211 } | |
212 | |
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) | |
219 return; | |
220 | |
221 iter->second.allowed = enabled; | |
222 if (ContainsKey(toggled_galleries_, gallery_id)) | |
223 toggled_galleries_.erase(gallery_id); | |
224 else | |
225 toggled_galleries_.insert(gallery_id); | |
226 return; | |
227 } | |
228 | |
229 iter = new_galleries_.find(gallery_id); | |
230 if (iter != new_galleries_.end()) | |
231 iter->second.allowed = enabled; | |
232 | |
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 | |
235 // redrawn. | |
236 } | |
237 | |
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); | |
244 } | |
245 dialog_->UpdateGalleries(); | |
246 } | |
247 | |
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. | |
252 if (preferences_) | |
253 preferences_->RemoveGalleryChangeObserver(this); | |
254 | |
255 if (accepted) | |
256 SavePermissions(); | |
257 | |
258 on_finish_.Run(); | |
259 | |
260 delete this; | |
261 } | |
262 | |
263 content::WebContents* MediaGalleriesDialogController::web_contents() { | |
264 return web_contents_; | |
265 } | |
266 | |
267 void MediaGalleriesDialogController::FileSelected(const base::FilePath& path, | |
268 int /*index*/, | |
269 void* /*params*/) { | |
270 extensions::file_system_api::SetLastChooseEntryDirectory( | |
271 extensions::ExtensionPrefs::Get(GetProfile()), | |
272 extension_->id(), | |
273 path); | |
274 | |
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(); | |
289 return; | |
290 } | |
291 | |
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(); | |
299 return; | |
300 } | |
301 } | |
302 | |
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(); | |
308 } | |
309 | |
310 void MediaGalleriesDialogController::OnRemovableStorageAttached( | |
311 const StorageInfo& info) { | |
312 UpdateGalleriesOnDeviceEvent(info.device_id()); | |
313 } | |
314 | |
315 void MediaGalleriesDialogController::OnRemovableStorageDetached( | |
316 const StorageInfo& info) { | |
317 UpdateGalleriesOnDeviceEvent(info.device_id()); | |
318 } | |
319 | |
320 void MediaGalleriesDialogController::OnPermissionAdded( | |
321 MediaGalleriesPreferences* /* prefs */, | |
322 const std::string& extension_id, | |
323 MediaGalleryPrefId /* pref_id */) { | |
324 if (extension_id != extension_->id()) | |
325 return; | |
326 UpdateGalleriesOnPreferencesEvent(); | |
327 } | |
328 | |
329 void MediaGalleriesDialogController::OnPermissionRemoved( | |
330 MediaGalleriesPreferences* /* prefs */, | |
331 const std::string& extension_id, | |
332 MediaGalleryPrefId /* pref_id */) { | |
333 if (extension_id != extension_->id()) | |
334 return; | |
335 UpdateGalleriesOnPreferencesEvent(); | |
336 } | |
337 | |
338 void MediaGalleriesDialogController::OnGalleryAdded( | |
339 MediaGalleriesPreferences* /* prefs */, | |
340 MediaGalleryPrefId /* pref_id */) { | |
341 UpdateGalleriesOnPreferencesEvent(); | |
342 } | |
343 | |
344 void MediaGalleriesDialogController::OnGalleryRemoved( | |
345 MediaGalleriesPreferences* /* prefs */, | |
346 MediaGalleryPrefId /* pref_id */) { | |
347 UpdateGalleriesOnPreferencesEvent(); | |
348 } | |
349 | |
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()) | |
359 return; | |
360 const MediaGalleryPrefInfo& gallery_info = pref_it->second; | |
361 UpdateGalleriesOnDeviceEvent(gallery_info.device_id); | |
362 } | |
363 | |
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(); | |
370 ++iter) { | |
371 const MediaGalleryPrefInfo& gallery = iter->second; | |
372 if (gallery.IsBlackListedType()) | |
373 continue; | |
374 | |
375 GalleryDialogId gallery_id = GetDialogId(gallery.pref_id); | |
376 known_galleries_[gallery_id] = | |
377 GalleryPermission(gallery_id, gallery, false); | |
378 } | |
379 | |
380 MediaGalleryPrefIdSet permitted = | |
381 preferences_->GalleriesForExtension(*extension_); | |
382 | |
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)) | |
387 continue; | |
388 DCHECK(ContainsKey(known_galleries_, gallery_id)); | |
389 known_galleries_[gallery_id].allowed = true; | |
390 } | |
391 } | |
392 | |
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); | |
401 } else { | |
402 bool changed = preferences_->SetGalleryPermissionForExtension( | |
403 *extension_, pref_id, iter->second.allowed); | |
404 if (changed) { | |
405 if (iter->second.allowed) { | |
406 media_galleries::UsageCount( | |
407 media_galleries::DIALOG_PERMISSION_ADDED); | |
408 } else { | |
409 media_galleries::UsageCount( | |
410 media_galleries::DIALOG_PERMISSION_REMOVED); | |
411 } | |
412 } | |
413 } | |
414 } | |
415 | |
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) | |
421 continue; | |
422 | |
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); | |
429 } | |
430 } | |
431 | |
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(); | |
437 | |
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(); | |
443 ++it) { | |
444 GalleryPermission& gallery = it->second; | |
445 for (GalleryPermissionsMap::iterator new_it = new_galleries_.begin(); | |
446 new_it != new_galleries_.end(); | |
447 ++new_it) { | |
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 | |
451 // remove it. | |
452 gallery.allowed = new_it->second.allowed; | |
453 new_galleries_to_remove.insert(new_it->first); | |
454 break; | |
455 } | |
456 } | |
457 } | |
458 for (std::set<GalleryDialogId>::const_iterator it = | |
459 new_galleries_to_remove.begin(); | |
460 it != new_galleries_to_remove.end(); | |
461 ++it) { | |
462 new_galleries_.erase(*it); | |
463 } | |
464 | |
465 dialog_->UpdateGalleries(); | |
466 } | |
467 | |
468 void MediaGalleriesDialogController::UpdateGalleriesOnDeviceEvent( | |
469 const std::string& device_id) { | |
470 dialog_->UpdateGalleries(); | |
471 } | |
472 | |
473 ui::MenuModel* MediaGalleriesDialogController::GetContextMenu( | |
474 GalleryDialogId gallery_id) { | |
475 context_menu_->set_pref_id(gallery_id); | |
476 return context_menu_.get(); | |
477 } | |
478 | |
479 GalleryDialogId MediaGalleriesDialogController::GetDialogId( | |
480 MediaGalleryPrefId pref_id) { | |
481 return id_map_.GetDialogId(pref_id); | |
482 } | |
483 | |
484 Profile* MediaGalleriesDialogController::GetProfile() { | |
485 return Profile::FromBrowserContext(web_contents_->GetBrowserContext()); | |
486 } | |
487 | |
488 MediaGalleriesDialogController::DialogIdMap::DialogIdMap() | |
489 : next_dialog_id_(1) { | |
490 } | |
491 | |
492 MediaGalleriesDialogController::DialogIdMap::~DialogIdMap() { | |
493 } | |
494 | |
495 GalleryDialogId | |
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()) | |
501 return it->second; | |
502 | |
503 GalleryDialogId result = next_dialog_id_++; | |
504 if (pref_id != kInvalidMediaGalleryPrefId) | |
505 mapping_[pref_id] = result; | |
506 return result; | |
507 } | |
508 | |
509 // MediaGalleries dialog ------------------------------------------------------- | |
510 | |
511 MediaGalleriesDialog::~MediaGalleriesDialog() {} | |
OLD | NEW |