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. 4 5 #include "chrome/browser/media_galleries/media_galleries_scan_result_controller.h" 6 7 #include <algorithm> 8 #include <list> 9 10 #include "base/bind.h" 11 #include "base/logging.h" 12 #include "base/metrics/histogram.h" 13 #include "base/stl_util.h" 14 #include "base/strings/utf_string_conversions.h" 15 #include "chrome/browser/browser_process.h" 16 #include "chrome/browser/media_galleries/media_file_system_registry.h" 17 #include "chrome/browser/media_galleries/media_galleries_histograms.h" 18 #include "chrome/browser/media_galleries/media_gallery_context_menu.h" 19 #include "chrome/browser/platform_util.h" 20 #include "chrome/browser/profiles/profile.h" 21 #include "chrome/grit/generated_resources.h" 22 #include "components/storage_monitor/storage_info.h" 23 #include "components/storage_monitor/storage_monitor.h" 24 #include "content/public/browser/web_contents.h" 25 #include "extensions/common/extension.h" 26 #include "extensions/common/permissions/media_galleries_permission.h" 27 #include "extensions/common/permissions/permissions_data.h" 28 #include "ui/base/l10n/l10n_util.h" 29 30 using storage_monitor::StorageInfo; 31 using storage_monitor::StorageMonitor; 32 33 namespace { 34 35 // Comparator for sorting Entries -- more files first and then sorts by 36 // absolute path. 37 bool ScanResultsComparator( 38 const MediaGalleriesDialogController::Entry& a, 39 const MediaGalleriesDialogController::Entry& b) { 40 int a_media_count = a.pref_info.audio_count + a.pref_info.image_count + 41 a.pref_info.video_count; 42 int b_media_count = b.pref_info.audio_count + b.pref_info.image_count + 43 b.pref_info.video_count; 44 if (a_media_count == b_media_count) 45 return a.pref_info.AbsolutePath() < b.pref_info.AbsolutePath(); 46 return a_media_count > b_media_count; 47 } 48 49 } // namespace 50 51 // static 52 size_t MediaGalleriesScanResultController::ScanResultCountForExtension( 53 MediaGalleriesPreferences* preferences, 54 const extensions::Extension* extension) { 55 ScanResults scan_results; 56 UpdateScanResultsFromPreferences(preferences, extension, 57 MediaGalleryPrefIdSet(), &scan_results); 58 return scan_results.size(); 59 } 60 61 MediaGalleriesScanResultController::MediaGalleriesScanResultController( 62 content::WebContents* web_contents, 63 const extensions::Extension& extension, 64 const base::Closure& on_finish) 65 : web_contents_(web_contents), 66 extension_(&extension), 67 on_finish_(on_finish), 68 create_dialog_callback_(base::Bind(&MediaGalleriesDialog::Create)) { 69 preferences_ = 70 g_browser_process->media_file_system_registry()->GetPreferences( 71 GetProfile()); 72 // Passing unretained pointer is safe, since the dialog controller 73 // is self-deleting, and so won't be deleted until it can be shown 74 // and then closed. 75 preferences_->EnsureInitialized(base::Bind( 76 &MediaGalleriesScanResultController::OnPreferencesInitialized, 77 base::Unretained(this))); 78 79 // Unretained is safe because |this| owns |context_menu_|. 80 context_menu_.reset(new MediaGalleryContextMenu(base::Bind( 81 &MediaGalleriesScanResultController::DidForgetEntry, 82 base::Unretained(this)))); 83 } 84 85 MediaGalleriesScanResultController::MediaGalleriesScanResultController( 86 const extensions::Extension& extension, 87 MediaGalleriesPreferences* preferences, 88 const CreateDialogCallback& create_dialog_callback, 89 const base::Closure& on_finish) 90 : web_contents_(NULL), 91 extension_(&extension), 92 on_finish_(on_finish), 93 preferences_(preferences), 94 create_dialog_callback_(create_dialog_callback) { 95 OnPreferencesInitialized(); 96 } 97 98 MediaGalleriesScanResultController::~MediaGalleriesScanResultController() { 99 // |preferences_| may be NULL in tests. 100 if (preferences_) 101 preferences_->RemoveGalleryChangeObserver(this); 102 if (StorageMonitor::GetInstance()) 103 StorageMonitor::GetInstance()->RemoveObserver(this); 104 } 105 106 base::string16 MediaGalleriesScanResultController::GetHeader() const { 107 return l10n_util::GetStringFUTF16( 108 IDS_MEDIA_GALLERIES_SCAN_RESULT_DIALOG_HEADER, 109 base::UTF8ToUTF16(extension_->name())); 110 } 111 112 base::string16 MediaGalleriesScanResultController::GetSubtext() const { 113 extensions::MediaGalleriesPermission::CheckParam copy_to_param( 114 extensions::MediaGalleriesPermission::kCopyToPermission); 115 extensions::MediaGalleriesPermission::CheckParam delete_param( 116 extensions::MediaGalleriesPermission::kDeletePermission); 117 const extensions::PermissionsData* permissions_data = 118 extension_->permissions_data(); 119 bool has_copy_to_permission = permissions_data->CheckAPIPermissionWithParam( 120 extensions::APIPermission::kMediaGalleries, ©_to_param); 121 bool has_delete_permission = permissions_data->CheckAPIPermissionWithParam( 122 extensions::APIPermission::kMediaGalleries, &delete_param); 123 124 int id; 125 if (has_copy_to_permission) 126 id = IDS_MEDIA_GALLERIES_SCAN_RESULT_DIALOG_SUBTEXT_READ_WRITE; 127 else if (has_delete_permission) 128 id = IDS_MEDIA_GALLERIES_SCAN_RESULT_DIALOG_SUBTEXT_READ_DELETE; 129 else 130 id = IDS_MEDIA_GALLERIES_SCAN_RESULT_DIALOG_SUBTEXT_READ_ONLY; 131 132 return l10n_util::GetStringFUTF16(id, base::UTF8ToUTF16(extension_->name())); 133 } 134 135 bool MediaGalleriesScanResultController::IsAcceptAllowed() const { 136 return true; 137 } 138 139 bool MediaGalleriesScanResultController::ShouldShowFolderViewer( 140 const Entry& entry) const { 141 return entry.pref_info.IsGalleryAvailable(); 142 } 143 144 std::vector<base::string16> 145 MediaGalleriesScanResultController::GetSectionHeaders() const { 146 std::vector<base::string16> result; 147 result.push_back(base::string16()); 148 return result; 149 } 150 151 MediaGalleriesDialogController::Entries 152 MediaGalleriesScanResultController::GetSectionEntries( 153 size_t index) const { 154 DCHECK_EQ(0U, index); 155 Entries result; 156 result.reserve(scan_results_.size()); 157 for (ScanResults::const_iterator it = scan_results_.begin(); 158 it != scan_results_.end(); 159 ++it) { 160 result.push_back(it->second); 161 } 162 std::sort(result.begin(), result.end(), ScanResultsComparator); 163 return result; 164 } 165 166 base::string16 167 MediaGalleriesScanResultController::GetAuxiliaryButtonText() const { 168 return base::string16(); 169 } 170 171 void MediaGalleriesScanResultController::DidClickAuxiliaryButton() { 172 NOTREACHED(); 173 } 174 175 void MediaGalleriesScanResultController::DidToggleEntry( 176 MediaGalleryPrefId pref_id, bool selected) { 177 DCHECK(ContainsKey(scan_results_, pref_id)); 178 ScanResults::iterator entry = scan_results_.find(pref_id); 179 entry->second.selected = selected; 180 } 181 182 void MediaGalleriesScanResultController::DidClickOpenFolderViewer( 183 MediaGalleryPrefId pref_id) { 184 ScanResults::const_iterator entry = scan_results_.find(pref_id); 185 if (entry == scan_results_.end()) { 186 NOTREACHED(); 187 return; 188 } 189 platform_util::OpenItem(GetProfile(), entry->second.pref_info.AbsolutePath()); 190 } 191 192 void MediaGalleriesScanResultController::DidForgetEntry( 193 MediaGalleryPrefId pref_id) { 194 media_galleries::UsageCount(media_galleries::ADD_SCAN_RESULTS_FORGET_GALLERY); 195 results_to_remove_.insert(pref_id); 196 scan_results_.erase(pref_id); 197 dialog_->UpdateGalleries(); 198 } 199 200 base::string16 MediaGalleriesScanResultController::GetAcceptButtonText() const { 201 return l10n_util::GetStringUTF16( 202 IDS_MEDIA_GALLERIES_SCAN_RESULT_DIALOG_CONFIRM); 203 } 204 205 void MediaGalleriesScanResultController::DialogFinished(bool accepted) { 206 // No longer interested in preference updates (and the below code generates 207 // some). 208 // |preferences_| may be NULL in tests. 209 if (preferences_) 210 preferences_->RemoveGalleryChangeObserver(this); 211 212 if (accepted) { 213 DCHECK(preferences_); 214 media_galleries::UsageCount(media_galleries::ADD_SCAN_RESULTS_ACCEPTED); 215 int granted = 0; 216 int total = 0; 217 for (ScanResults::const_iterator it = scan_results_.begin(); 218 it != scan_results_.end(); 219 ++it) { 220 if (it->second.selected) { 221 bool changed = preferences_->SetGalleryPermissionForExtension( 222 *extension_, it->first, true); 223 DCHECK(changed); 224 granted++; 225 } 226 total++; 227 } 228 if (total > 0) { 229 UMA_HISTOGRAM_PERCENTAGE("MediaGalleries.ScanGalleriesGranted", 230 (granted * 100 / total)); 231 } 232 for (MediaGalleryPrefIdSet::const_iterator it = results_to_remove_.begin(); 233 it != results_to_remove_.end(); 234 ++it) { 235 preferences_->ForgetGalleryById(*it); 236 } 237 } else { 238 media_galleries::UsageCount(media_galleries::ADD_SCAN_RESULTS_CANCELLED); 239 } 240 241 on_finish_.Run(); 242 delete this; 243 } 244 245 ui::MenuModel* MediaGalleriesScanResultController::GetContextMenu( 246 MediaGalleryPrefId id) { 247 context_menu_->set_pref_id(id); 248 return context_menu_.get(); 249 } 250 251 content::WebContents* MediaGalleriesScanResultController::WebContents() { 252 return web_contents_; 253 } 254 255 // static 256 void MediaGalleriesScanResultController::UpdateScanResultsFromPreferences( 257 MediaGalleriesPreferences* preferences, 258 const extensions::Extension* extension, 259 MediaGalleryPrefIdSet ignore_list, 260 ScanResults* scan_results) { 261 DCHECK(preferences->IsInitialized()); 262 const MediaGalleriesPrefInfoMap& galleries = preferences->known_galleries(); 263 MediaGalleryPrefIdSet permitted = 264 preferences->GalleriesForExtension(*extension); 265 266 // Add or update any scan results that the extension doesn't already have 267 // access to or isn't in |ignore_list|. 268 for (MediaGalleriesPrefInfoMap::const_iterator it = galleries.begin(); 269 it != galleries.end(); 270 ++it) { 271 const MediaGalleryPrefInfo& gallery = it->second; 272 if ((gallery.audio_count || gallery.image_count || gallery.video_count) && 273 !gallery.IsBlackListedType() && 274 !ContainsKey(permitted, gallery.pref_id) && 275 !ContainsKey(ignore_list, gallery.pref_id)) { 276 ScanResults::iterator existing = scan_results->find(gallery.pref_id); 277 if (existing == scan_results->end()) { 278 // Default to selected. 279 (*scan_results)[gallery.pref_id] = Entry(gallery, true); 280 } else { 281 // Update pref_info, in case anything has been updated. 282 existing->second.pref_info = gallery; 283 } 284 } 285 } 286 287 // Remove anything from |scan_results| that's no longer valid or the user 288 // already has access to. 289 std::list<ScanResults::iterator> to_remove; 290 for (ScanResults::iterator it = scan_results->begin(); 291 it != scan_results->end(); 292 ++it) { 293 MediaGalleriesPrefInfoMap::const_iterator pref_gallery = 294 galleries.find(it->first); 295 if (pref_gallery == galleries.end() || 296 pref_gallery->second.IsBlackListedType() || 297 ContainsKey(permitted, it->first)) { 298 to_remove.push_back(it); 299 } 300 } 301 while (!to_remove.empty()) { 302 scan_results->erase(to_remove.front()); 303 to_remove.pop_front(); 304 } 305 } 306 307 void MediaGalleriesScanResultController::OnPreferencesInitialized() { 308 // These may be NULL in tests. 309 if (StorageMonitor::GetInstance()) 310 StorageMonitor::GetInstance()->AddObserver(this); 311 if (preferences_) { 312 preferences_->AddGalleryChangeObserver(this); 313 UpdateScanResultsFromPreferences(preferences_, extension_, 314 results_to_remove_, &scan_results_); 315 } 316 317 dialog_.reset(create_dialog_callback_.Run(this)); 318 } 319 320 void MediaGalleriesScanResultController::OnPreferenceUpdate( 321 const std::string& extension_id) { 322 if (extension_id == extension_->id()) { 323 UpdateScanResultsFromPreferences(preferences_, extension_, 324 results_to_remove_, &scan_results_); 325 dialog_->UpdateGalleries(); 326 } 327 } 328 329 void MediaGalleriesScanResultController::OnRemovableDeviceUpdate( 330 const std::string device_id) { 331 for (ScanResults::const_iterator it = scan_results_.begin(); 332 it != scan_results_.end(); 333 ++it) { 334 if (it->second.pref_info.device_id == device_id) { 335 dialog_->UpdateGalleries(); 336 return; 337 } 338 } 339 } 340 341 Profile* MediaGalleriesScanResultController::GetProfile() const { 342 return Profile::FromBrowserContext(web_contents_->GetBrowserContext()); 343 } 344 345 void MediaGalleriesScanResultController::OnRemovableStorageAttached( 346 const StorageInfo& info) { 347 OnRemovableDeviceUpdate(info.device_id()); 348 } 349 350 void MediaGalleriesScanResultController::OnRemovableStorageDetached( 351 const StorageInfo& info) { 352 OnRemovableDeviceUpdate(info.device_id()); 353 } 354 355 void MediaGalleriesScanResultController::OnPermissionAdded( 356 MediaGalleriesPreferences* /*pref*/, 357 const std::string& extension_id, 358 MediaGalleryPrefId /*pref_id*/) { 359 OnPreferenceUpdate(extension_id); 360 } 361 362 void MediaGalleriesScanResultController::OnPermissionRemoved( 363 MediaGalleriesPreferences* /*pref*/, 364 const std::string& extension_id, 365 MediaGalleryPrefId /*pref_id*/) { 366 OnPreferenceUpdate(extension_id); 367 } 368 369 void MediaGalleriesScanResultController::OnGalleryAdded( 370 MediaGalleriesPreferences* /*prefs*/, 371 MediaGalleryPrefId /*pref_id*/) { 372 OnPreferenceUpdate(extension_->id()); 373 } 374 375 void MediaGalleriesScanResultController::OnGalleryRemoved( 376 MediaGalleriesPreferences* /*prefs*/, 377 MediaGalleryPrefId /*pref_id*/) { 378 OnPreferenceUpdate(extension_->id()); 379 } 380 381 void MediaGalleriesScanResultController::OnGalleryInfoUpdated( 382 MediaGalleriesPreferences* /*prefs*/, 383 MediaGalleryPrefId /*pref_id*/) { 384 OnPreferenceUpdate(extension_->id()); 385 } 386