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