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