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