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_preferences.h"
      6 
      7 #include "base/i18n/time_formatting.h"
      8 #include "base/path_service.h"
      9 #include "base/prefs/pref_service.h"
     10 #include "base/stl_util.h"
     11 #include "base/strings/string16.h"
     12 #include "base/strings/string_number_conversions.h"
     13 #include "base/strings/utf_string_conversions.h"
     14 #include "base/values.h"
     15 #include "chrome/browser/browser_process.h"
     16 #include "chrome/browser/extensions/api/media_galleries_private/media_galleries_private_api.h"
     17 #include "chrome/browser/extensions/extension_prefs.h"
     18 #include "chrome/browser/extensions/extension_service.h"
     19 #include "chrome/browser/extensions/extension_system.h"
     20 #include "chrome/browser/media_galleries/fileapi/itunes_finder.h"
     21 #include "chrome/browser/media_galleries/fileapi/picasa/picasa_finder.h"
     22 #include "chrome/browser/media_galleries/media_file_system_registry.h"
     23 #include "chrome/browser/prefs/scoped_user_pref_update.h"
     24 #include "chrome/browser/profiles/profile.h"
     25 #include "chrome/browser/storage_monitor/media_storage_util.h"
     26 #include "chrome/browser/storage_monitor/storage_monitor.h"
     27 #include "chrome/common/chrome_paths.h"
     28 #include "chrome/common/extensions/extension.h"
     29 #include "chrome/common/extensions/permissions/api_permission.h"
     30 #include "chrome/common/extensions/permissions/media_galleries_permission.h"
     31 #include "chrome/common/extensions/permissions/permissions_data.h"
     32 #include "chrome/common/pref_names.h"
     33 #include "components/user_prefs/pref_registry_syncable.h"
     34 #include "grit/generated_resources.h"
     35 #include "ui/base/l10n/l10n_util.h"
     36 #include "ui/base/text/bytes_formatting.h"
     37 
     38 using base::DictionaryValue;
     39 using base::ListValue;
     40 using extensions::ExtensionPrefs;
     41 
     42 namespace chrome {
     43 
     44 namespace {
     45 
     46 // Pref key for the list of media gallery permissions.
     47 const char kMediaGalleriesPermissions[] = "media_galleries_permissions";
     48 // Pref key for Media Gallery ID.
     49 const char kMediaGalleryIdKey[] = "id";
     50 // Pref key for Media Gallery Permission Value.
     51 const char kMediaGalleryHasPermissionKey[] = "has_permission";
     52 
     53 const char kMediaGalleriesDeviceIdKey[] = "deviceId";
     54 const char kMediaGalleriesDisplayNameKey[] = "displayName";
     55 const char kMediaGalleriesPathKey[] = "path";
     56 const char kMediaGalleriesPrefIdKey[] = "prefId";
     57 const char kMediaGalleriesTypeKey[] = "type";
     58 const char kMediaGalleriesVolumeLabelKey[] = "volumeLabel";
     59 const char kMediaGalleriesVendorNameKey[] = "vendorName";
     60 const char kMediaGalleriesModelNameKey[] = "modelName";
     61 const char kMediaGalleriesSizeKey[] = "totalSize";
     62 const char kMediaGalleriesLastAttachTimeKey[] = "lastAttachTime";
     63 const char kMediaGalleriesPrefsVersionKey[] = "preferencesVersion";
     64 
     65 const char kMediaGalleriesTypeAutoDetectedValue[] = "autoDetected";
     66 const char kMediaGalleriesTypeUserAddedValue[] = "userAdded";
     67 const char kMediaGalleriesTypeBlackListedValue[] = "blackListed";
     68 
     69 const char kITunesGalleryName[] = "iTunes";
     70 const char kPicasaGalleryName[] = "Picasa";
     71 
     72 bool GetPrefId(const DictionaryValue& dict, MediaGalleryPrefId* value) {
     73   std::string string_id;
     74   if (!dict.GetString(kMediaGalleriesPrefIdKey, &string_id) ||
     75       !base::StringToUint64(string_id, value)) {
     76     return false;
     77   }
     78 
     79   return true;
     80 }
     81 
     82 bool GetType(const DictionaryValue& dict, MediaGalleryPrefInfo::Type* type) {
     83   std::string string_type;
     84   if (!dict.GetString(kMediaGalleriesTypeKey, &string_type))
     85     return false;
     86 
     87   if (string_type == kMediaGalleriesTypeAutoDetectedValue) {
     88     *type = MediaGalleryPrefInfo::kAutoDetected;
     89     return true;
     90   }
     91   if (string_type == kMediaGalleriesTypeUserAddedValue) {
     92     *type = MediaGalleryPrefInfo::kUserAdded;
     93     return true;
     94   }
     95   if (string_type == kMediaGalleriesTypeBlackListedValue) {
     96     *type = MediaGalleryPrefInfo::kBlackListed;
     97     return true;
     98   }
     99 
    100   return false;
    101 }
    102 
    103 bool PopulateGalleryPrefInfoFromDictionary(
    104     const DictionaryValue& dict, MediaGalleryPrefInfo* out_gallery_info) {
    105   MediaGalleryPrefId pref_id;
    106   string16 display_name;
    107   std::string device_id;
    108   base::FilePath::StringType path;
    109   MediaGalleryPrefInfo::Type type = MediaGalleryPrefInfo::kAutoDetected;
    110   string16 volume_label;
    111   string16 vendor_name;
    112   string16 model_name;
    113   double total_size_in_bytes = 0.0;
    114   double last_attach_time = 0.0;
    115   bool volume_metadata_valid = false;
    116   int prefs_version = 0;
    117 
    118   if (!GetPrefId(dict, &pref_id) ||
    119       !dict.GetString(kMediaGalleriesDeviceIdKey, &device_id) ||
    120       !dict.GetString(kMediaGalleriesPathKey, &path) ||
    121       !GetType(dict, &type)) {
    122     return false;
    123   }
    124 
    125   dict.GetString(kMediaGalleriesDisplayNameKey, &display_name);
    126   dict.GetInteger(kMediaGalleriesPrefsVersionKey, &prefs_version);
    127 
    128   if (dict.GetString(kMediaGalleriesVolumeLabelKey, &volume_label) &&
    129       dict.GetString(kMediaGalleriesVendorNameKey, &vendor_name) &&
    130       dict.GetString(kMediaGalleriesModelNameKey, &model_name) &&
    131       dict.GetDouble(kMediaGalleriesSizeKey, &total_size_in_bytes) &&
    132       dict.GetDouble(kMediaGalleriesLastAttachTimeKey, &last_attach_time)) {
    133     volume_metadata_valid = true;
    134   }
    135 
    136   out_gallery_info->pref_id = pref_id;
    137   out_gallery_info->display_name = display_name;
    138   out_gallery_info->device_id = device_id;
    139   out_gallery_info->path = base::FilePath(path);
    140   out_gallery_info->type = type;
    141   out_gallery_info->volume_label = volume_label;
    142   out_gallery_info->vendor_name = vendor_name;
    143   out_gallery_info->model_name = model_name;
    144   out_gallery_info->total_size_in_bytes = total_size_in_bytes;
    145   out_gallery_info->last_attach_time =
    146       base::Time::FromInternalValue(last_attach_time);
    147   out_gallery_info->volume_metadata_valid = volume_metadata_valid;
    148   out_gallery_info->prefs_version = prefs_version;
    149 
    150   return true;
    151 }
    152 
    153 DictionaryValue* CreateGalleryPrefInfoDictionary(
    154     const MediaGalleryPrefInfo& gallery) {
    155   DictionaryValue* dict = new DictionaryValue();
    156   dict->SetString(kMediaGalleriesPrefIdKey,
    157                   base::Uint64ToString(gallery.pref_id));
    158   if (!gallery.volume_metadata_valid)
    159     dict->SetString(kMediaGalleriesDisplayNameKey, gallery.display_name);
    160   dict->SetString(kMediaGalleriesDeviceIdKey, gallery.device_id);
    161   dict->SetString(kMediaGalleriesPathKey, gallery.path.value());
    162 
    163   const char* type = NULL;
    164   switch (gallery.type) {
    165     case MediaGalleryPrefInfo::kAutoDetected:
    166       type = kMediaGalleriesTypeAutoDetectedValue;
    167       break;
    168     case MediaGalleryPrefInfo::kUserAdded:
    169       type = kMediaGalleriesTypeUserAddedValue;
    170       break;
    171     case MediaGalleryPrefInfo::kBlackListed:
    172       type = kMediaGalleriesTypeBlackListedValue;
    173       break;
    174     default:
    175       NOTREACHED();
    176       break;
    177   }
    178   dict->SetString(kMediaGalleriesTypeKey, type);
    179 
    180   if (gallery.volume_metadata_valid) {
    181     dict->SetString(kMediaGalleriesVolumeLabelKey, gallery.volume_label);
    182     dict->SetString(kMediaGalleriesVendorNameKey, gallery.vendor_name);
    183     dict->SetString(kMediaGalleriesModelNameKey, gallery.model_name);
    184     dict->SetDouble(kMediaGalleriesSizeKey, gallery.total_size_in_bytes);
    185     dict->SetDouble(kMediaGalleriesLastAttachTimeKey,
    186                     gallery.last_attach_time.ToInternalValue());
    187   }
    188 
    189   // Version 0 of the prefs format was that the display_name was always
    190   // used to show the user-visible name of the gallery. Version 1 means
    191   // that there is an optional display_name, and when it is present, it
    192   // overrides the name that would be built from the volume metadata, path,
    193   // or whatever other data. So if we see a display_name with version 0, it
    194   // means it may be overwritten simply by getting new volume metadata.
    195   // A display_name with version 1 should not be overwritten.
    196   dict->SetInteger(kMediaGalleriesPrefsVersionKey, gallery.prefs_version);
    197 
    198   return dict;
    199 }
    200 
    201 bool HasAutoDetectedGalleryPermission(const extensions::Extension& extension) {
    202   extensions::MediaGalleriesPermission::CheckParam param(
    203       extensions::MediaGalleriesPermission::kAllAutoDetectedPermission);
    204   return extensions::PermissionsData::CheckAPIPermissionWithParam(
    205       &extension, extensions::APIPermission::kMediaGalleries, &param);
    206 }
    207 
    208 // Retrieves the MediaGalleryPermission from the given dictionary; DCHECKs on
    209 // failure.
    210 bool GetMediaGalleryPermissionFromDictionary(
    211     const DictionaryValue* dict,
    212     MediaGalleryPermission* out_permission) {
    213   std::string string_id;
    214   if (dict->GetString(kMediaGalleryIdKey, &string_id) &&
    215       base::StringToUint64(string_id, &out_permission->pref_id) &&
    216       dict->GetBoolean(kMediaGalleryHasPermissionKey,
    217                        &out_permission->has_permission)) {
    218     return true;
    219   }
    220   NOTREACHED();
    221   return false;
    222 }
    223 
    224 string16 GetDisplayNameForDevice(uint64 storage_size_in_bytes,
    225                                  const string16& name) {
    226   DCHECK(!name.empty());
    227   return (storage_size_in_bytes == 0) ?
    228       name : ui::FormatBytes(storage_size_in_bytes) + ASCIIToUTF16(" ") + name;
    229 }
    230 
    231 // For a device with |device_name| and a relative path |sub_folder|, construct
    232 // a display name. If |sub_folder| is empty, then just return |device_name|.
    233 string16 GetDisplayNameForSubFolder(const string16& device_name,
    234                                     const base::FilePath& sub_folder) {
    235   if (sub_folder.empty())
    236     return device_name;
    237   return (sub_folder.BaseName().LossyDisplayName() +
    238           ASCIIToUTF16(" - ") +
    239           device_name);
    240 }
    241 
    242 string16 GetFullProductName(const string16& vendor_name,
    243                             const string16& model_name) {
    244   if (vendor_name.empty() && model_name.empty())
    245     return string16();
    246 
    247   string16 product_name;
    248   if (vendor_name.empty())
    249     product_name = model_name;
    250   else if (model_name.empty())
    251     product_name = vendor_name;
    252   else if (!vendor_name.empty() && !model_name.empty())
    253     product_name = vendor_name + UTF8ToUTF16(", ") + model_name;
    254 
    255   return product_name;
    256 }
    257 
    258 }  // namespace
    259 
    260 MediaGalleryPrefInfo::MediaGalleryPrefInfo()
    261     : pref_id(kInvalidMediaGalleryPrefId),
    262       type(kInvalidType),
    263       total_size_in_bytes(0),
    264       volume_metadata_valid(false),
    265       prefs_version(0) {
    266 }
    267 
    268 MediaGalleryPrefInfo::~MediaGalleryPrefInfo() {}
    269 
    270 base::FilePath MediaGalleryPrefInfo::AbsolutePath() const {
    271   base::FilePath base_path = MediaStorageUtil::FindDevicePathById(device_id);
    272   DCHECK(!path.IsAbsolute());
    273   return base_path.empty() ? base_path : base_path.Append(path);
    274 }
    275 
    276 string16 MediaGalleryPrefInfo::GetGalleryDisplayName() const {
    277   if (!StorageInfo::IsRemovableDevice(device_id)) {
    278     // For fixed storage, the name is the directory name, or, in the case
    279     // of a root directory, the root directory name.
    280     // TODO(gbillock): Using only the BaseName can lead to ambiguity. The
    281     // tooltip resolves it. Is that enough?
    282     base::FilePath path = AbsolutePath();
    283     if (!display_name.empty())
    284       return display_name;
    285     if (path == path.DirName())
    286       return path.LossyDisplayName();
    287     return path.BaseName().LossyDisplayName();
    288   }
    289 
    290   string16 name = display_name;
    291   if (name.empty())
    292     name = volume_label;
    293   if (name.empty())
    294     name = GetFullProductName(vendor_name, model_name);
    295   if (name.empty())
    296     name = l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_UNLABELED_DEVICE);
    297 
    298   name = GetDisplayNameForDevice(total_size_in_bytes, name);
    299 
    300   if (!path.empty())
    301     name = GetDisplayNameForSubFolder(name, path);
    302 
    303   return name;
    304 }
    305 
    306 string16 MediaGalleryPrefInfo::GetGalleryTooltip() const {
    307   return AbsolutePath().LossyDisplayName();
    308 }
    309 
    310 string16 MediaGalleryPrefInfo::GetGalleryAdditionalDetails() const {
    311   string16 attached;
    312   if (StorageInfo::IsRemovableDevice(device_id)) {
    313     if (MediaStorageUtil::IsRemovableStorageAttached(device_id)) {
    314       attached = l10n_util::GetStringUTF16(
    315           IDS_MEDIA_GALLERIES_DIALOG_DEVICE_ATTACHED);
    316     } else if (!last_attach_time.is_null()) {
    317       attached = l10n_util::GetStringFUTF16(
    318           IDS_MEDIA_GALLERIES_LAST_ATTACHED,
    319           base::TimeFormatShortDateNumeric(last_attach_time));
    320     } else {
    321       attached = l10n_util::GetStringUTF16(
    322           IDS_MEDIA_GALLERIES_DIALOG_DEVICE_NOT_ATTACHED);
    323     }
    324   }
    325 
    326   return attached;
    327 }
    328 
    329 bool MediaGalleryPrefInfo::IsGalleryAvailable() const {
    330   return !StorageInfo::IsRemovableDevice(device_id) ||
    331          MediaStorageUtil::IsRemovableStorageAttached(device_id);
    332 }
    333 
    334 MediaGalleriesPreferences::GalleryChangeObserver::~GalleryChangeObserver() {}
    335 
    336 MediaGalleriesPreferences::MediaGalleriesPreferences(Profile* profile)
    337     : weak_factory_(this),
    338       profile_(profile),
    339       extension_prefs_for_testing_(NULL) {
    340   AddDefaultGalleriesIfFreshProfile();
    341 
    342   // Look for optional default galleries every time.
    343   itunes::ITunesFinder::FindITunesLibrary(
    344       base::Bind(&MediaGalleriesPreferences::OnITunesDeviceID,
    345                  weak_factory_.GetWeakPtr()));
    346 
    347   // TODO(tommycli): Turn on when Picasa code is ready.
    348 #if 0
    349   picasa::PicasaFinder::FindPicasaDatabaseOnUIThread(
    350       base::Bind(&MediaGalleriesPreferences::OnPicasaDeviceID,
    351                  weak_factory_.GetWeakPtr()));
    352 #endif
    353 
    354   InitFromPrefs(false /*no notification*/);
    355 
    356   StorageMonitor::GetInstance()->AddObserver(this);
    357 }
    358 
    359 MediaGalleriesPreferences::~MediaGalleriesPreferences() {
    360   if (StorageMonitor::GetInstance())
    361     StorageMonitor::GetInstance()->RemoveObserver(this);
    362 }
    363 
    364 void MediaGalleriesPreferences::AddDefaultGalleriesIfFreshProfile() {
    365   // Only add defaults the first time.
    366   if (APIHasBeenUsed(profile_))
    367     return;
    368 
    369   // Fresh profile case.
    370   const int kDirectoryKeys[] = {
    371     DIR_USER_MUSIC,
    372     DIR_USER_PICTURES,
    373     DIR_USER_VIDEOS,
    374   };
    375 
    376   for (size_t i = 0; i < arraysize(kDirectoryKeys); ++i) {
    377     base::FilePath path;
    378     if (!PathService::Get(kDirectoryKeys[i], &path))
    379       continue;
    380 
    381     base::FilePath relative_path;
    382     StorageInfo info;
    383     if (MediaStorageUtil::GetDeviceInfoFromPath(path, &info, &relative_path)) {
    384       AddGalleryInternal(info.device_id(), info.name(), relative_path, false,
    385                          info.storage_label(), info.vendor_name(),
    386                          info.model_name(), info.total_size_in_bytes(),
    387                          base::Time(), true, 2);
    388     }
    389   }
    390 }
    391 
    392 bool MediaGalleriesPreferences::UpdateDeviceIDForSingletonType(
    393     const std::string& device_id) {
    394   StorageInfo::Type singleton_type;
    395   if (!StorageInfo::CrackDeviceId(device_id, &singleton_type, NULL))
    396     return false;
    397 
    398   PrefService* prefs = profile_->GetPrefs();
    399   scoped_ptr<ListPrefUpdate> update(new ListPrefUpdate(
    400       prefs, prefs::kMediaGalleriesRememberedGalleries));
    401   ListValue* list = update->Get();
    402   for (ListValue::iterator iter = list->begin(); iter != list->end(); ++iter) {
    403     // All of these calls should succeed, but preferences file can be corrupt.
    404     DictionaryValue* dict;
    405     if (!(*iter)->GetAsDictionary(&dict))
    406       continue;
    407     std::string this_device_id;
    408     if (!dict->GetString(kMediaGalleriesDeviceIdKey, &this_device_id))
    409       continue;
    410     if (this_device_id == device_id)
    411       return true;  // No update is necessary.
    412     StorageInfo::Type device_type;
    413     if (!StorageInfo::CrackDeviceId(this_device_id, &device_type, NULL))
    414       continue;
    415 
    416     if (device_type == singleton_type) {
    417       dict->SetString(kMediaGalleriesDeviceIdKey, device_id);
    418       update.reset();  // commits the update.
    419       InitFromPrefs(true /* notify observers */);
    420       return true;
    421     }
    422   }
    423   return false;
    424 }
    425 
    426 void MediaGalleriesPreferences::OnITunesDeviceID(const std::string& device_id) {
    427   if (device_id.empty())
    428     return;
    429   if (!UpdateDeviceIDForSingletonType(device_id)) {
    430     AddGalleryInternal(device_id, ASCIIToUTF16(kITunesGalleryName),
    431                        base::FilePath(), false /*not user added*/,
    432                        string16(), string16(), string16(), 0,
    433                        base::Time(), false, 2);
    434   }
    435 }
    436 
    437 void MediaGalleriesPreferences::OnPicasaDeviceID(const std::string& device_id) {
    438   DCHECK(!device_id.empty());
    439   if (!UpdateDeviceIDForSingletonType(device_id)) {
    440     AddGalleryInternal(device_id, ASCIIToUTF16(kPicasaGalleryName),
    441                        base::FilePath(), false /*not user added*/,
    442                        string16(), string16(), string16(), 0,
    443                        base::Time(), false, 2);
    444   }
    445 }
    446 
    447 void MediaGalleriesPreferences::InitFromPrefs(bool notify_observers) {
    448   known_galleries_.clear();
    449   device_map_.clear();
    450 
    451   PrefService* prefs = profile_->GetPrefs();
    452   const ListValue* list = prefs->GetList(
    453       prefs::kMediaGalleriesRememberedGalleries);
    454   if (list) {
    455     for (ListValue::const_iterator it = list->begin();
    456          it != list->end(); ++it) {
    457       const DictionaryValue* dict = NULL;
    458       if (!(*it)->GetAsDictionary(&dict))
    459         continue;
    460 
    461       MediaGalleryPrefInfo gallery_info;
    462       if (!PopulateGalleryPrefInfoFromDictionary(*dict, &gallery_info))
    463         continue;
    464 
    465       known_galleries_[gallery_info.pref_id] = gallery_info;
    466       device_map_[gallery_info.device_id].insert(gallery_info.pref_id);
    467     }
    468   }
    469   if (notify_observers)
    470     NotifyChangeObservers(std::string(), kInvalidMediaGalleryPrefId, false);
    471 }
    472 
    473 void MediaGalleriesPreferences::NotifyChangeObservers(
    474     const std::string& extension_id,
    475     MediaGalleryPrefId pref_id,
    476     bool has_permission) {
    477   FOR_EACH_OBSERVER(GalleryChangeObserver,
    478                     gallery_change_observers_,
    479                     OnGalleryChanged(this, extension_id, pref_id,
    480                                      has_permission));
    481 }
    482 
    483 void MediaGalleriesPreferences::AddGalleryChangeObserver(
    484     GalleryChangeObserver* observer) {
    485   gallery_change_observers_.AddObserver(observer);
    486 }
    487 
    488 void MediaGalleriesPreferences::RemoveGalleryChangeObserver(
    489     GalleryChangeObserver* observer) {
    490   gallery_change_observers_.RemoveObserver(observer);
    491 }
    492 
    493 void MediaGalleriesPreferences::OnRemovableStorageAttached(
    494     const StorageInfo& info) {
    495   if (!StorageInfo::IsMediaDevice(info.device_id()))
    496     return;
    497 
    498   AddGallery(info.device_id(), base::FilePath(),
    499              false /*not user added*/,
    500              info.storage_label(),
    501              info.vendor_name(),
    502              info.model_name(),
    503              info.total_size_in_bytes(),
    504              base::Time::Now());
    505 }
    506 
    507 bool MediaGalleriesPreferences::LookUpGalleryByPath(
    508     const base::FilePath& path,
    509     MediaGalleryPrefInfo* gallery_info) const {
    510   StorageInfo info;
    511   base::FilePath relative_path;
    512   if (!MediaStorageUtil::GetDeviceInfoFromPath(path, &info, &relative_path)) {
    513     if (gallery_info)
    514       *gallery_info = MediaGalleryPrefInfo();
    515     return false;
    516   }
    517 
    518   relative_path = relative_path.NormalizePathSeparators();
    519   MediaGalleryPrefIdSet galleries_on_device =
    520       LookUpGalleriesByDeviceId(info.device_id());
    521   for (MediaGalleryPrefIdSet::const_iterator it = galleries_on_device.begin();
    522        it != galleries_on_device.end();
    523        ++it) {
    524     const MediaGalleryPrefInfo& gallery = known_galleries_.find(*it)->second;
    525     if (gallery.path != relative_path)
    526       continue;
    527 
    528     if (gallery_info)
    529       *gallery_info = gallery;
    530     return true;
    531   }
    532 
    533   // This method is called by controller::FilesSelected when the user
    534   // adds a new gallery. Control reaches here when the selected gallery is
    535   // on a volume we know about, but have no gallery already for. Returns
    536   // hypothetical data to the caller about what the prefs will look like
    537   // if the gallery is added.
    538   // TODO(gbillock): split this out into another function so it doesn't
    539   // conflate LookUp.
    540   if (gallery_info) {
    541     gallery_info->pref_id = kInvalidMediaGalleryPrefId;
    542     gallery_info->device_id = info.device_id();
    543     gallery_info->path = relative_path;
    544     gallery_info->type = MediaGalleryPrefInfo::kUserAdded;
    545     gallery_info->volume_label = info.storage_label();
    546     gallery_info->vendor_name = info.vendor_name();
    547     gallery_info->model_name = info.model_name();
    548     gallery_info->total_size_in_bytes = info.total_size_in_bytes();
    549     gallery_info->last_attach_time = base::Time::Now();
    550     gallery_info->volume_metadata_valid = true;
    551     gallery_info->prefs_version = 2;
    552   }
    553   return false;
    554 }
    555 
    556 MediaGalleryPrefIdSet MediaGalleriesPreferences::LookUpGalleriesByDeviceId(
    557     const std::string& device_id) const {
    558   DeviceIdPrefIdsMap::const_iterator found = device_map_.find(device_id);
    559   if (found == device_map_.end())
    560     return MediaGalleryPrefIdSet();
    561   return found->second;
    562 }
    563 
    564 base::FilePath MediaGalleriesPreferences::LookUpGalleryPathForExtension(
    565     MediaGalleryPrefId gallery_id,
    566     const extensions::Extension* extension,
    567     bool include_unpermitted_galleries) {
    568   DCHECK(extension);
    569   if (!include_unpermitted_galleries &&
    570       !ContainsKey(GalleriesForExtension(*extension), gallery_id))
    571     return base::FilePath();
    572 
    573   MediaGalleriesPrefInfoMap::const_iterator it =
    574       known_galleries_.find(gallery_id);
    575   if (it == known_galleries_.end())
    576     return base::FilePath();
    577   return MediaStorageUtil::FindDevicePathById(it->second.device_id);
    578 }
    579 
    580 MediaGalleryPrefId MediaGalleriesPreferences::AddGallery(
    581     const std::string& device_id,
    582     const base::FilePath& relative_path, bool user_added,
    583     const string16& volume_label, const string16& vendor_name,
    584     const string16& model_name, uint64 total_size_in_bytes,
    585     base::Time last_attach_time) {
    586   return AddGalleryInternal(device_id, string16(), relative_path, user_added,
    587                             volume_label, vendor_name, model_name,
    588                             total_size_in_bytes, last_attach_time, true, 2);
    589 }
    590 
    591 MediaGalleryPrefId MediaGalleriesPreferences::AddGalleryInternal(
    592     const std::string& device_id, const string16& display_name,
    593     const base::FilePath& relative_path, bool user_added,
    594     const string16& volume_label, const string16& vendor_name,
    595     const string16& model_name, uint64 total_size_in_bytes,
    596     base::Time last_attach_time,
    597     bool volume_metadata_valid,
    598     int prefs_version) {
    599   base::FilePath normalized_relative_path =
    600       relative_path.NormalizePathSeparators();
    601   MediaGalleryPrefIdSet galleries_on_device =
    602     LookUpGalleriesByDeviceId(device_id);
    603   for (MediaGalleryPrefIdSet::const_iterator it = galleries_on_device.begin();
    604        it != galleries_on_device.end();
    605        ++it) {
    606     const MediaGalleryPrefInfo& existing = known_galleries_.find(*it)->second;
    607     if (existing.path != normalized_relative_path)
    608       continue;
    609 
    610     bool update_gallery_type =
    611         user_added && (existing.type == MediaGalleryPrefInfo::kBlackListed);
    612     // Status quo: In M27 and M28, galleries added manually use version 0,
    613     // and galleries added automatically (including default galleries) use
    614     // version 1. The name override is used by default galleries as well
    615     // as all device attach events.
    616     // We want to upgrade the name if the existing version is < 2. Leave it
    617     // alone if the existing display name is set with version == 2 and the
    618     // proposed new name is empty.
    619     bool update_gallery_name = existing.display_name != display_name;
    620     if (existing.prefs_version == 2 && !existing.display_name.empty() &&
    621         display_name.empty()) {
    622       update_gallery_name = false;
    623     }
    624     bool update_gallery_metadata = volume_metadata_valid &&
    625         ((existing.volume_label != volume_label) ||
    626          (existing.vendor_name != vendor_name) ||
    627          (existing.model_name != model_name) ||
    628          (existing.total_size_in_bytes != total_size_in_bytes) ||
    629          (existing.last_attach_time != last_attach_time));
    630 
    631     if (!update_gallery_name && !update_gallery_type &&
    632         !update_gallery_metadata)
    633       return *it;
    634 
    635     PrefService* prefs = profile_->GetPrefs();
    636     scoped_ptr<ListPrefUpdate> update(
    637         new ListPrefUpdate(prefs, prefs::kMediaGalleriesRememberedGalleries));
    638     ListValue* list = update->Get();
    639 
    640     for (ListValue::const_iterator list_iter = list->begin();
    641          list_iter != list->end();
    642          ++list_iter) {
    643       DictionaryValue* dict;
    644       MediaGalleryPrefId iter_id;
    645       if ((*list_iter)->GetAsDictionary(&dict) &&
    646           GetPrefId(*dict, &iter_id) &&
    647           *it == iter_id) {
    648         if (update_gallery_type) {
    649           dict->SetString(kMediaGalleriesTypeKey,
    650                           kMediaGalleriesTypeAutoDetectedValue);
    651         }
    652         if (update_gallery_name)
    653           dict->SetString(kMediaGalleriesDisplayNameKey, display_name);
    654         if (update_gallery_metadata) {
    655           dict->SetString(kMediaGalleriesVolumeLabelKey, volume_label);
    656           dict->SetString(kMediaGalleriesVendorNameKey, vendor_name);
    657           dict->SetString(kMediaGalleriesModelNameKey, model_name);
    658           dict->SetDouble(kMediaGalleriesSizeKey, total_size_in_bytes);
    659           dict->SetDouble(kMediaGalleriesLastAttachTimeKey,
    660                           last_attach_time.ToInternalValue());
    661         }
    662         dict->SetInteger(kMediaGalleriesPrefsVersionKey, prefs_version);
    663         break;
    664       }
    665     }
    666 
    667     // Commits the prefs update.
    668     update.reset();
    669 
    670     if (update_gallery_name || update_gallery_metadata || update_gallery_type)
    671       InitFromPrefs(true /* notify observers */);
    672     return *it;
    673   }
    674 
    675   PrefService* prefs = profile_->GetPrefs();
    676 
    677   MediaGalleryPrefInfo gallery_info;
    678   gallery_info.pref_id = prefs->GetUint64(prefs::kMediaGalleriesUniqueId);
    679   prefs->SetUint64(prefs::kMediaGalleriesUniqueId, gallery_info.pref_id + 1);
    680   gallery_info.display_name = display_name;
    681   gallery_info.device_id = device_id;
    682   gallery_info.path = normalized_relative_path;
    683   gallery_info.type = MediaGalleryPrefInfo::kAutoDetected;
    684   if (user_added)
    685     gallery_info.type = MediaGalleryPrefInfo::kUserAdded;
    686   if (volume_metadata_valid) {
    687     gallery_info.volume_label = volume_label;
    688     gallery_info.vendor_name = vendor_name;
    689     gallery_info.model_name = model_name;
    690     gallery_info.total_size_in_bytes = total_size_in_bytes;
    691     gallery_info.last_attach_time = last_attach_time;
    692   }
    693   gallery_info.volume_metadata_valid = volume_metadata_valid;
    694   gallery_info.prefs_version = prefs_version;
    695 
    696   {
    697     ListPrefUpdate update(prefs, prefs::kMediaGalleriesRememberedGalleries);
    698     ListValue* list = update.Get();
    699     list->Append(CreateGalleryPrefInfoDictionary(gallery_info));
    700   }
    701   InitFromPrefs(true /* notify observers */);
    702 
    703   return gallery_info.pref_id;
    704 }
    705 
    706 MediaGalleryPrefId MediaGalleriesPreferences::AddGalleryByPath(
    707     const base::FilePath& path) {
    708   MediaGalleryPrefInfo gallery_info;
    709   if (LookUpGalleryByPath(path, &gallery_info) &&
    710       gallery_info.type != MediaGalleryPrefInfo::kBlackListed) {
    711     return gallery_info.pref_id;
    712   }
    713   return AddGalleryInternal(gallery_info.device_id,
    714                             gallery_info.display_name,
    715                             gallery_info.path,
    716                             true /*user added*/,
    717                             gallery_info.volume_label,
    718                             gallery_info.vendor_name,
    719                             gallery_info.model_name,
    720                             gallery_info.total_size_in_bytes,
    721                             gallery_info.last_attach_time,
    722                             gallery_info.volume_metadata_valid,
    723                             gallery_info.prefs_version);
    724 }
    725 
    726 void MediaGalleriesPreferences::ForgetGalleryById(MediaGalleryPrefId pref_id) {
    727   PrefService* prefs = profile_->GetPrefs();
    728   scoped_ptr<ListPrefUpdate> update(new ListPrefUpdate(
    729       prefs, prefs::kMediaGalleriesRememberedGalleries));
    730   ListValue* list = update->Get();
    731 
    732   if (!ContainsKey(known_galleries_, pref_id))
    733     return;
    734 
    735   for (ListValue::iterator iter = list->begin(); iter != list->end(); ++iter) {
    736     DictionaryValue* dict;
    737     MediaGalleryPrefId iter_id;
    738     if ((*iter)->GetAsDictionary(&dict) && GetPrefId(*dict, &iter_id) &&
    739         pref_id == iter_id) {
    740       RemoveGalleryPermissionsFromPrefs(pref_id);
    741       MediaGalleryPrefInfo::Type type;
    742       if (GetType(*dict, &type) &&
    743           type == MediaGalleryPrefInfo::kAutoDetected) {
    744         dict->SetString(kMediaGalleriesTypeKey,
    745                         kMediaGalleriesTypeBlackListedValue);
    746       } else {
    747         list->Erase(iter, NULL);
    748       }
    749       update.reset(NULL);  // commits the update.
    750 
    751       InitFromPrefs(true /* notify observers */);
    752       return;
    753     }
    754   }
    755 }
    756 
    757 MediaGalleryPrefIdSet MediaGalleriesPreferences::GalleriesForExtension(
    758     const extensions::Extension& extension) const {
    759   MediaGalleryPrefIdSet result;
    760 
    761   if (HasAutoDetectedGalleryPermission(extension)) {
    762     for (MediaGalleriesPrefInfoMap::const_iterator it =
    763              known_galleries_.begin(); it != known_galleries_.end(); ++it) {
    764       if (it->second.type == MediaGalleryPrefInfo::kAutoDetected)
    765         result.insert(it->second.pref_id);
    766     }
    767   }
    768 
    769   std::vector<MediaGalleryPermission> stored_permissions =
    770       GetGalleryPermissionsFromPrefs(extension.id());
    771   for (std::vector<MediaGalleryPermission>::const_iterator it =
    772            stored_permissions.begin(); it != stored_permissions.end(); ++it) {
    773     if (!it->has_permission) {
    774       result.erase(it->pref_id);
    775     } else {
    776       MediaGalleriesPrefInfoMap::const_iterator gallery =
    777           known_galleries_.find(it->pref_id);
    778       DCHECK(gallery != known_galleries_.end());
    779       if (gallery->second.type != MediaGalleryPrefInfo::kBlackListed) {
    780         result.insert(it->pref_id);
    781       } else {
    782         NOTREACHED() << gallery->second.device_id;
    783       }
    784     }
    785   }
    786   return result;
    787 }
    788 
    789 void MediaGalleriesPreferences::SetGalleryPermissionForExtension(
    790     const extensions::Extension& extension,
    791     MediaGalleryPrefId pref_id,
    792     bool has_permission) {
    793   // The gallery may not exist anymore if the user opened a second config
    794   // surface concurrently and removed it. Drop the permission update if so.
    795   MediaGalleriesPrefInfoMap::const_iterator gallery_info =
    796       known_galleries_.find(pref_id);
    797   if (gallery_info == known_galleries_.end())
    798     return;
    799 
    800   bool all_permission = HasAutoDetectedGalleryPermission(extension);
    801   if (has_permission && all_permission) {
    802     if (gallery_info->second.type == MediaGalleryPrefInfo::kAutoDetected) {
    803       UnsetGalleryPermissionInPrefs(extension.id(), pref_id);
    804       NotifyChangeObservers(extension.id(), pref_id, true);
    805       return;
    806     }
    807   }
    808 
    809   if (!has_permission && !all_permission) {
    810     UnsetGalleryPermissionInPrefs(extension.id(), pref_id);
    811   } else {
    812     SetGalleryPermissionInPrefs(extension.id(), pref_id, has_permission);
    813   }
    814   NotifyChangeObservers(extension.id(), pref_id, has_permission);
    815 }
    816 
    817 void MediaGalleriesPreferences::Shutdown() {
    818   weak_factory_.InvalidateWeakPtrs();
    819   profile_ = NULL;
    820 }
    821 
    822 // static
    823 bool MediaGalleriesPreferences::APIHasBeenUsed(Profile* profile) {
    824   MediaGalleryPrefId current_id =
    825       profile->GetPrefs()->GetUint64(prefs::kMediaGalleriesUniqueId);
    826   return current_id != kInvalidMediaGalleryPrefId + 1;
    827 }
    828 
    829 // static
    830 void MediaGalleriesPreferences::RegisterProfilePrefs(
    831     user_prefs::PrefRegistrySyncable* registry) {
    832   registry->RegisterListPref(prefs::kMediaGalleriesRememberedGalleries,
    833                              user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
    834   registry->RegisterUint64Pref(
    835       prefs::kMediaGalleriesUniqueId,
    836       kInvalidMediaGalleryPrefId + 1,
    837       user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
    838 }
    839 
    840 void MediaGalleriesPreferences::SetGalleryPermissionInPrefs(
    841     const std::string& extension_id,
    842     MediaGalleryPrefId gallery_id,
    843     bool has_access) {
    844   ExtensionPrefs::ScopedListUpdate update(GetExtensionPrefs(),
    845                                           extension_id,
    846                                           kMediaGalleriesPermissions);
    847   ListValue* permissions = update.Get();
    848   if (!permissions) {
    849     permissions = update.Create();
    850   } else {
    851     // If the gallery is already in the list, update the permission...
    852     for (ListValue::iterator iter = permissions->begin();
    853          iter != permissions->end(); ++iter) {
    854       DictionaryValue* dict = NULL;
    855       if (!(*iter)->GetAsDictionary(&dict))
    856         continue;
    857       MediaGalleryPermission perm;
    858       if (!GetMediaGalleryPermissionFromDictionary(dict, &perm))
    859         continue;
    860       if (perm.pref_id == gallery_id) {
    861         dict->SetBoolean(kMediaGalleryHasPermissionKey, has_access);
    862         return;
    863       }
    864     }
    865   }
    866   // ...Otherwise, add a new entry for the gallery.
    867   DictionaryValue* dict = new DictionaryValue;
    868   dict->SetString(kMediaGalleryIdKey, base::Uint64ToString(gallery_id));
    869   dict->SetBoolean(kMediaGalleryHasPermissionKey, has_access);
    870   permissions->Append(dict);
    871 }
    872 
    873 void MediaGalleriesPreferences::UnsetGalleryPermissionInPrefs(
    874     const std::string& extension_id,
    875     MediaGalleryPrefId gallery_id) {
    876   ExtensionPrefs::ScopedListUpdate update(GetExtensionPrefs(),
    877                                           extension_id,
    878                                           kMediaGalleriesPermissions);
    879   ListValue* permissions = update.Get();
    880   if (!permissions)
    881     return;
    882 
    883   for (ListValue::iterator iter = permissions->begin();
    884        iter != permissions->end(); ++iter) {
    885     const DictionaryValue* dict = NULL;
    886     if (!(*iter)->GetAsDictionary(&dict))
    887       continue;
    888     MediaGalleryPermission perm;
    889     if (!GetMediaGalleryPermissionFromDictionary(dict, &perm))
    890       continue;
    891     if (perm.pref_id == gallery_id) {
    892       permissions->Erase(iter, NULL);
    893       return;
    894     }
    895   }
    896 }
    897 
    898 std::vector<MediaGalleryPermission>
    899 MediaGalleriesPreferences::GetGalleryPermissionsFromPrefs(
    900     const std::string& extension_id) const {
    901   std::vector<MediaGalleryPermission> result;
    902   const ListValue* permissions;
    903   if (!GetExtensionPrefs()->ReadPrefAsList(extension_id,
    904                                            kMediaGalleriesPermissions,
    905                                            &permissions)) {
    906     return result;
    907   }
    908 
    909   for (ListValue::const_iterator iter = permissions->begin();
    910        iter != permissions->end(); ++iter) {
    911     DictionaryValue* dict = NULL;
    912     if (!(*iter)->GetAsDictionary(&dict))
    913       continue;
    914     MediaGalleryPermission perm;
    915     if (!GetMediaGalleryPermissionFromDictionary(dict, &perm))
    916       continue;
    917     result.push_back(perm);
    918   }
    919 
    920   return result;
    921 }
    922 
    923 void MediaGalleriesPreferences::RemoveGalleryPermissionsFromPrefs(
    924     MediaGalleryPrefId gallery_id) {
    925   ExtensionPrefs* prefs = GetExtensionPrefs();
    926   const DictionaryValue* extensions =
    927       prefs->pref_service()->GetDictionary(ExtensionPrefs::kExtensionsPref);
    928   if (!extensions)
    929     return;
    930 
    931   for (DictionaryValue::Iterator iter(*extensions); !iter.IsAtEnd();
    932        iter.Advance()) {
    933     if (!extensions::Extension::IdIsValid(iter.key())) {
    934       NOTREACHED();
    935       continue;
    936     }
    937     UnsetGalleryPermissionInPrefs(iter.key(), gallery_id);
    938   }
    939 }
    940 
    941 ExtensionPrefs* MediaGalleriesPreferences::GetExtensionPrefs() const {
    942   if (extension_prefs_for_testing_)
    943     return extension_prefs_for_testing_;
    944   return extensions::ExtensionPrefs::Get(profile_);
    945 }
    946 
    947 void MediaGalleriesPreferences::SetExtensionPrefsForTesting(
    948     extensions::ExtensionPrefs* extension_prefs) {
    949   extension_prefs_for_testing_ = extension_prefs;
    950 }
    951 
    952 }  // namespace chrome
    953