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/path_service.h" 8 #include "base/stl_util.h" 9 #include "base/strings/utf_string_conversions.h" 10 #include "chrome/browser/browser_process.h" 11 #include "chrome/browser/media_galleries/media_file_system_registry.h" 12 #include "chrome/browser/profiles/profile.h" 13 #include "chrome/browser/storage_monitor/storage_info.h" 14 #include "chrome/browser/storage_monitor/storage_monitor.h" 15 #include "chrome/browser/ui/chrome_select_file_policy.h" 16 #include "chrome/common/chrome_paths.h" 17 #include "chrome/common/extensions/extension.h" 18 #include "chrome/common/extensions/permissions/media_galleries_permission.h" 19 #include "chrome/common/extensions/permissions/permissions_data.h" 20 #include "content/public/browser/web_contents.h" 21 #include "content/public/browser/web_contents_view.h" 22 #include "grit/generated_resources.h" 23 #include "ui/base/l10n/l10n_util.h" 24 #include "ui/base/text/bytes_formatting.h" 25 26 using extensions::APIPermission; 27 using extensions::Extension; 28 29 namespace chrome { 30 31 namespace { 32 33 // Comparator for sorting GalleryPermissionsVector -- sorts 34 // allowed galleries low, and then sorts by absolute path. 35 bool GalleriesVectorComparator( 36 const MediaGalleriesDialogController::GalleryPermission& a, 37 const MediaGalleriesDialogController::GalleryPermission& b) { 38 if (a.allowed && !b.allowed) 39 return true; 40 if (!a.allowed && b.allowed) 41 return false; 42 43 return a.pref_info.AbsolutePath() < b.pref_info.AbsolutePath(); 44 } 45 46 } // namespace 47 48 MediaGalleriesDialogController::MediaGalleriesDialogController( 49 content::WebContents* web_contents, 50 const Extension& extension, 51 const base::Closure& on_finish) 52 : web_contents_(web_contents), 53 extension_(&extension), 54 on_finish_(on_finish) { 55 // Passing unretained pointer is safe, since the dialog controller 56 // is self-deleting, and so won't be deleted until it can be shown 57 // and then closed. 58 StorageMonitor::GetInstance()->EnsureInitialized(base::Bind( 59 &MediaGalleriesDialogController::OnStorageMonitorInitialized, 60 base::Unretained(this))); 61 } 62 63 void MediaGalleriesDialogController::OnStorageMonitorInitialized() { 64 MediaFileSystemRegistry* registry = 65 g_browser_process->media_file_system_registry(); 66 preferences_ = registry->GetPreferences( 67 Profile::FromBrowserContext(web_contents_->GetBrowserContext())); 68 InitializePermissions(); 69 70 dialog_.reset(MediaGalleriesDialog::Create(this)); 71 72 StorageMonitor::GetInstance()->AddObserver(this); 73 74 preferences_->AddGalleryChangeObserver(this); 75 } 76 77 MediaGalleriesDialogController::MediaGalleriesDialogController( 78 const extensions::Extension& extension) 79 : web_contents_(NULL), 80 extension_(&extension), 81 preferences_(NULL) {} 82 83 MediaGalleriesDialogController::~MediaGalleriesDialogController() { 84 if (chrome::StorageMonitor::GetInstance()) 85 StorageMonitor::GetInstance()->RemoveObserver(this); 86 87 if (select_folder_dialog_.get()) 88 select_folder_dialog_->ListenerDestroyed(); 89 } 90 91 string16 MediaGalleriesDialogController::GetHeader() const { 92 return l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_DIALOG_HEADER); 93 } 94 95 string16 MediaGalleriesDialogController::GetSubtext() const { 96 extensions::MediaGalleriesPermission::CheckParam read_param( 97 extensions::MediaGalleriesPermission::kReadPermission); 98 extensions::MediaGalleriesPermission::CheckParam copy_to_param( 99 extensions::MediaGalleriesPermission::kCopyToPermission); 100 bool has_read_permission = 101 extensions::PermissionsData::CheckAPIPermissionWithParam( 102 extension_, APIPermission::kMediaGalleries, &read_param); 103 bool has_copy_to_permission = 104 extensions::PermissionsData::CheckAPIPermissionWithParam( 105 extension_, APIPermission::kMediaGalleries, ©_to_param); 106 107 int id; 108 if (has_read_permission && has_copy_to_permission) 109 id = IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_WRITE; 110 else if (has_copy_to_permission) 111 id = IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_WRITE_ONLY; 112 else 113 id = IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_ONLY; 114 115 return l10n_util::GetStringFUTF16(id, UTF8ToUTF16(extension_->name())); 116 } 117 118 string16 MediaGalleriesDialogController::GetUnattachedLocationsHeader() const { 119 return l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_UNATTACHED_LOCATIONS); 120 } 121 122 // TODO(gbillock): Call this something a bit more connected to the 123 // messaging in the dialog. 124 bool MediaGalleriesDialogController::HasPermittedGalleries() const { 125 for (KnownGalleryPermissions::const_iterator iter = known_galleries_.begin(); 126 iter != known_galleries_.end(); ++iter) { 127 if (iter->second.allowed) 128 return true; 129 } 130 131 // Do this? Views did. 132 if (new_galleries_.size() > 0) 133 return true; 134 135 return false; 136 } 137 138 // Note: sorts by display criterion: GalleriesVectorComparator. 139 void MediaGalleriesDialogController::FillPermissions( 140 bool attached, 141 MediaGalleriesDialogController::GalleryPermissionsVector* permissions) 142 const { 143 for (KnownGalleryPermissions::const_iterator iter = known_galleries_.begin(); 144 iter != known_galleries_.end(); ++iter) { 145 if ((attached && iter->second.pref_info.IsGalleryAvailable()) || 146 (!attached && !iter->second.pref_info.IsGalleryAvailable())) { 147 permissions->push_back(iter->second); 148 } 149 } 150 for (GalleryPermissionsVector::const_iterator iter = new_galleries_.begin(); 151 iter != new_galleries_.end(); ++iter) { 152 if ((attached && iter->pref_info.IsGalleryAvailable()) || 153 (!attached && !iter->pref_info.IsGalleryAvailable())) { 154 permissions->push_back(*iter); 155 } 156 } 157 158 std::sort(permissions->begin(), permissions->end(), 159 GalleriesVectorComparator); 160 } 161 162 MediaGalleriesDialogController::GalleryPermissionsVector 163 MediaGalleriesDialogController::AttachedPermissions() const { 164 GalleryPermissionsVector attached; 165 FillPermissions(true, &attached); 166 return attached; 167 } 168 169 MediaGalleriesDialogController::GalleryPermissionsVector 170 MediaGalleriesDialogController::UnattachedPermissions() const { 171 GalleryPermissionsVector unattached; 172 FillPermissions(false, &unattached); 173 return unattached; 174 } 175 176 void MediaGalleriesDialogController::OnAddFolderClicked() { 177 base::FilePath user_data_dir; 178 PathService::Get(chrome::DIR_USER_DATA, &user_data_dir); 179 select_folder_dialog_ = 180 ui::SelectFileDialog::Create(this, new ChromeSelectFilePolicy(NULL)); 181 select_folder_dialog_->SelectFile( 182 ui::SelectFileDialog::SELECT_FOLDER, 183 l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_DIALOG_ADD_GALLERY_TITLE), 184 user_data_dir, 185 NULL, 186 0, 187 base::FilePath::StringType(), 188 web_contents_->GetView()->GetTopLevelNativeWindow(), 189 NULL); 190 } 191 192 void MediaGalleriesDialogController::DidToggleGalleryId( 193 MediaGalleryPrefId gallery_id, 194 bool enabled) { 195 // Check known galleries. 196 KnownGalleryPermissions::iterator iter = 197 known_galleries_.find(gallery_id); 198 if (iter != known_galleries_.end()) { 199 if (iter->second.allowed == enabled) 200 return; 201 202 iter->second.allowed = enabled; 203 if (ContainsKey(toggled_galleries_, gallery_id)) 204 toggled_galleries_.erase(gallery_id); 205 else 206 toggled_galleries_.insert(gallery_id); 207 return; 208 } 209 210 // Check new galleries. 211 for (GalleryPermissionsVector::iterator iter = new_galleries_.begin(); 212 iter != new_galleries_.end(); ++iter) { 213 if (iter->pref_info.pref_id == gallery_id) { 214 iter->allowed = enabled; 215 return; 216 } 217 } 218 219 // Don't sort -- the dialog is open, and we don't want to adjust any 220 // positions for future updates to the dialog contents until they are 221 // redrawn. 222 } 223 224 void MediaGalleriesDialogController::DialogFinished(bool accepted) { 225 // The dialog has finished, so there is no need to watch for more updates 226 // from |preferences_|. Do this here and not in the dtor since this is the 227 // only non-test code path that deletes |this|. The test ctor never adds 228 // this observer in the first place. 229 preferences_->RemoveGalleryChangeObserver(this); 230 231 if (accepted) 232 SavePermissions(); 233 234 on_finish_.Run(); 235 delete this; 236 } 237 238 content::WebContents* MediaGalleriesDialogController::web_contents() { 239 return web_contents_; 240 } 241 242 void MediaGalleriesDialogController::FileSelected(const base::FilePath& path, 243 int /*index*/, 244 void* /*params*/) { 245 // Try to find it in the prefs. 246 MediaGalleryPrefInfo gallery; 247 bool gallery_exists = preferences_->LookUpGalleryByPath(path, &gallery); 248 if (gallery_exists && gallery.type != MediaGalleryPrefInfo::kBlackListed) { 249 // The prefs are in sync with |known_galleries_|, so it should exist in 250 // |known_galleries_| as well. User selecting a known gallery effectively 251 // just sets the gallery to permitted. 252 KnownGalleryPermissions::const_iterator iter = 253 known_galleries_.find(gallery.pref_id); 254 DCHECK(iter != known_galleries_.end()); 255 dialog_->UpdateGallery(iter->second.pref_info, true); 256 return; 257 } 258 259 // Try to find it in |new_galleries_| (user added same folder twice). 260 for (GalleryPermissionsVector::iterator iter = new_galleries_.begin(); 261 iter != new_galleries_.end(); ++iter) { 262 if (iter->pref_info.path == gallery.path && 263 iter->pref_info.device_id == gallery.device_id) { 264 iter->allowed = true; 265 dialog_->UpdateGallery(iter->pref_info, true); 266 return; 267 } 268 } 269 270 // Lastly, add a new gallery to |new_galleries_|. 271 new_galleries_.push_back(GalleryPermission(gallery, true)); 272 dialog_->UpdateGallery(new_galleries_.back().pref_info, true); 273 } 274 275 void MediaGalleriesDialogController::OnRemovableStorageAttached( 276 const StorageInfo& info) { 277 UpdateGalleriesOnDeviceEvent(info.device_id()); 278 } 279 280 void MediaGalleriesDialogController::OnRemovableStorageDetached( 281 const StorageInfo& info) { 282 UpdateGalleriesOnDeviceEvent(info.device_id()); 283 } 284 285 void MediaGalleriesDialogController::OnGalleryChanged( 286 MediaGalleriesPreferences* pref, const std::string& extension_id, 287 MediaGalleryPrefId /* pref_id */, bool /* has_permission */) { 288 DCHECK_EQ(preferences_, pref); 289 if (extension_id.empty() || extension_id == extension_->id()) 290 UpdateGalleriesOnPreferencesEvent(); 291 } 292 293 void MediaGalleriesDialogController::InitializePermissions() { 294 const MediaGalleriesPrefInfoMap& galleries = preferences_->known_galleries(); 295 for (MediaGalleriesPrefInfoMap::const_iterator iter = galleries.begin(); 296 iter != galleries.end(); 297 ++iter) { 298 const MediaGalleryPrefInfo& gallery = iter->second; 299 if (gallery.type == MediaGalleryPrefInfo::kBlackListed) 300 continue; 301 302 known_galleries_[iter->first] = GalleryPermission(gallery, false); 303 } 304 305 MediaGalleryPrefIdSet permitted = 306 preferences_->GalleriesForExtension(*extension_); 307 308 for (MediaGalleryPrefIdSet::iterator iter = permitted.begin(); 309 iter != permitted.end(); ++iter) { 310 if (ContainsKey(toggled_galleries_, *iter)) 311 continue; 312 DCHECK(ContainsKey(known_galleries_, *iter)); 313 known_galleries_[*iter].allowed = true; 314 } 315 } 316 317 void MediaGalleriesDialogController::SavePermissions() { 318 for (KnownGalleryPermissions::const_iterator iter = known_galleries_.begin(); 319 iter != known_galleries_.end(); ++iter) { 320 preferences_->SetGalleryPermissionForExtension( 321 *extension_, iter->first, iter->second.allowed); 322 } 323 324 for (GalleryPermissionsVector::const_iterator iter = new_galleries_.begin(); 325 iter != new_galleries_.end(); ++iter) { 326 // If the user added a gallery then unchecked it, forget about it. 327 if (!iter->allowed) 328 continue; 329 330 // TODO(gbillock): Should be adding volume metadata during FileSelected. 331 const MediaGalleryPrefInfo& gallery = iter->pref_info; 332 MediaGalleryPrefId id = preferences_->AddGallery( 333 gallery.device_id, gallery.path, true, 334 gallery.volume_label, gallery.vendor_name, gallery.model_name, 335 gallery.total_size_in_bytes, gallery.last_attach_time); 336 preferences_->SetGalleryPermissionForExtension(*extension_, id, true); 337 } 338 } 339 340 void MediaGalleriesDialogController::UpdateGalleriesOnPreferencesEvent() { 341 // Merge in the permissions from |preferences_|. Afterwards, 342 // |known_galleries_| may contain galleries that no longer belong there, 343 // but the code below will put |known_galleries_| back in a consistent state. 344 InitializePermissions(); 345 346 // If a gallery no longer belongs in |known_galleries_|, forget it in the 347 // model/view. 348 // If a gallery still belong in |known_galleries_|, check for a duplicate 349 // entry in |new_galleries_|, merge its permission and remove it. Then update 350 // the view. 351 const MediaGalleriesPrefInfoMap& pref_galleries = 352 preferences_->known_galleries(); 353 MediaGalleryPrefIdSet galleries_to_forget; 354 for (KnownGalleryPermissions::iterator it = known_galleries_.begin(); 355 it != known_galleries_.end(); 356 ++it) { 357 const MediaGalleryPrefId& gallery_id = it->first; 358 GalleryPermission& gallery = it->second; 359 MediaGalleriesPrefInfoMap::const_iterator pref_it = 360 pref_galleries.find(gallery_id); 361 // Check for lingering entry that should be removed. 362 if (pref_it == pref_galleries.end() || 363 pref_it->second.type == MediaGalleryPrefInfo::kBlackListed) { 364 galleries_to_forget.insert(gallery_id); 365 dialog_->ForgetGallery(gallery.pref_info.pref_id); 366 continue; 367 } 368 369 // Look for duplicate entries in |new_galleries_|. 370 for (GalleryPermissionsVector::iterator new_it = new_galleries_.begin(); 371 new_it != new_galleries_.end(); 372 ++new_it) { 373 if (new_it->pref_info.path == gallery.pref_info.path && 374 new_it->pref_info.device_id == gallery.pref_info.device_id) { 375 // Found duplicate entry. Get the existing permission from it and then 376 // remove it. 377 gallery.allowed = new_it->allowed; 378 dialog_->ForgetGallery(new_it->pref_info.pref_id); 379 new_galleries_.erase(new_it); 380 break; 381 } 382 } 383 dialog_->UpdateGallery(gallery.pref_info, gallery.allowed); 384 } 385 386 // Remove the galleries to forget from |known_galleries_|. Doing it in the 387 // above loop would invalidate the iterator there. 388 for (MediaGalleryPrefIdSet::const_iterator it = galleries_to_forget.begin(); 389 it != galleries_to_forget.end(); 390 ++it) { 391 known_galleries_.erase(*it); 392 } 393 } 394 395 void MediaGalleriesDialogController::UpdateGalleriesOnDeviceEvent( 396 const std::string& device_id) { 397 for (KnownGalleryPermissions::iterator iter = known_galleries_.begin(); 398 iter != known_galleries_.end(); ++iter) { 399 if (iter->second.pref_info.device_id == device_id) 400 dialog_->UpdateGallery(iter->second.pref_info, iter->second.allowed); 401 } 402 403 for (GalleryPermissionsVector::iterator iter = new_galleries_.begin(); 404 iter != new_galleries_.end(); ++iter) { 405 if (iter->pref_info.device_id == device_id) 406 dialog_->UpdateGallery(iter->pref_info, iter->allowed); 407 } 408 } 409 410 // MediaGalleries dialog ------------------------------------------------------- 411 412 MediaGalleriesDialog::~MediaGalleriesDialog() {} 413 414 } // namespace chrome 415