text: // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "chrome/browser/media_galleries/media_galleries_preferences.h"
  
  #include <stddef.h>
  
  #include "base/base_paths_posix.h"
  #include "base/callback.h"
  #include "base/i18n/time_formatting.h"
  #include "base/macros.h"
  #include "base/path_service.h"
  #include "base/prefs/pref_service.h"
  #include "base/prefs/scoped_user_pref_update.h"
  #include "base/stl_util.h"
  #include "base/strings/string16.h"
  #include "base/strings/string_number_conversions.h"
  #include "base/strings/utf_string_conversions.h"
  #include "base/values.h"
  #include "build/build_config.h"
  #include "chrome/browser/browser_process.h"
  #include "chrome/browser/media_galleries/fileapi/iapps_finder.h"
  #include "chrome/browser/media_galleries/fileapi/picasa_finder.h"
  #include "chrome/browser/media_galleries/imported_media_gallery_registry.h"
  #include "chrome/browser/media_galleries/media_file_system_registry.h"
  #include "chrome/browser/media_galleries/media_galleries_histograms.h"
  #include "chrome/browser/profiles/profile.h"
  #include "chrome/common/chrome_paths.h"
  #include "chrome/common/pref_names.h"
  #include "chrome/grit/generated_resources.h"
  #include "components/crx_file/id_util.h"
  #include "components/pref_registry/pref_registry_syncable.h"
  #include "components/storage_monitor/media_storage_util.h"
  #include "components/storage_monitor/storage_monitor.h"
  #include "content/public/browser/browser_thread.h"
  #include "extensions/browser/extension_prefs.h"
  #include "extensions/browser/extension_registry.h"
  #include "extensions/browser/pref_names.h"
  #include "extensions/common/extension_set.h"
  #include "extensions/common/permissions/api_permission.h"
  #include "extensions/common/permissions/media_galleries_permission.h"
  #include "extensions/common/permissions/permissions_data.h"
  #include "ui/base/l10n/l10n_util.h"
  
  using base::DictionaryValue;
  using base::ListValue;
  using extensions::ExtensionPrefs;
  using storage_monitor::MediaStorageUtil;
  using storage_monitor::StorageInfo;
  using storage_monitor::StorageMonitor;
  
  namespace {
  
  // Pref key for the list of media gallery permissions.
  const char kMediaGalleriesPermissions[] = "media_galleries_permissions";
  // Pref key for Media Gallery ID.
  const char kMediaGalleryIdKey[] = "id";
  // Pref key for Media Gallery Permission Value.
  const char kMediaGalleryHasPermissionKey[] = "has_permission";
  
  const char kMediaGalleriesDeviceIdKey[] = "deviceId";
  const char kMediaGalleriesDisplayNameKey[] = "displayName";
  const char kMediaGalleriesPathKey[] = "path";
  const char kMediaGalleriesPrefIdKey[] = "prefId";
  const char kMediaGalleriesTypeKey[] = "type";
  const char kMediaGalleriesVolumeLabelKey[] = "volumeLabel";
  const char kMediaGalleriesVendorNameKey[] = "vendorName";
  const char kMediaGalleriesModelNameKey[] = "modelName";
  const char kMediaGalleriesSizeKey[] = "totalSize";
  const char kMediaGalleriesLastAttachTimeKey[] = "lastAttachTime";
  const char kMediaGalleriesScanAudioCountKey[] = "audioCount";
  const char kMediaGalleriesScanImageCountKey[] = "imageCount";
  const char kMediaGalleriesScanVideoCountKey[] = "videoCount";
  
  const char kMediaGalleriesTypeAutoDetectedValue[] = "autoDetected";
  const char kMediaGalleriesTypeBlackListedValue[] = "blackListed";
  const char kMediaGalleriesTypeRemovedScanValue[] = "removedScan";
  const char kMediaGalleriesTypeScanResultValue[] = "scanResult";
  const char kMediaGalleriesTypeUserAddedValue[] = "userAdded";
  
  const char kMediaGalleriesDefaultGalleryTypeNotDefaultValue[] = "notDefault";
  const char kMediaGalleriesDefaultGalleryTypeMusicDefaultValue[] = "music";
  const char kMediaGalleriesDefaultGalleryTypePicturesDefaultValue[] = "pictures";
  const char kMediaGalleriesDefaultGalleryTypeVideosDefaultValue[] = "videos";
  
  const char kIPhotoGalleryName[] = "iPhoto";
  const char kITunesGalleryName[] = "iTunes";
  const char kPicasaGalleryName[] = "Picasa";
  
  const int kCurrentPrefsVersion = 3;
  
  int NumberExtensionsUsingMediaGalleries(Profile* profile) {
    int count = 0;
    if (!profile)
      return count;
  
    for (const scoped_refptr<const extensions::Extension>& extension :
         extensions::ExtensionRegistry::Get(profile)->enabled_extensions()) {
      const extensions::PermissionsData* permissions_data =
          extension->permissions_data();
      if (permissions_data->HasAPIPermission(
              extensions::APIPermission::kMediaGalleries)) {
        count++;
      }
    }
    return count;
  }
  
  bool GetPrefId(const base::DictionaryValue& dict, MediaGalleryPrefId* value) {
    std::string string_id;
    if (!dict.GetString(kMediaGalleriesPrefIdKey, &string_id) ||
        !base::StringToUint64(string_id, value)) {
      return false;
    }
  
    return true;
  }
  
  bool GetType(const base::DictionaryValue& dict,
               MediaGalleryPrefInfo::Type* type) {
    std::string string_type;
    if (!dict.GetString(kMediaGalleriesTypeKey, &string_type))
      return false;
  
    if (string_type == kMediaGalleriesTypeUserAddedValue) {
      *type = MediaGalleryPrefInfo::kUserAdded;
      return true;
    }
    if (string_type == kMediaGalleriesTypeAutoDetectedValue) {
      *type = MediaGalleryPrefInfo::kAutoDetected;
      return true;
    }
    if (string_type == kMediaGalleriesTypeBlackListedValue) {
      *type = MediaGalleryPrefInfo::kBlackListed;
      return true;
    }
    if (string_type == kMediaGalleriesTypeScanResultValue) {
      *type = MediaGalleryPrefInfo::kScanResult;
      return true;
    }
    if (string_type == kMediaGalleriesTypeRemovedScanValue) {
      *type = MediaGalleryPrefInfo::kRemovedScan;
      return true;
    }
  
    return false;
  }
  
  const char* TypeToStringValue(MediaGalleryPrefInfo::Type type) {
    const char* result = NULL;
    switch (type) {
      case MediaGalleryPrefInfo::kUserAdded:
        result = kMediaGalleriesTypeUserAddedValue;
        break;
      case MediaGalleryPrefInfo::kAutoDetected:
        result = kMediaGalleriesTypeAutoDetectedValue;
        break;
      case MediaGalleryPrefInfo::kBlackListed:
        result = kMediaGalleriesTypeBlackListedValue;
        break;
      case MediaGalleryPrefInfo::kScanResult:
        result = kMediaGalleriesTypeScanResultValue;
        break;
      case MediaGalleryPrefInfo::kRemovedScan:
        result = kMediaGalleriesTypeRemovedScanValue;
        break;
      default:
        NOTREACHED();
        break;
    }
    return result;
  }
  
  MediaGalleryPrefInfo::DefaultGalleryType GetDefaultGalleryType(
      const base::DictionaryValue& dict) {
    std::string default_gallery_type_string;
    if (!dict.GetString(
            kMediaGalleriesDefaultGalleryTypeKey, &default_gallery_type_string))
      return MediaGalleryPrefInfo::kNotDefault;
  
    if (default_gallery_type_string ==
        kMediaGalleriesDefaultGalleryTypeMusicDefaultValue) {
      return MediaGalleryPrefInfo::kMusicDefault;
    }
    if (default_gallery_type_string ==
        kMediaGalleriesDefaultGalleryTypePicturesDefaultValue) {
      return MediaGalleryPrefInfo::kPicturesDefault;
    }
    if (default_gallery_type_string ==
        kMediaGalleriesDefaultGalleryTypeVideosDefaultValue) {
      return MediaGalleryPrefInfo::kVideosDefault;
    }
    return MediaGalleryPrefInfo::kNotDefault;
  }
  
  const char* DefaultGalleryTypeToStringValue(
      MediaGalleryPrefInfo::DefaultGalleryType default_gallery_type) {
    const char* result = NULL;
    switch (default_gallery_type) {
      case MediaGalleryPrefInfo::kNotDefault:
        result = kMediaGalleriesDefaultGalleryTypeNotDefaultValue;
        break;
      case MediaGalleryPrefInfo::kMusicDefault:
        result = kMediaGalleriesDefaultGalleryTypeMusicDefaultValue;
        break;
      case MediaGalleryPrefInfo::kPicturesDefault:
        result = kMediaGalleriesDefaultGalleryTypePicturesDefaultValue;
        break;
      case MediaGalleryPrefInfo::kVideosDefault:
        result = kMediaGalleriesDefaultGalleryTypeVideosDefaultValue;
        break;
      default:
        NOTREACHED();
        break;
    }
    return result;
  }
  
  bool PopulateGalleryPrefInfoFromDictionary(
      const base::DictionaryValue& dict, MediaGalleryPrefInfo* out_gallery_info) {
    MediaGalleryPrefId pref_id;
    base::string16 display_name;
    std::string device_id;
    base::FilePath::StringType path;
    MediaGalleryPrefInfo::Type type = MediaGalleryPrefInfo::kInvalidType;
    base::string16 volume_label;
    base::string16 vendor_name;
    base::string16 model_name;
    double total_size_in_bytes = 0.0;
    double last_attach_time = 0.0;
    bool volume_metadata_valid = false;
    int audio_count = 0;
    int image_count = 0;
    int video_count = 0;
    int prefs_version = 0;
  
    if (!GetPrefId(dict, &pref_id) ||
        !dict.GetString(kMediaGalleriesDeviceIdKey, &device_id) ||
        !dict.GetString(kMediaGalleriesPathKey, &path) ||
        !GetType(dict, &type)) {
      return false;
    }
  
    dict.GetString(kMediaGalleriesDisplayNameKey, &display_name);
    dict.GetInteger(kMediaGalleriesPrefsVersionKey, &prefs_version);
  
    if (dict.GetString(kMediaGalleriesVolumeLabelKey, &volume_label) &&
        dict.GetString(kMediaGalleriesVendorNameKey, &vendor_name) &&
        dict.GetString(kMediaGalleriesModelNameKey, &model_name) &&
        dict.GetDouble(kMediaGalleriesSizeKey, &total_size_in_bytes) &&
        dict.GetDouble(kMediaGalleriesLastAttachTimeKey, &last_attach_time)) {
      volume_metadata_valid = true;
    }
  
    if (dict.GetInteger(kMediaGalleriesScanAudioCountKey, &audio_count) &&
        dict.GetInteger(kMediaGalleriesScanImageCountKey, &image_count) &&
        dict.GetInteger(kMediaGalleriesScanVideoCountKey, &video_count)) {
      out_gallery_info->audio_count = audio_count;
      out_gallery_info->image_count = image_count;
      out_gallery_info->video_count = video_count;
    } else {
      out_gallery_info->audio_count = 0;
      out_gallery_info->image_count = 0;
      out_gallery_info->video_count = 0;
    }
  
    out_gallery_info->pref_id = pref_id;
    out_gallery_info->display_name = display_name;
    out_gallery_info->device_id = device_id;
    out_gallery_info->path = base::FilePath(path);
    out_gallery_info->type = type;
    out_gallery_info->volume_label = volume_label;
    out_gallery_info->vendor_name = vendor_name;
    out_gallery_info->model_name = model_name;
    out_gallery_info->total_size_in_bytes = total_size_in_bytes;
    out_gallery_info->last_attach_time =
        base::Time::FromInternalValue(last_attach_time);
    out_gallery_info->volume_metadata_valid = volume_metadata_valid;
    out_gallery_info->prefs_version = prefs_version;
    out_gallery_info->default_gallery_type = GetDefaultGalleryType(dict);
    return true;
  }
  
  base::DictionaryValue* CreateGalleryPrefInfoDictionary(
      const MediaGalleryPrefInfo& gallery) {
    base::DictionaryValue* dict = new base::DictionaryValue();
    dict->SetString(kMediaGalleriesPrefIdKey,
                    base::Uint64ToString(gallery.pref_id));
    dict->SetString(kMediaGalleriesDeviceIdKey, gallery.device_id);
    dict->SetString(kMediaGalleriesPathKey, gallery.path.value());
    dict->SetString(kMediaGalleriesTypeKey, TypeToStringValue(gallery.type));
  
    if (gallery.default_gallery_type != MediaGalleryPrefInfo::kNotDefault) {
      dict->SetString(kMediaGalleriesDefaultGalleryTypeKey,
                      DefaultGalleryTypeToStringValue(
                          gallery.default_gallery_type));
    }
  
    if (gallery.volume_metadata_valid) {
      dict->SetString(kMediaGalleriesVolumeLabelKey, gallery.volume_label);
      dict->SetString(kMediaGalleriesVendorNameKey, gallery.vendor_name);
      dict->SetString(kMediaGalleriesModelNameKey, gallery.model_name);
      dict->SetDouble(kMediaGalleriesSizeKey, gallery.total_size_in_bytes);
      dict->SetDouble(kMediaGalleriesLastAttachTimeKey,
                      gallery.last_attach_time.ToInternalValue());
    } else {
      dict->SetString(kMediaGalleriesDisplayNameKey, gallery.display_name);
    }
  
    if (gallery.audio_count || gallery.image_count || gallery.video_count) {
      dict->SetInteger(kMediaGalleriesScanAudioCountKey, gallery.audio_count);
      dict->SetInteger(kMediaGalleriesScanImageCountKey, gallery.image_count);
      dict->SetInteger(kMediaGalleriesScanVideoCountKey, gallery.video_count);
    }
  
    // Version 0 of the prefs format was that the display_name was always
    // used to show the user-visible name of the gallery. Version 1 means
    // that there is an optional display_name, and when it is present, it
    // overrides the name that would be built from the volume metadata, path,
    // or whatever other data. So if we see a display_name with version 0, it
    // means it may be overwritten simply by getting new volume metadata.
    // A display_name with version 1 should not be overwritten.
    dict->SetInteger(kMediaGalleriesPrefsVersionKey, gallery.prefs_version);
  
    return dict;
  }
  
  bool HasAutoDetectedGalleryPermission(const extensions::Extension& extension) {
    extensions::MediaGalleriesPermission::CheckParam param(
        extensions::MediaGalleriesPermission::kAllAutoDetectedPermission);
    return extension.permissions_data()->CheckAPIPermissionWithParam(
        extensions::APIPermission::kMediaGalleries, &param);
  }
  
  // Retrieves the MediaGalleryPermission from the given dictionary; DCHECKs on
  // failure.
  bool GetMediaGalleryPermissionFromDictionary(
      const base::DictionaryValue* dict,
      MediaGalleryPermission* out_permission) {
    std::string string_id;
    if (dict->GetString(kMediaGalleryIdKey, &string_id) &&
        base::StringToUint64(string_id, &out_permission->pref_id) &&
        dict->GetBoolean(kMediaGalleryHasPermissionKey,
                         &out_permission->has_permission)) {
      return true;
    }
    NOTREACHED();
    return false;
  }
  
  // For a device with |device_name| and a relative path |sub_folder|, construct
  // a display name. If |sub_folder| is empty, then just return |device_name|.
  base::string16 GetDisplayNameForSubFolder(const base::string16& device_name,
                                            const base::FilePath& sub_folder) {
    if (sub_folder.empty())
      return device_name;
    return (sub_folder.BaseName().LossyDisplayName() +
            base::ASCIIToUTF16(" - ") +
            device_name);
  }
  
  void InitializeImportedMediaGalleryRegistryOnFileThread() {
    DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
    ImportedMediaGalleryRegistry::GetInstance()->Initialize();
  }
  
  }  // namespace
  
  MediaGalleryPrefInfo::MediaGalleryPrefInfo()
      : pref_id(kInvalidMediaGalleryPrefId),
        type(kInvalidType),
        total_size_in_bytes(0),
        volume_metadata_valid(false),
        audio_count(0),
        image_count(0),
        video_count(0),
        default_gallery_type(kNotDefault),
        prefs_version(0) {
  }
  
  MediaGalleryPrefInfo::~MediaGalleryPrefInfo() {}
  
  base::FilePath MediaGalleryPrefInfo::AbsolutePath() const {
    base::FilePath base_path = MediaStorageUtil::FindDevicePathById(device_id);
    DCHECK(!path.IsAbsolute());
    return base_path.empty() ? base_path : base_path.Append(path);
  }
  
  bool MediaGalleryPrefInfo::IsBlackListedType() const {
    return type == kBlackListed || type == kRemovedScan;
  }
  
  base::string16 MediaGalleryPrefInfo::GetGalleryDisplayName() const {
    if (!StorageInfo::IsRemovableDevice(device_id)) {
      // For fixed storage, the default name is the fully qualified directory
      // name, or in the case of a root directory, the root directory name.
      // Exception: ChromeOS -- the full pathname isn't visible there, so only
      // the directory name is used.
      base::FilePath path = AbsolutePath();
      if (!display_name.empty())
        return display_name;
  
  #if defined(OS_CHROMEOS)
      // See chrome/browser/chromeos/fileapi/file_system_backend.cc
      base::FilePath download_path;
      if (PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS_SAFE, &download_path)) {
        base::FilePath relative;
        if (download_path.AppendRelativePath(path, &relative))
          return relative.LossyDisplayName();
      }
      return path.BaseName().LossyDisplayName();
  #else
      return path.LossyDisplayName();
  #endif
    }
  
    StorageInfo info(device_id,
                     MediaStorageUtil::FindDevicePathById(device_id).value(),
                     volume_label, vendor_name, model_name, total_size_in_bytes);
    base::string16 name = info.GetDisplayNameWithOverride(display_name, true);
    if (!path.empty())
      name = GetDisplayNameForSubFolder(name, path);
    return name;
  }
  
  base::string16 MediaGalleryPrefInfo::GetGalleryTooltip() const {
    return AbsolutePath().LossyDisplayName();
  }
  
  base::string16 MediaGalleryPrefInfo::GetGalleryAdditionalDetails() const {
    base::string16 attached;
    if (StorageInfo::IsRemovableDevice(device_id)) {
      if (MediaStorageUtil::IsRemovableStorageAttached(device_id)) {
        attached = l10n_util::GetStringUTF16(
            IDS_MEDIA_GALLERIES_DIALOG_DEVICE_ATTACHED);
      } else if (!last_attach_time.is_null()) {
        attached = l10n_util::GetStringFUTF16(
            IDS_MEDIA_GALLERIES_LAST_ATTACHED,
            base::TimeFormatShortDateNumeric(last_attach_time));
      } else {
        attached = l10n_util::GetStringUTF16(
            IDS_MEDIA_GALLERIES_DIALOG_DEVICE_NOT_ATTACHED);
      }
    }
  
    return attached;
  }
  
  bool MediaGalleryPrefInfo::IsGalleryAvailable() const {
    return !StorageInfo::IsRemovableDevice(device_id) ||
           MediaStorageUtil::IsRemovableStorageAttached(device_id);
  }
  
  MediaGalleriesPreferences::GalleryChangeObserver::~GalleryChangeObserver() {}
  
  MediaGalleriesPreferences::MediaGalleriesPreferences(Profile* profile)
      : initialized_(false),
        pre_initialization_callbacks_waiting_(0),
        profile_(profile),
        extension_prefs_for_testing_(NULL),
        weak_factory_(this) {
  }
  
  MediaGalleriesPreferences::~MediaGalleriesPreferences() {
    if (StorageMonitor::GetInstance())
      StorageMonitor::GetInstance()->RemoveObserver(this);
  }
  
  void MediaGalleriesPreferences::EnsureInitialized(base::Closure callback) {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  
    if (IsInitialized()) {
      if (!callback.is_null())
        callback.Run();
      return;
    }
  
    on_initialize_callbacks_.push_back(callback);
    if (on_initialize_callbacks_.size() > 1)
      return;
  
    // This counter must match the number of async methods dispatched below.
    // It cannot be incremented inline with each callback, as some may return
    // synchronously, decrement the counter to 0, and prematurely trigger
    // FinishInitialization.
    pre_initialization_callbacks_waiting_ = 4;
  
    // Check whether we should be initializing -- are there any extensions that
    // are using media galleries?
    media_galleries::UsageCount(media_galleries::PREFS_INITIALIZED);
    if (NumberExtensionsUsingMediaGalleries(profile_) == 0) {
      media_galleries::UsageCount(media_galleries::PREFS_INITIALIZED_ERROR);
    }
  
    // We determine the freshness of the profile here, before any of the finders
    // return and add media galleries to it (hence why the APIHasBeenUsed check
    // needs to happen here rather than inside OnStorageMonitorInit itself).
    StorageMonitor::GetInstance()->EnsureInitialized(
        base::Bind(&MediaGalleriesPreferences::OnStorageMonitorInit,
                   weak_factory_.GetWeakPtr(),
                   APIHasBeenUsed(profile_)));
  
    // Look for optional default galleries every time.
    iapps::FindITunesLibrary(
        base::Bind(&MediaGalleriesPreferences::OnFinderDeviceID,
                   weak_factory_.GetWeakPtr()));
  
    picasa::FindPicasaDatabase(
        base::Bind(&MediaGalleriesPreferences::OnFinderDeviceID,
                   weak_factory_.GetWeakPtr()));
  
    iapps::FindIPhotoLibrary(
        base::Bind(&MediaGalleriesPreferences::OnFinderDeviceID,
                   weak_factory_.GetWeakPtr()));
  }
  
  bool MediaGalleriesPreferences::IsInitialized() const { return initialized_; }
  
  Profile* MediaGalleriesPreferences::profile() { return profile_; }
  
  void MediaGalleriesPreferences::OnInitializationCallbackReturned() {
    DCHECK(!IsInitialized());
    DCHECK_GT(pre_initialization_callbacks_waiting_, 0);
    if (--pre_initialization_callbacks_waiting_ == 0)
      FinishInitialization();
  }
  
  void MediaGalleriesPreferences::FinishInitialization() {
    DCHECK(!IsInitialized());
  
    initialized_ = true;
  
    StorageMonitor* monitor = StorageMonitor::GetInstance();
    DCHECK(monitor->IsInitialized());
  
    InitFromPrefs();
  
    StorageMonitor::GetInstance()->AddObserver(this);
  
    std::vector<StorageInfo> existing_devices =
        monitor->GetAllAvailableStorages();
    for (size_t i = 0; i < existing_devices.size(); i++) {
      if (!(StorageInfo::IsMediaDevice(existing_devices[i].device_id()) &&
            StorageInfo::IsRemovableDevice(existing_devices[i].device_id())))
        continue;
      AddGallery(existing_devices[i].device_id(),
                 base::FilePath(),
                 MediaGalleryPrefInfo::kAutoDetected,
                 existing_devices[i].storage_label(),
                 existing_devices[i].vendor_name(),
                 existing_devices[i].model_name(),
                 existing_devices[i].total_size_in_bytes(),
                 base::Time::Now(), 0, 0, 0);
    }
  
    for (std::vector<base::Closure>::iterator iter =
             on_initialize_callbacks_.begin();
         iter != on_initialize_callbacks_.end();
         ++iter) {
      iter->Run();
    }
    on_initialize_callbacks_.clear();
  }
  
  void MediaGalleriesPreferences::AddDefaultGalleries() {
    const struct DefaultTypes {
      int directory_key;
      MediaGalleryPrefInfo::DefaultGalleryType default_gallery_type;
    } kDirectories[] = {
      {chrome::DIR_USER_MUSIC, MediaGalleryPrefInfo::kMusicDefault},
      {chrome::DIR_USER_PICTURES, MediaGalleryPrefInfo::kPicturesDefault},
      {chrome::DIR_USER_VIDEOS, MediaGalleryPrefInfo::kVideosDefault},
    };
  
    for (size_t i = 0; i < arraysize(kDirectories); ++i) {
      base::FilePath path;
      if (!PathService::Get(kDirectories[i].directory_key, &path))
        continue;
  
      base::FilePath relative_path;
      StorageInfo info;
      if (MediaStorageUtil::GetDeviceInfoFromPath(path, &info, &relative_path)) {
        MediaGalleryPrefInfo::DefaultGalleryType default_gallery_type =
            kDirectories[i].default_gallery_type;
        DCHECK_NE(default_gallery_type, MediaGalleryPrefInfo::kNotDefault);
  
        AddOrUpdateGalleryInternal(
            info.device_id(),
            base::string16(),
            relative_path,
            MediaGalleryPrefInfo::kAutoDetected,
            info.storage_label(),
            info.vendor_name(),
            info.model_name(),
            info.total_size_in_bytes(),
            base::Time(),
            true,
            0,
            0,
            0,
            kCurrentPrefsVersion,
            default_gallery_type);
      }
    }
  }
  
  bool MediaGalleriesPreferences::UpdateDeviceIDForSingletonType(
      const std::string& device_id) {
    StorageInfo::Type singleton_type;
    if (!StorageInfo::CrackDeviceId(device_id, &singleton_type, NULL))
      return false;
  
    PrefService* prefs = profile_->GetPrefs();
    scoped_ptr<ListPrefUpdate> update(new ListPrefUpdate(
        prefs, prefs::kMediaGalleriesRememberedGalleries));
    base::ListValue* list = update->Get();
    for (base::ListValue::iterator iter = list->begin();
         iter != list->end(); ++iter) {
      // All of these calls should succeed, but preferences file can be corrupt.
      base::DictionaryValue* dict;
      if (!(*iter)->GetAsDictionary(&dict))
        continue;
      std::string this_device_id;
      if (!dict->GetString(kMediaGalleriesDeviceIdKey, &this_device_id))
        continue;
      if (this_device_id == device_id)
        return true;  // No update is necessary.
      StorageInfo::Type device_type;
      if (!StorageInfo::CrackDeviceId(this_device_id, &device_type, NULL))
        continue;
  
      if (device_type == singleton_type) {
        dict->SetString(kMediaGalleriesDeviceIdKey, device_id);
        update.reset();  // commits the update.
        InitFromPrefs();
        MediaGalleryPrefId pref_id;
        if (GetPrefId(*dict, &pref_id)) {
          FOR_EACH_OBSERVER(GalleryChangeObserver,
                            gallery_change_observers_,
                            OnGalleryInfoUpdated(this, pref_id));
        }
        return true;
      }
    }
    return false;
  }
  
  void MediaGalleriesPreferences::OnStorageMonitorInit(
      bool api_has_been_used) {
    if (api_has_been_used)
      UpdateDefaultGalleriesPaths();
  
    // Invoke this method even if the API has been used before, in order to ensure
    // we upgrade (migrate) prefs for galleries with prefs version prior to 3.
    AddDefaultGalleries();
  
    OnInitializationCallbackReturned();
  }
  
  void MediaGalleriesPreferences::OnFinderDeviceID(const std::string& device_id) {
    if (!device_id.empty()) {
      std::string gallery_name;
      if (StorageInfo::IsIPhotoDevice(device_id))
        gallery_name = kIPhotoGalleryName;
      else if (StorageInfo::IsITunesDevice(device_id))
        gallery_name = kITunesGalleryName;
      else if (StorageInfo::IsPicasaDevice(device_id))
        gallery_name = kPicasaGalleryName;
  
      if (!gallery_name.empty()) {
        pre_initialization_callbacks_waiting_++;
        content::BrowserThread::PostTaskAndReply(
            content::BrowserThread::FILE,
            FROM_HERE,
            base::Bind(&InitializeImportedMediaGalleryRegistryOnFileThread),
            base::Bind(
                &MediaGalleriesPreferences::OnInitializationCallbackReturned,
                weak_factory_.GetWeakPtr()));
      }
  
      if (!UpdateDeviceIDForSingletonType(device_id)) {
        DCHECK(!gallery_name.empty());
        AddOrUpdateGalleryInternal(
            device_id,
            base::ASCIIToUTF16(gallery_name),
            base::FilePath(),
            MediaGalleryPrefInfo::kAutoDetected,
            base::string16(),
            base::string16(),
            base::string16(),
            0,
            base::Time(),
            false,
            0,
            0,
            0,
            kCurrentPrefsVersion,
            MediaGalleryPrefInfo::kNotDefault);
      }
    }
  
    OnInitializationCallbackReturned();
  }
  
  void MediaGalleriesPreferences::InitFromPrefs() {
    known_galleries_.clear();
    device_map_.clear();
  
    PrefService* prefs = profile_->GetPrefs();
    const base::ListValue* list = prefs->GetList(
        prefs::kMediaGalleriesRememberedGalleries);
    if (list) {
      for (base::ListValue::const_iterator it = list->begin();
           it != list->end(); ++it) {
        const base::DictionaryValue* dict = NULL;
        if (!(*it)->GetAsDictionary(&dict))
          continue;
  
        MediaGalleryPrefInfo gallery_info;
        if (!PopulateGalleryPrefInfoFromDictionary(*dict, &gallery_info))
          continue;
  
        known_galleries_[gallery_info.pref_id] = gallery_info;
        device_map_[gallery_info.device_id].insert(gallery_info.pref_id);
      }
    }
  }
  
  void MediaGalleriesPreferences::AddGalleryChangeObserver(
      GalleryChangeObserver* observer) {
    DCHECK(IsInitialized());
    gallery_change_observers_.AddObserver(observer);
  }
  
  void MediaGalleriesPreferences::RemoveGalleryChangeObserver(
      GalleryChangeObserver* observer) {
    DCHECK(IsInitialized());
    gallery_change_observers_.RemoveObserver(observer);
  }
  
  void MediaGalleriesPreferences::OnRemovableStorageAttached(
      const StorageInfo& info) {
    DCHECK(IsInitialized());
    if (!StorageInfo::IsMediaDevice(info.device_id()))
      return;
  
    AddGallery(info.device_id(), base::FilePath(),
               MediaGalleryPrefInfo::kAutoDetected, info.storage_label(),
               info.vendor_name(), info.model_name(), info.total_size_in_bytes(),
               base::Time::Now(), 0, 0, 0);
  }
  
  bool MediaGalleriesPreferences::LookUpGalleryByPath(
      const base::FilePath& path,
      MediaGalleryPrefInfo* gallery_info) const {
    DCHECK(IsInitialized());
  
    // First check if the path matches an imported gallery.
    for (MediaGalleriesPrefInfoMap::const_iterator it =
             known_galleries_.begin(); it != known_galleries_.end(); ++it) {
      const std::string& device_id = it->second.device_id;
      if (iapps::PathIndicatesIPhotoLibrary(device_id, path) ||
          iapps::PathIndicatesITunesLibrary(device_id, path)) {
        *gallery_info = it->second;
        return true;
      }
    }
  
    StorageInfo info;
    base::FilePath relative_path;
    if (!MediaStorageUtil::GetDeviceInfoFromPath(path, &info, &relative_path)) {
      if (gallery_info)
        *gallery_info = MediaGalleryPrefInfo();
      return false;
    }
  
    relative_path = relative_path.NormalizePathSeparators();
    MediaGalleryPrefIdSet galleries_on_device =
        LookUpGalleriesByDeviceId(info.device_id());
    for (MediaGalleryPrefIdSet::const_iterator it = galleries_on_device.begin();
         it != galleries_on_device.end();
         ++it) {
      const MediaGalleryPrefInfo& gallery = known_galleries_.find(*it)->second;
      if (gallery.path != relative_path)
        continue;
  
      if (gallery_info)
        *gallery_info = gallery;
      return true;
    }
  
    // This method is called by controller::FilesSelected when the user
    // adds a new gallery. Control reaches here when the selected gallery is
    // on a volume we know about, but have no gallery already for. Returns
    // hypothetical data to the caller about what the prefs will look like
    // if the gallery is added.
    // TODO(gbillock): split this out into another function so it doesn't
    // conflate LookUp.
    if (gallery_info) {
      gallery_info->pref_id = kInvalidMediaGalleryPrefId;
      gallery_info->device_id = info.device_id();
      gallery_info->path = relative_path;
      gallery_info->type = MediaGalleryPrefInfo::kInvalidType;
      gallery_info->volume_label = info.storage_label();
      gallery_info->vendor_name = info.vendor_name();
      gallery_info->model_name = info.model_name();
      gallery_info->total_size_in_bytes = info.total_size_in_bytes();
      gallery_info->last_attach_time = base::Time::Now();
      gallery_info->volume_metadata_valid = true;
      gallery_info->prefs_version = kCurrentPrefsVersion;
    }
    return false;
  }
  
  MediaGalleryPrefIdSet MediaGalleriesPreferences::LookUpGalleriesByDeviceId(
      const std::string& device_id) const {
    DeviceIdPrefIdsMap::const_iterator found = device_map_.find(device_id);
    if (found == device_map_.end())
      return MediaGalleryPrefIdSet();
    return found->second;
  }
  
  base::FilePath MediaGalleriesPreferences::LookUpGalleryPathForExtension(
      MediaGalleryPrefId gallery_id,
      const extensions::Extension* extension,
      bool include_unpermitted_galleries) {
    DCHECK(IsInitialized());
    DCHECK(extension);
    if (!include_unpermitted_galleries &&
        !ContainsKey(GalleriesForExtension(*extension), gallery_id))
      return base::FilePath();
  
    MediaGalleriesPrefInfoMap::const_iterator it =
        known_galleries_.find(gallery_id);
    if (it == known_galleries_.end())
      return base::FilePath();
    return MediaStorageUtil::FindDevicePathById(it->second.device_id);
  }
  
  MediaGalleryPrefId MediaGalleriesPreferences::AddGallery(
      const std::string& device_id,
      const base::FilePath& relative_path,
      MediaGalleryPrefInfo::Type type,
      const base::string16& volume_label,
      const base::string16& vendor_name,
      const base::string16& model_name,
      uint64_t total_size_in_bytes,
      base::Time last_attach_time,
      int audio_count,
      int image_count,
      int video_count) {
    DCHECK(IsInitialized());
    return AddOrUpdateGalleryInternal(
        device_id,
        base::string16(),
        relative_path,
        type,
        volume_label,
        vendor_name,
        model_name,
        total_size_in_bytes,
        last_attach_time,
        true,
        audio_count,
        image_count,
        video_count,
        kCurrentPrefsVersion,
        MediaGalleryPrefInfo::kNotDefault);
  }
  
  MediaGalleryPrefId MediaGalleriesPreferences::AddOrUpdateGalleryInternal(
      const std::string& device_id,
      const base::string16& display_name,
      const base::FilePath& relative_path,
      MediaGalleryPrefInfo::Type type,
      const base::string16& volume_label,
      const base::string16& vendor_name,
      const base::string16& model_name,
      uint64_t total_size_in_bytes,
      base::Time last_attach_time,
      bool volume_metadata_valid,
      int audio_count,
      int image_count,
      int video_count,
      int prefs_version,
      MediaGalleryPrefInfo::DefaultGalleryType default_gallery_type) {
    DCHECK(type == MediaGalleryPrefInfo::kUserAdded ||
           type == MediaGalleryPrefInfo::kAutoDetected ||
           type == MediaGalleryPrefInfo::kScanResult);
    base::FilePath normalized_relative_path =
        relative_path.NormalizePathSeparators();
    MediaGalleryPrefIdSet galleries_on_device =
      LookUpGalleriesByDeviceId(device_id);
  
    for (MediaGalleryPrefIdSet::const_iterator pref_id_it =
             galleries_on_device.begin();
         pref_id_it != galleries_on_device.end();
         ++pref_id_it) {
      const MediaGalleryPrefInfo& existing =
          known_galleries_.find(*pref_id_it)->second;
      if (existing.path != normalized_relative_path)
        continue;
  
      bool update_gallery_type = false;
      MediaGalleryPrefInfo::Type new_type = existing.type;
      if (type == MediaGalleryPrefInfo::kUserAdded) {
        if (existing.type == MediaGalleryPrefInfo::kBlackListed) {
          new_type = MediaGalleryPrefInfo::kAutoDetected;
          update_gallery_type = true;
        }
        if (existing.type == MediaGalleryPrefInfo::kRemovedScan) {
          new_type = MediaGalleryPrefInfo::kUserAdded;
          update_gallery_type = true;
        }
      }
  
      // Status quo: In M27 and M28, galleries added manually use version 0,
      // and galleries added automatically (including default galleries) use
      // version 1. The name override is used by default galleries as well
      // as all device attach events.
      // We want to upgrade the name if the existing version is < 2. Leave it
      // alone if the existing display name is set with version >= 2 and the
      // proposed new name is empty.
      bool update_gallery_name = existing.display_name != display_name;
      if (existing.prefs_version >= 2 && !existing.display_name.empty() &&
          display_name.empty()) {
        update_gallery_name = false;
      }
  
      // Version 3 adds the default_gallery_type field.
      bool update_default_gallery_type =
           existing.prefs_version <= 2 &&
           default_gallery_type != existing.default_gallery_type;
  
      bool update_gallery_metadata = volume_metadata_valid &&
          ((existing.volume_label != volume_label) ||
           (existing.vendor_name != vendor_name) ||
           (existing.model_name != model_name) ||
           (existing.total_size_in_bytes != total_size_in_bytes) ||
           (existing.last_attach_time != last_attach_time));
  
      bool update_scan_counts =
        new_type != MediaGalleryPrefInfo::kRemovedScan &&
        new_type != MediaGalleryPrefInfo::kBlackListed &&
        (audio_count > 0 || image_count > 0 || video_count > 0 ||
         existing.audio_count || existing.image_count || existing.video_count);
  
      if (!update_gallery_name && !update_gallery_type &&
          !update_gallery_metadata && !update_scan_counts &&
          !update_default_gallery_type)
        return *pref_id_it;
  
      PrefService* prefs = profile_->GetPrefs();
      scoped_ptr<ListPrefUpdate> update(
          new ListPrefUpdate(prefs, prefs::kMediaGalleriesRememberedGalleries));
      base::ListValue* list = update->Get();
  
      for (base::ListValue::const_iterator list_iter = list->begin();
           list_iter != list->end();
           ++list_iter) {
        base::DictionaryValue* dict;
        MediaGalleryPrefId iter_id;
        if ((*list_iter)->GetAsDictionary(&dict) &&
            GetPrefId(*dict, &iter_id) &&
            *pref_id_it == iter_id) {
          if (update_gallery_type)
            dict->SetString(kMediaGalleriesTypeKey, TypeToStringValue(new_type));
          if (update_gallery_name)
            dict->SetString(kMediaGalleriesDisplayNameKey, display_name);
          if (update_gallery_metadata) {
            dict->SetString(kMediaGalleriesVolumeLabelKey, volume_label);
            dict->SetString(kMediaGalleriesVendorNameKey, vendor_name);
            dict->SetString(kMediaGalleriesModelNameKey, model_name);
            dict->SetDouble(kMediaGalleriesSizeKey, total_size_in_bytes);
            dict->SetDouble(kMediaGalleriesLastAttachTimeKey,
                            last_attach_time.ToInternalValue());
          }
          if (update_scan_counts) {
            dict->SetInteger(kMediaGalleriesScanAudioCountKey, audio_count);
            dict->SetInteger(kMediaGalleriesScanImageCountKey, image_count);
            dict->SetInteger(kMediaGalleriesScanVideoCountKey, video_count);
          }
          if (update_default_gallery_type) {
            dict->SetString(
                kMediaGalleriesDefaultGalleryTypeKey,
                DefaultGalleryTypeToStringValue(default_gallery_type));
          }
          dict->SetInteger(kMediaGalleriesPrefsVersionKey, prefs_version);
          break;
        }
      }
  
      // Commits the prefs update.
      update.reset();
  
      InitFromPrefs();
      FOR_EACH_OBSERVER(GalleryChangeObserver, gallery_change_observers_,
                        OnGalleryInfoUpdated(this, *pref_id_it));
      return *pref_id_it;
    }
  
    PrefService* prefs = profile_->GetPrefs();
  
    MediaGalleryPrefInfo gallery_info;
    gallery_info.pref_id = prefs->GetUint64(prefs::kMediaGalleriesUniqueId);
    prefs->SetUint64(prefs::kMediaGalleriesUniqueId, gallery_info.pref_id + 1);
    gallery_info.display_name = display_name;
    gallery_info.device_id = device_id;
    gallery_info.path = normalized_relative_path;
    gallery_info.type = type;
    gallery_info.volume_label = volume_label;
    gallery_info.vendor_name = vendor_name;
    gallery_info.model_name = model_name;
    gallery_info.total_size_in_bytes = total_size_in_bytes;
    gallery_info.last_attach_time = last_attach_time;
    gallery_info.volume_metadata_valid = volume_metadata_valid;
    gallery_info.audio_count = audio_count;
    gallery_info.image_count = image_count;
    gallery_info.video_count = video_count;
    gallery_info.prefs_version = prefs_version;
    gallery_info.default_gallery_type = default_gallery_type;
  
    {
      ListPrefUpdate update(prefs, prefs::kMediaGalleriesRememberedGalleries);
      base::ListValue* list = update.Get();
      list->Append(CreateGalleryPrefInfoDictionary(gallery_info));
    }
    InitFromPrefs();
    FOR_EACH_OBSERVER(GalleryChangeObserver,
                      gallery_change_observers_,
                      OnGalleryAdded(this, gallery_info.pref_id));
  
    return gallery_info.pref_id;
  }
  
  
  void MediaGalleriesPreferences::UpdateDefaultGalleriesPaths() {
    base::FilePath music_path;
    base::FilePath pictures_path;
    base::FilePath videos_path;
    bool got_music_path = PathService::Get(chrome::DIR_USER_MUSIC, &music_path);
    bool got_pictures_path =
        PathService::Get(chrome::DIR_USER_PICTURES, &pictures_path);
    bool got_videos_path =
        PathService::Get(chrome::DIR_USER_VIDEOS, &videos_path);
  
    PrefService* prefs = profile_->GetPrefs();
    scoped_ptr<ListPrefUpdate> update(new ListPrefUpdate(
        prefs, prefs::kMediaGalleriesRememberedGalleries));
    base::ListValue* list = update->Get();
  
    std::vector<MediaGalleryPrefId> pref_ids;
  
    for (base::ListValue::iterator iter = list->begin();
         iter != list->end();
         ++iter) {
      base::DictionaryValue* dict;
      MediaGalleryPrefId pref_id;
  
      if (!((*iter)->GetAsDictionary(&dict) && GetPrefId(*dict, &pref_id)))
        continue;
  
      std::string default_gallery_type_string;
  
      // If the "default gallery type" key is set, just update the paths in place.
      // If it's not set, then AddOrUpdateGalleryInternal will take care of
      // setting it as part of migration to prefs version 3.
      if (dict->GetString(kMediaGalleriesDefaultGalleryTypeKey,
                          &default_gallery_type_string)) {
        std::string device_id;
        if (got_music_path &&
            default_gallery_type_string ==
                kMediaGalleriesDefaultGalleryTypeMusicDefaultValue) {
          device_id = StorageInfo::MakeDeviceId(
              StorageInfo::Type::FIXED_MASS_STORAGE,
              music_path.AsUTF8Unsafe());
        } else if (got_pictures_path &&
                   default_gallery_type_string ==
                       kMediaGalleriesDefaultGalleryTypePicturesDefaultValue) {
          device_id = StorageInfo::MakeDeviceId(
              StorageInfo::Type::FIXED_MASS_STORAGE,
              pictures_path.AsUTF8Unsafe());
        } else if (got_videos_path &&
                   default_gallery_type_string ==
                       kMediaGalleriesDefaultGalleryTypeVideosDefaultValue) {
          device_id = StorageInfo::MakeDeviceId(
              StorageInfo::Type::FIXED_MASS_STORAGE,
              videos_path.AsUTF8Unsafe());
        }
  
        if (!device_id.empty())
          dict->SetString(kMediaGalleriesDeviceIdKey, device_id);
      }
  
      pref_ids.push_back(pref_id);
    }
  
    // Commit the prefs update.
    update.reset();
    InitFromPrefs();
  
    for (std::vector<MediaGalleryPrefId>::iterator iter = pref_ids.begin();
         iter != pref_ids.end();
         ++iter) {
      FOR_EACH_OBSERVER(GalleryChangeObserver,
                        gallery_change_observers_,
                        OnGalleryInfoUpdated(this, *iter));
    }
  }
  
  
  MediaGalleryPrefId MediaGalleriesPreferences::AddGalleryByPath(
      const base::FilePath& path, MediaGalleryPrefInfo::Type type) {
    DCHECK(IsInitialized());
    MediaGalleryPrefInfo gallery_info;
    if (LookUpGalleryByPath(path, &gallery_info) &&
        !gallery_info.IsBlackListedType()) {
      return gallery_info.pref_id;
    }
    return AddOrUpdateGalleryInternal(gallery_info.device_id,
                              gallery_info.display_name,
                              gallery_info.path,
                              type,
                              gallery_info.volume_label,
                              gallery_info.vendor_name,
                              gallery_info.model_name,
                              gallery_info.total_size_in_bytes,
                              gallery_info.last_attach_time,
                              gallery_info.volume_metadata_valid,
                              0, 0, 0,
                              kCurrentPrefsVersion,
                              MediaGalleryPrefInfo::kNotDefault);
  }
  
  void MediaGalleriesPreferences::ForgetGalleryById(MediaGalleryPrefId id) {
    EraseOrBlacklistGalleryById(id, false);
  }
  
  void MediaGalleriesPreferences::EraseGalleryById(MediaGalleryPrefId id) {
    EraseOrBlacklistGalleryById(id, true);
  }
  
  void MediaGalleriesPreferences::EraseOrBlacklistGalleryById(
      MediaGalleryPrefId id, bool erase) {
    DCHECK(IsInitialized());
    PrefService* prefs = profile_->GetPrefs();
    scoped_ptr<ListPrefUpdate> update(new ListPrefUpdate(
        prefs, prefs::kMediaGalleriesRememberedGalleries));
    base::ListValue* list = update->Get();
  
    if (!ContainsKey(known_galleries_, id))
      return;
  
    for (base::ListValue::iterator iter = list->begin();
         iter != list->end(); ++iter) {
      base::DictionaryValue* dict;
      MediaGalleryPrefId iter_id;
      if ((*iter)->GetAsDictionary(&dict) && GetPrefId(*dict, &iter_id) &&
          id == iter_id) {
        RemoveGalleryPermissionsFromPrefs(id);
        MediaGalleryPrefInfo::Type type;
        if (!erase && GetType(*dict, &type) &&
            (type == MediaGalleryPrefInfo::kAutoDetected ||
             type == MediaGalleryPrefInfo::kScanResult)) {
          if (type == MediaGalleryPrefInfo::kAutoDetected) {
            dict->SetString(kMediaGalleriesTypeKey,
                            kMediaGalleriesTypeBlackListedValue);
          } else {
            dict->SetString(kMediaGalleriesTypeKey,
                            kMediaGalleriesTypeRemovedScanValue);
            dict->SetInteger(kMediaGalleriesScanAudioCountKey, 0);
            dict->SetInteger(kMediaGalleriesScanImageCountKey, 0);
            dict->SetInteger(kMediaGalleriesScanVideoCountKey, 0);
          }
        } else {
          list->Erase(iter, NULL);
        }
        update.reset(NULL);  // commits the update.
  
        InitFromPrefs();
        FOR_EACH_OBSERVER(GalleryChangeObserver,
                          gallery_change_observers_,
                          OnGalleryRemoved(this, id));
        return;
      }
    }
  }
  
  bool MediaGalleriesPreferences::NonAutoGalleryHasPermission(
      MediaGalleryPrefId id) const {
    DCHECK(IsInitialized());
    DCHECK(!ContainsKey(known_galleries_, id) ||
           known_galleries_.find(id)->second.type !=
               MediaGalleryPrefInfo::kAutoDetected);
    ExtensionPrefs* prefs = GetExtensionPrefs();
    const base::DictionaryValue* extensions =
        prefs->pref_service()->GetDictionary(extensions::pref_names::kExtensions);
    if (!extensions)
      return true;
  
    for (base::DictionaryValue::Iterator iter(*extensions); !iter.IsAtEnd();
         iter.Advance()) {
      if (!crx_file::id_util::IdIsValid(iter.key())) {
        NOTREACHED();
        continue;
      }
      std::vector<MediaGalleryPermission> permissions =
          GetGalleryPermissionsFromPrefs(iter.key());
      for (std::vector<MediaGalleryPermission>::const_iterator it =
               permissions.begin(); it != permissions.end(); ++it) {
        if (it->pref_id == id) {
          if (it->has_permission)
            return true;
          break;
        }
      }
    }
    return false;
  }
  
  MediaGalleryPrefIdSet MediaGalleriesPreferences::GalleriesForExtension(
      const extensions::Extension& extension) {
    DCHECK(IsInitialized());
    MediaGalleryPrefIdSet result;
  
    if (HasAutoDetectedGalleryPermission(extension)) {
      for (MediaGalleriesPrefInfoMap::const_iterator it =
               known_galleries_.begin(); it != known_galleries_.end(); ++it) {
        if (it->second.type == MediaGalleryPrefInfo::kAutoDetected)
          result.insert(it->second.pref_id);
      }
    }
  
    std::vector<MediaGalleryPermission> stored_permissions =
        GetGalleryPermissionsFromPrefs(extension.id());
    for (std::vector<MediaGalleryPermission>::const_iterator it =
             stored_permissions.begin(); it != stored_permissions.end(); ++it) {
      if (!it->has_permission) {
        result.erase(it->pref_id);
      } else {
        MediaGalleriesPrefInfoMap::const_iterator gallery =
            known_galleries_.find(it->pref_id);
  
        // Handle a stored permission for an erased gallery. This should never
        // happen but, has caused crashes in the wild. http://crbug.com/374330.
        if (gallery == known_galleries_.end()) {
          RemoveGalleryPermissionsFromPrefs(it->pref_id);
          continue;
        }
  
        if (!gallery->second.IsBlackListedType()) {
          result.insert(it->pref_id);
        } else {
          NOTREACHED() << gallery->second.device_id;
        }
      }
    }
    return result;
  }
  
  bool MediaGalleriesPreferences::SetGalleryPermissionForExtension(
      const extensions::Extension& extension,
      MediaGalleryPrefId pref_id,
      bool has_permission) {
    DCHECK(IsInitialized());
    // The gallery may not exist anymore if the user opened a second config
    // surface concurrently and removed it. Drop the permission update if so.
    MediaGalleriesPrefInfoMap::const_iterator gallery_info =
        known_galleries_.find(pref_id);
    if (gallery_info == known_galleries_.end())
      return false;
  
    bool default_permission = false;
    if (gallery_info->second.type == MediaGalleryPrefInfo::kAutoDetected)
      default_permission = HasAutoDetectedGalleryPermission(extension);
    // When the permission matches the default, we don't need to remember it.
    if (has_permission == default_permission) {
      if (!UnsetGalleryPermissionInPrefs(extension.id(), pref_id))
        // If permission wasn't set, assume nothing has changed.
        return false;
    } else {
      if (!SetGalleryPermissionInPrefs(extension.id(), pref_id, has_permission))
        return false;
    }
    if (has_permission)
      FOR_EACH_OBSERVER(GalleryChangeObserver,
                        gallery_change_observers_,
                        OnPermissionAdded(this, extension.id(), pref_id));
    else
      FOR_EACH_OBSERVER(GalleryChangeObserver,
                        gallery_change_observers_,
                        OnPermissionRemoved(this, extension.id(), pref_id));
    return true;
  }
  
  const MediaGalleriesPrefInfoMap& MediaGalleriesPreferences::known_galleries()
      const {
    DCHECK(IsInitialized());
    return known_galleries_;
  }
  
  base::Time MediaGalleriesPreferences::GetLastScanCompletionTime() const {
    int64_t last_scan_time_internal =
        profile_->GetPrefs()->GetInt64(prefs::kMediaGalleriesLastScanTime);
    return base::Time::FromInternalValue(last_scan_time_internal);
  }
  
  void MediaGalleriesPreferences::SetLastScanCompletionTime(
      const base::Time& time) {
    profile_->GetPrefs()->SetInt64(prefs::kMediaGalleriesLastScanTime,
                                   time.ToInternalValue());
  }
  
  void MediaGalleriesPreferences::Shutdown() {
    weak_factory_.InvalidateWeakPtrs();
    profile_ = NULL;
  }
  
  // static
  bool MediaGalleriesPreferences::APIHasBeenUsed(Profile* profile) {
    MediaGalleryPrefId current_id =
        profile->GetPrefs()->GetUint64(prefs::kMediaGalleriesUniqueId);
    return current_id != kInvalidMediaGalleryPrefId + 1;
  }
  
  // static
  void MediaGalleriesPreferences::RegisterProfilePrefs(
      user_prefs::PrefRegistrySyncable* registry) {
    registry->RegisterListPref(prefs::kMediaGalleriesRememberedGalleries);
    registry->RegisterUint64Pref(prefs::kMediaGalleriesUniqueId,
                                 kInvalidMediaGalleryPrefId + 1);
    registry->RegisterInt64Pref(prefs::kMediaGalleriesLastScanTime,
                                base::Time().ToInternalValue());
  }
  
  bool MediaGalleriesPreferences::SetGalleryPermissionInPrefs(
      const std::string& extension_id,
      MediaGalleryPrefId gallery_id,
      bool has_access) {
    DCHECK(IsInitialized());
    ExtensionPrefs::ScopedListUpdate update(GetExtensionPrefs(),
                                            extension_id,
                                            kMediaGalleriesPermissions);
    base::ListValue* permissions = update.Get();
    if (!permissions) {
      permissions = update.Create();
    } else {
      // If the gallery is already in the list, update the permission...
      for (base::ListValue::iterator iter = permissions->begin();
           iter != permissions->end(); ++iter) {
        base::DictionaryValue* dict = NULL;
        if (!(*iter)->GetAsDictionary(&dict))
          continue;
        MediaGalleryPermission perm;
        if (!GetMediaGalleryPermissionFromDictionary(dict, &perm))
          continue;
        if (perm.pref_id == gallery_id) {
          if (has_access != perm.has_permission) {
            dict->SetBoolean(kMediaGalleryHasPermissionKey, has_access);
            return true;
          } else {
            return false;
          }
        }
      }
    }
    // ...Otherwise, add a new entry for the gallery.
    base::DictionaryValue* dict = new base::DictionaryValue;
    dict->SetString(kMediaGalleryIdKey, base::Uint64ToString(gallery_id));
    dict->SetBoolean(kMediaGalleryHasPermissionKey, has_access);
    permissions->Append(dict);
    return true;
  }
  
  bool MediaGalleriesPreferences::UnsetGalleryPermissionInPrefs(
      const std::string& extension_id,
      MediaGalleryPrefId gallery_id) {
    DCHECK(IsInitialized());
    ExtensionPrefs::ScopedListUpdate update(GetExtensionPrefs(),
                                            extension_id,
                                            kMediaGalleriesPermissions);
    base::ListValue* permissions = update.Get();
    if (!permissions)
      return false;
  
    for (base::ListValue::iterator iter = permissions->begin();
         iter != permissions->end(); ++iter) {
      const base::DictionaryValue* dict = NULL;
      if (!(*iter)->GetAsDictionary(&dict))
        continue;
      MediaGalleryPermission perm;
      if (!GetMediaGalleryPermissionFromDictionary(dict, &perm))
        continue;
      if (perm.pref_id == gallery_id) {
        permissions->Erase(iter, NULL);
        return true;
      }
    }
    return false;
  }
  
  std::vector<MediaGalleryPermission>
  MediaGalleriesPreferences::GetGalleryPermissionsFromPrefs(
      const std::string& extension_id) const {
    DCHECK(IsInitialized());
    std::vector<MediaGalleryPermission> result;
    const base::ListValue* permissions;
    if (!GetExtensionPrefs()->ReadPrefAsList(extension_id,
                                             kMediaGalleriesPermissions,
                                             &permissions)) {
      return result;
    }
  
    for (base::ListValue::const_iterator iter = permissions->begin();
         iter != permissions->end(); ++iter) {
      base::DictionaryValue* dict = NULL;
      if (!(*iter)->GetAsDictionary(&dict))
        continue;
      MediaGalleryPermission perm;
      if (!GetMediaGalleryPermissionFromDictionary(dict, &perm))
        continue;
      result.push_back(perm);
    }
  
    return result;
  }
  
  void MediaGalleriesPreferences::RemoveGalleryPermissionsFromPrefs(
      MediaGalleryPrefId gallery_id) {
    DCHECK(IsInitialized());
    ExtensionPrefs* prefs = GetExtensionPrefs();
    const base::DictionaryValue* extensions =
        prefs->pref_service()->GetDictionary(extensions::pref_names::kExtensions);
    if (!extensions)
      return;
  
    for (base::DictionaryValue::Iterator iter(*extensions); !iter.IsAtEnd();
         iter.Advance()) {
      if (!crx_file::id_util::IdIsValid(iter.key())) {
        NOTREACHED();
        continue;
      }
      UnsetGalleryPermissionInPrefs(iter.key(), gallery_id);
    }
  }
  
  ExtensionPrefs* MediaGalleriesPreferences::GetExtensionPrefs() const {
    DCHECK(IsInitialized());
    if (extension_prefs_for_testing_)
      return extension_prefs_for_testing_;
    return extensions::ExtensionPrefs::Get(profile_);
  }
  
  void MediaGalleriesPreferences::SetExtensionPrefsForTesting(
      extensions::ExtensionPrefs* extension_prefs) {
    DCHECK(IsInitialized());
    extension_prefs_for_testing_ = extension_prefs;
  }
  