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