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