Home | History | Annotate | Download | only in media_galleries
      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, &copy_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