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.
4
5 #include "chrome/browser/media_galleries/media_galleries_dialog_controller.h"
6
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/extensions/extension_prefs.h"
14 #include "chrome/browser/media_galleries/media_file_system_registry.h"
15 #include "chrome/browser/media_galleries/media_galleries_histograms.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/storage_monitor/storage_info.h"
18 #include "chrome/browser/storage_monitor/storage_monitor.h"
19 #include "chrome/browser/ui/chrome_select_file_policy.h"
20 #include "chrome/common/extensions/permissions/media_galleries_permission.h"
21 #include "content/public/browser/web_contents.h"
22 #include "content/public/browser/web_contents_view.h"
23 #include "extensions/common/extension.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
33 namespace {
34
35 // Comparator for sorting GalleryPermissionsVector -- sorts
36 // allowed galleries low, and then sorts by absolute path.
GalleriesVectorComparator(const MediaGalleriesDialogController::GalleryPermission & a,const MediaGalleriesDialogController::GalleryPermission & b)37 bool GalleriesVectorComparator(
38 const MediaGalleriesDialogController::GalleryPermission& a,
39 const MediaGalleriesDialogController::GalleryPermission& b) {
40 if (a.allowed && !b.allowed)
41 return true;
42 if (!a.allowed && b.allowed)
43 return false;
44
45 return a.pref_info.AbsolutePath() < b.pref_info.AbsolutePath();
46 }
47
48 } // namespace
49
50 class GalleryContextMenuModel : public ui::SimpleMenuModel::Delegate {
51 public:
GalleryContextMenuModel(MediaGalleriesDialogController * controller)52 explicit GalleryContextMenuModel(MediaGalleriesDialogController* controller)
53 : controller_(controller), id_(kInvalidMediaGalleryPrefId) {}
~GalleryContextMenuModel()54 virtual ~GalleryContextMenuModel() {}
55
set_media_gallery_pref_id(MediaGalleryPrefId id)56 void set_media_gallery_pref_id(MediaGalleryPrefId id) {
57 id_ = id;
58 }
59
IsCommandIdChecked(int command_id) const60 virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
61 return false;
62 }
IsCommandIdEnabled(int command_id) const63 virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
64 return true;
65 }
IsCommandIdVisible(int command_id) const66 virtual bool IsCommandIdVisible(int command_id) const OVERRIDE {
67 return true;
68 }
69
GetAcceleratorForCommandId(int command_id,ui::Accelerator * accelerator)70 virtual bool GetAcceleratorForCommandId(
71 int command_id, ui::Accelerator* accelerator) OVERRIDE {
72 return false;
73 }
74
ExecuteCommand(int command_id,int event_flags)75 virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
76 controller_->DidForgetGallery(id_);
77 }
78
79 private:
80 MediaGalleriesDialogController* controller_;
81 MediaGalleryPrefId id_;
82 };
83
MediaGalleriesDialogController(content::WebContents * web_contents,const Extension & extension,const base::Closure & on_finish)84 MediaGalleriesDialogController::MediaGalleriesDialogController(
85 content::WebContents* web_contents,
86 const Extension& extension,
87 const base::Closure& on_finish)
88 : web_contents_(web_contents),
89 extension_(&extension),
90 on_finish_(on_finish) {
91 preferences_ =
92 g_browser_process->media_file_system_registry()->GetPreferences(
93 GetProfile());
94 // Passing unretained pointer is safe, since the dialog controller
95 // is self-deleting, and so won't be deleted until it can be shown
96 // and then closed.
97 preferences_->EnsureInitialized(
98 base::Bind(&MediaGalleriesDialogController::OnPreferencesInitialized,
99 base::Unretained(this)));
100
101 gallery_menu_model_.reset(new GalleryContextMenuModel(this));
102 ui::SimpleMenuModel* menu_model =
103 new ui::SimpleMenuModel(gallery_menu_model_.get());
104 menu_model->AddItem(
105 1, l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_DIALOG_DELETE));
106 context_menu_model_.reset(menu_model);
107 }
108
OnPreferencesInitialized()109 void MediaGalleriesDialogController::OnPreferencesInitialized() {
110 InitializePermissions();
111
112 dialog_.reset(MediaGalleriesDialog::Create(this));
113
114 StorageMonitor::GetInstance()->AddObserver(this);
115
116 preferences_->AddGalleryChangeObserver(this);
117 }
118
MediaGalleriesDialogController(const extensions::Extension & extension)119 MediaGalleriesDialogController::MediaGalleriesDialogController(
120 const extensions::Extension& extension)
121 : web_contents_(NULL),
122 extension_(&extension),
123 preferences_(NULL) {}
124
~MediaGalleriesDialogController()125 MediaGalleriesDialogController::~MediaGalleriesDialogController() {
126 if (StorageMonitor::GetInstance())
127 StorageMonitor::GetInstance()->RemoveObserver(this);
128
129 if (select_folder_dialog_.get())
130 select_folder_dialog_->ListenerDestroyed();
131 }
132
GetHeader() const133 base::string16 MediaGalleriesDialogController::GetHeader() const {
134 return l10n_util::GetStringFUTF16(IDS_MEDIA_GALLERIES_DIALOG_HEADER,
135 UTF8ToUTF16(extension_->name()));
136 }
137
GetSubtext() const138 base::string16 MediaGalleriesDialogController::GetSubtext() const {
139 extensions::MediaGalleriesPermission::CheckParam copy_to_param(
140 extensions::MediaGalleriesPermission::kCopyToPermission);
141 extensions::MediaGalleriesPermission::CheckParam delete_param(
142 extensions::MediaGalleriesPermission::kDeletePermission);
143 bool has_copy_to_permission =
144 extensions::PermissionsData::CheckAPIPermissionWithParam(
145 extension_, APIPermission::kMediaGalleries, ©_to_param);
146 bool has_delete_permission =
147 extensions::PermissionsData::CheckAPIPermissionWithParam(
148 extension_, APIPermission::kMediaGalleries, &delete_param);
149
150 int id;
151 if (has_copy_to_permission)
152 id = IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_WRITE;
153 else if (has_delete_permission)
154 id = IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_DELETE;
155 else
156 id = IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_ONLY;
157
158 return l10n_util::GetStringFUTF16(id, UTF8ToUTF16(extension_->name()));
159 }
160
GetUnattachedLocationsHeader() const161 base::string16 MediaGalleriesDialogController::GetUnattachedLocationsHeader()
162 const {
163 return l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_UNATTACHED_LOCATIONS);
164 }
165
166 // TODO(gbillock): Call this something a bit more connected to the
167 // messaging in the dialog.
HasPermittedGalleries() const168 bool MediaGalleriesDialogController::HasPermittedGalleries() const {
169 for (KnownGalleryPermissions::const_iterator iter = known_galleries_.begin();
170 iter != known_galleries_.end(); ++iter) {
171 if (iter->second.allowed)
172 return true;
173 }
174
175 // Do this? Views did.
176 if (new_galleries_.size() > 0)
177 return true;
178
179 return false;
180 }
181
182 // Note: sorts by display criterion: GalleriesVectorComparator.
FillPermissions(bool attached,MediaGalleriesDialogController::GalleryPermissionsVector * permissions) const183 void MediaGalleriesDialogController::FillPermissions(
184 bool attached,
185 MediaGalleriesDialogController::GalleryPermissionsVector* permissions)
186 const {
187 for (KnownGalleryPermissions::const_iterator iter = known_galleries_.begin();
188 iter != known_galleries_.end(); ++iter) {
189 if (attached == iter->second.pref_info.IsGalleryAvailable())
190 permissions->push_back(iter->second);
191 }
192 for (GalleryPermissionsVector::const_iterator iter = new_galleries_.begin();
193 iter != new_galleries_.end(); ++iter) {
194 if (attached == iter->pref_info.IsGalleryAvailable())
195 permissions->push_back(*iter);
196 }
197
198 std::sort(permissions->begin(), permissions->end(),
199 GalleriesVectorComparator);
200 }
201
202 MediaGalleriesDialogController::GalleryPermissionsVector
AttachedPermissions() const203 MediaGalleriesDialogController::AttachedPermissions() const {
204 GalleryPermissionsVector attached;
205 FillPermissions(true, &attached);
206 return attached;
207 }
208
209 MediaGalleriesDialogController::GalleryPermissionsVector
UnattachedPermissions() const210 MediaGalleriesDialogController::UnattachedPermissions() const {
211 GalleryPermissionsVector unattached;
212 FillPermissions(false, &unattached);
213 return unattached;
214 }
215
OnAddFolderClicked()216 void MediaGalleriesDialogController::OnAddFolderClicked() {
217 base::FilePath default_path =
218 extensions::file_system_api::GetLastChooseEntryDirectory(
219 extensions::ExtensionPrefs::Get(GetProfile()), extension_->id());
220 if (default_path.empty())
221 PathService::Get(base::DIR_USER_DESKTOP, &default_path);
222 select_folder_dialog_ =
223 ui::SelectFileDialog::Create(this, new ChromeSelectFilePolicy(NULL));
224 select_folder_dialog_->SelectFile(
225 ui::SelectFileDialog::SELECT_FOLDER,
226 l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_DIALOG_ADD_GALLERY_TITLE),
227 default_path,
228 NULL,
229 0,
230 base::FilePath::StringType(),
231 web_contents_->GetView()->GetTopLevelNativeWindow(),
232 NULL);
233 }
234
DidToggleGalleryId(MediaGalleryPrefId gallery_id,bool enabled)235 void MediaGalleriesDialogController::DidToggleGalleryId(
236 MediaGalleryPrefId gallery_id,
237 bool enabled) {
238 // Check known galleries.
239 KnownGalleryPermissions::iterator iter =
240 known_galleries_.find(gallery_id);
241 if (iter != known_galleries_.end()) {
242 if (iter->second.allowed == enabled)
243 return;
244
245 iter->second.allowed = enabled;
246 if (ContainsKey(toggled_galleries_, gallery_id))
247 toggled_galleries_.erase(gallery_id);
248 else
249 toggled_galleries_.insert(gallery_id);
250 return;
251 }
252
253 // Don't sort -- the dialog is open, and we don't want to adjust any
254 // positions for future updates to the dialog contents until they are
255 // redrawn.
256 }
257
DidToggleNewGallery(const MediaGalleryPrefInfo & gallery,bool enabled)258 void MediaGalleriesDialogController::DidToggleNewGallery(
259 const MediaGalleryPrefInfo& gallery,
260 bool enabled) {
261 for (GalleryPermissionsVector::iterator iter = new_galleries_.begin();
262 iter != new_galleries_.end(); ++iter) {
263 if (iter->pref_info.path == gallery.path &&
264 iter->pref_info.device_id == gallery.device_id) {
265 iter->allowed = enabled;
266 return;
267 }
268 }
269 }
270
DidForgetGallery(MediaGalleryPrefId pref_id)271 void MediaGalleriesDialogController::DidForgetGallery(
272 MediaGalleryPrefId pref_id) {
273 DCHECK(preferences_);
274 preferences_->ForgetGalleryById(pref_id);
275 }
276
DialogFinished(bool accepted)277 void MediaGalleriesDialogController::DialogFinished(bool accepted) {
278 // The dialog has finished, so there is no need to watch for more updates
279 // from |preferences_|. Do this here and not in the dtor since this is the
280 // only non-test code path that deletes |this|. The test ctor never adds
281 // this observer in the first place.
282 preferences_->RemoveGalleryChangeObserver(this);
283
284 if (accepted)
285 SavePermissions();
286
287 on_finish_.Run();
288 delete this;
289 }
290
web_contents()291 content::WebContents* MediaGalleriesDialogController::web_contents() {
292 return web_contents_;
293 }
294
FileSelected(const base::FilePath & path,int,void *)295 void MediaGalleriesDialogController::FileSelected(const base::FilePath& path,
296 int /*index*/,
297 void* /*params*/) {
298 extensions::file_system_api::SetLastChooseEntryDirectory(
299 extensions::ExtensionPrefs::Get(GetProfile()),
300 extension_->id(),
301 path);
302
303 // Try to find it in the prefs.
304 MediaGalleryPrefInfo gallery;
305 bool gallery_exists = preferences_->LookUpGalleryByPath(path, &gallery);
306 if (gallery_exists && gallery.type != MediaGalleryPrefInfo::kBlackListed) {
307 // The prefs are in sync with |known_galleries_|, so it should exist in
308 // |known_galleries_| as well. User selecting a known gallery effectively
309 // just sets the gallery to permitted.
310 DCHECK(ContainsKey(known_galleries_, gallery.pref_id));
311 dialog_->UpdateGalleries();
312 return;
313 }
314
315 // Try to find it in |new_galleries_| (user added same folder twice).
316 for (GalleryPermissionsVector::iterator iter = new_galleries_.begin();
317 iter != new_galleries_.end(); ++iter) {
318 if (iter->pref_info.path == gallery.path &&
319 iter->pref_info.device_id == gallery.device_id) {
320 iter->allowed = true;
321 dialog_->UpdateGalleries();
322 return;
323 }
324 }
325
326 // Lastly, if not found, add a new gallery to |new_galleries_|.
327 // Note that it will have prefId = kInvalidMediaGalleryPrefId.
328 new_galleries_.push_back(GalleryPermission(gallery, true));
329 dialog_->UpdateGalleries();
330 }
331
OnRemovableStorageAttached(const StorageInfo & info)332 void MediaGalleriesDialogController::OnRemovableStorageAttached(
333 const StorageInfo& info) {
334 UpdateGalleriesOnDeviceEvent(info.device_id());
335 }
336
OnRemovableStorageDetached(const StorageInfo & info)337 void MediaGalleriesDialogController::OnRemovableStorageDetached(
338 const StorageInfo& info) {
339 UpdateGalleriesOnDeviceEvent(info.device_id());
340 }
341
OnPermissionAdded(MediaGalleriesPreferences *,const std::string & extension_id,MediaGalleryPrefId)342 void MediaGalleriesDialogController::OnPermissionAdded(
343 MediaGalleriesPreferences* /* prefs */,
344 const std::string& extension_id,
345 MediaGalleryPrefId /* pref_id */) {
346 if (extension_id != extension_->id())
347 return;
348 UpdateGalleriesOnPreferencesEvent();
349 }
350
OnPermissionRemoved(MediaGalleriesPreferences *,const std::string & extension_id,MediaGalleryPrefId)351 void MediaGalleriesDialogController::OnPermissionRemoved(
352 MediaGalleriesPreferences* /* prefs */,
353 const std::string& extension_id,
354 MediaGalleryPrefId /* pref_id */) {
355 if (extension_id != extension_->id())
356 return;
357 UpdateGalleriesOnPreferencesEvent();
358 }
359
OnGalleryAdded(MediaGalleriesPreferences *,MediaGalleryPrefId)360 void MediaGalleriesDialogController::OnGalleryAdded(
361 MediaGalleriesPreferences* /* prefs */,
362 MediaGalleryPrefId /* pref_id */) {
363 UpdateGalleriesOnPreferencesEvent();
364 }
365
OnGalleryRemoved(MediaGalleriesPreferences *,MediaGalleryPrefId)366 void MediaGalleriesDialogController::OnGalleryRemoved(
367 MediaGalleriesPreferences* /* prefs */,
368 MediaGalleryPrefId /* pref_id */) {
369 UpdateGalleriesOnPreferencesEvent();
370 }
371
OnGalleryInfoUpdated(MediaGalleriesPreferences * prefs,MediaGalleryPrefId pref_id)372 void MediaGalleriesDialogController::OnGalleryInfoUpdated(
373 MediaGalleriesPreferences* prefs,
374 MediaGalleryPrefId pref_id) {
375 const MediaGalleriesPrefInfoMap& pref_galleries =
376 preferences_->known_galleries();
377 MediaGalleriesPrefInfoMap::const_iterator pref_it =
378 pref_galleries.find(pref_id);
379 if (pref_it == pref_galleries.end())
380 return;
381 const MediaGalleryPrefInfo& gallery_info = pref_it->second;
382 UpdateGalleriesOnDeviceEvent(gallery_info.device_id);
383 }
384
InitializePermissions()385 void MediaGalleriesDialogController::InitializePermissions() {
386 known_galleries_.clear();
387 const MediaGalleriesPrefInfoMap& galleries = preferences_->known_galleries();
388 for (MediaGalleriesPrefInfoMap::const_iterator iter = galleries.begin();
389 iter != galleries.end();
390 ++iter) {
391 const MediaGalleryPrefInfo& gallery = iter->second;
392 if (gallery.type == MediaGalleryPrefInfo::kBlackListed) {
393 continue;
394 }
395
396 known_galleries_[iter->first] = GalleryPermission(gallery, false);
397 }
398
399 MediaGalleryPrefIdSet permitted =
400 preferences_->GalleriesForExtension(*extension_);
401
402 for (MediaGalleryPrefIdSet::iterator iter = permitted.begin();
403 iter != permitted.end(); ++iter) {
404 if (ContainsKey(toggled_galleries_, *iter))
405 continue;
406 DCHECK(ContainsKey(known_galleries_, *iter));
407 known_galleries_[*iter].allowed = true;
408 }
409 }
410
SavePermissions()411 void MediaGalleriesDialogController::SavePermissions() {
412 media_galleries::UsageCount(media_galleries::SAVE_DIALOG);
413 for (KnownGalleryPermissions::const_iterator iter = known_galleries_.begin();
414 iter != known_galleries_.end(); ++iter) {
415 bool changed = preferences_->SetGalleryPermissionForExtension(
416 *extension_, iter->first, iter->second.allowed);
417 if (changed) {
418 if (iter->second.allowed)
419 media_galleries::UsageCount(media_galleries::DIALOG_PERMISSION_ADDED);
420 else
421 media_galleries::UsageCount(media_galleries::DIALOG_PERMISSION_REMOVED);
422 }
423 }
424
425 for (GalleryPermissionsVector::const_iterator iter = new_galleries_.begin();
426 iter != new_galleries_.end(); ++iter) {
427 media_galleries::UsageCount(media_galleries::DIALOG_GALLERY_ADDED);
428 // If the user added a gallery then unchecked it, forget about it.
429 if (!iter->allowed)
430 continue;
431
432 // TODO(gbillock): Should be adding volume metadata during FileSelected.
433 const MediaGalleryPrefInfo& gallery = iter->pref_info;
434 MediaGalleryPrefId id = preferences_->AddGallery(
435 gallery.device_id, gallery.path, true,
436 gallery.volume_label, gallery.vendor_name, gallery.model_name,
437 gallery.total_size_in_bytes, gallery.last_attach_time);
438 preferences_->SetGalleryPermissionForExtension(*extension_, id, true);
439 }
440 }
441
UpdateGalleriesOnPreferencesEvent()442 void MediaGalleriesDialogController::UpdateGalleriesOnPreferencesEvent() {
443 // Merge in the permissions from |preferences_|. Afterwards,
444 // |known_galleries_| may contain galleries that no longer belong there,
445 // but the code below will put |known_galleries_| back in a consistent state.
446 InitializePermissions();
447
448 // Look for duplicate entries in |new_galleries_| in case one was added
449 // in another dialog.
450 for (KnownGalleryPermissions::iterator it = known_galleries_.begin();
451 it != known_galleries_.end();
452 ++it) {
453 GalleryPermission& gallery = it->second;
454 for (GalleryPermissionsVector::iterator new_it = new_galleries_.begin();
455 new_it != new_galleries_.end();
456 ++new_it) {
457 if (new_it->pref_info.path == gallery.pref_info.path &&
458 new_it->pref_info.device_id == gallery.pref_info.device_id) {
459 // Found duplicate entry. Get the existing permission from it and then
460 // remove it.
461 gallery.allowed = new_it->allowed;
462 new_galleries_.erase(new_it);
463 break;
464 }
465 }
466 }
467
468 dialog_->UpdateGalleries();
469 }
470
UpdateGalleriesOnDeviceEvent(const std::string & device_id)471 void MediaGalleriesDialogController::UpdateGalleriesOnDeviceEvent(
472 const std::string& device_id) {
473 dialog_->UpdateGalleries();
474 }
475
GetContextMenuModel(MediaGalleryPrefId id)476 ui::MenuModel* MediaGalleriesDialogController::GetContextMenuModel(
477 MediaGalleryPrefId id) {
478 gallery_menu_model_->set_media_gallery_pref_id(id);
479 return context_menu_model_.get();
480 }
481
GetProfile()482 Profile* MediaGalleriesDialogController::GetProfile() {
483 return Profile::FromBrowserContext(web_contents_->GetBrowserContext());
484 }
485
486 // MediaGalleries dialog -------------------------------------------------------
487
~MediaGalleriesDialog()488 MediaGalleriesDialog::~MediaGalleriesDialog() {}
489