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