1 // Copyright 2014 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.
5 #include "chrome/browser/media_galleries/media_galleries_scan_result_controller.h"
10 #include "base/bind.h"
11 #include "base/logging.h"
12 #include "base/metrics/histogram.h"
13 #include "base/stl_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "chrome/browser/browser_process.h"
16 #include "chrome/browser/media_galleries/media_file_system_registry.h"
17 #include "chrome/browser/media_galleries/media_galleries_histograms.h"
18 #include "chrome/browser/media_galleries/media_gallery_context_menu.h"
19 #include "chrome/browser/platform_util.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/grit/generated_resources.h"
22 #include "components/storage_monitor/storage_info.h"
23 #include "components/storage_monitor/storage_monitor.h"
24 #include "content/public/browser/web_contents.h"
25 #include "extensions/common/extension.h"
26 #include "extensions/common/permissions/media_galleries_permission.h"
27 #include "extensions/common/permissions/permissions_data.h"
28 #include "ui/base/l10n/l10n_util.h"
30 using storage_monitor::StorageInfo
;
31 using storage_monitor::StorageMonitor
;
35 // Comparator for sorting Entries -- more files first and then sorts by
37 bool ScanResultsComparator(
38 const MediaGalleriesDialogController::Entry
& a
,
39 const MediaGalleriesDialogController::Entry
& b
) {
40 int a_media_count
= a
.pref_info
.audio_count
+ a
.pref_info
.image_count
+
41 a
.pref_info
.video_count
;
42 int b_media_count
= b
.pref_info
.audio_count
+ b
.pref_info
.image_count
+
43 b
.pref_info
.video_count
;
44 if (a_media_count
== b_media_count
)
45 return a
.pref_info
.AbsolutePath() < b
.pref_info
.AbsolutePath();
46 return a_media_count
> b_media_count
;
52 size_t MediaGalleriesScanResultController::ScanResultCountForExtension(
53 MediaGalleriesPreferences
* preferences
,
54 const extensions::Extension
* extension
) {
55 ScanResults scan_results
;
56 UpdateScanResultsFromPreferences(preferences
, extension
,
57 MediaGalleryPrefIdSet(), &scan_results
);
58 return scan_results
.size();
61 MediaGalleriesScanResultController::MediaGalleriesScanResultController(
62 content::WebContents
* web_contents
,
63 const extensions::Extension
& extension
,
64 const base::Closure
& on_finish
)
65 : web_contents_(web_contents
),
66 extension_(&extension
),
67 on_finish_(on_finish
),
68 create_dialog_callback_(base::Bind(&MediaGalleriesDialog::Create
)) {
70 g_browser_process
->media_file_system_registry()->GetPreferences(
72 // Passing unretained pointer is safe, since the dialog controller
73 // is self-deleting, and so won't be deleted until it can be shown
75 preferences_
->EnsureInitialized(base::Bind(
76 &MediaGalleriesScanResultController::OnPreferencesInitialized
,
77 base::Unretained(this)));
79 // Unretained is safe because |this| owns |context_menu_|.
80 context_menu_
.reset(new MediaGalleryContextMenu(base::Bind(
81 &MediaGalleriesScanResultController::DidForgetEntry
,
82 base::Unretained(this))));
85 MediaGalleriesScanResultController::MediaGalleriesScanResultController(
86 const extensions::Extension
& extension
,
87 MediaGalleriesPreferences
* preferences
,
88 const CreateDialogCallback
& create_dialog_callback
,
89 const base::Closure
& on_finish
)
90 : web_contents_(NULL
),
91 extension_(&extension
),
92 on_finish_(on_finish
),
93 preferences_(preferences
),
94 create_dialog_callback_(create_dialog_callback
) {
95 OnPreferencesInitialized();
98 MediaGalleriesScanResultController::~MediaGalleriesScanResultController() {
99 // |preferences_| may be NULL in tests.
101 preferences_
->RemoveGalleryChangeObserver(this);
102 if (StorageMonitor::GetInstance())
103 StorageMonitor::GetInstance()->RemoveObserver(this);
106 base::string16
MediaGalleriesScanResultController::GetHeader() const {
107 return l10n_util::GetStringFUTF16(
108 IDS_MEDIA_GALLERIES_SCAN_RESULT_DIALOG_HEADER
,
109 base::UTF8ToUTF16(extension_
->name()));
112 base::string16
MediaGalleriesScanResultController::GetSubtext() const {
113 extensions::MediaGalleriesPermission::CheckParam
copy_to_param(
114 extensions::MediaGalleriesPermission::kCopyToPermission
);
115 extensions::MediaGalleriesPermission::CheckParam
delete_param(
116 extensions::MediaGalleriesPermission::kDeletePermission
);
117 const extensions::PermissionsData
* permissions_data
=
118 extension_
->permissions_data();
119 bool has_copy_to_permission
= permissions_data
->CheckAPIPermissionWithParam(
120 extensions::APIPermission::kMediaGalleries
, ©_to_param
);
121 bool has_delete_permission
= permissions_data
->CheckAPIPermissionWithParam(
122 extensions::APIPermission::kMediaGalleries
, &delete_param
);
125 if (has_copy_to_permission
)
126 id
= IDS_MEDIA_GALLERIES_SCAN_RESULT_DIALOG_SUBTEXT_READ_WRITE
;
127 else if (has_delete_permission
)
128 id
= IDS_MEDIA_GALLERIES_SCAN_RESULT_DIALOG_SUBTEXT_READ_DELETE
;
130 id
= IDS_MEDIA_GALLERIES_SCAN_RESULT_DIALOG_SUBTEXT_READ_ONLY
;
132 return l10n_util::GetStringFUTF16(id
, base::UTF8ToUTF16(extension_
->name()));
135 bool MediaGalleriesScanResultController::IsAcceptAllowed() const {
139 bool MediaGalleriesScanResultController::ShouldShowFolderViewer(
140 const Entry
& entry
) const {
141 return entry
.pref_info
.IsGalleryAvailable();
144 std::vector
<base::string16
>
145 MediaGalleriesScanResultController::GetSectionHeaders() const {
146 std::vector
<base::string16
> result
;
147 result
.push_back(base::string16());
151 MediaGalleriesDialogController::Entries
152 MediaGalleriesScanResultController::GetSectionEntries(
153 size_t index
) const {
154 DCHECK_EQ(0U, index
);
156 result
.reserve(scan_results_
.size());
157 for (ScanResults::const_iterator it
= scan_results_
.begin();
158 it
!= scan_results_
.end();
160 result
.push_back(it
->second
);
162 std::sort(result
.begin(), result
.end(), ScanResultsComparator
);
167 MediaGalleriesScanResultController::GetAuxiliaryButtonText() const {
168 return base::string16();
171 void MediaGalleriesScanResultController::DidClickAuxiliaryButton() {
175 void MediaGalleriesScanResultController::DidToggleEntry(
176 MediaGalleryPrefId pref_id
, bool selected
) {
177 DCHECK(ContainsKey(scan_results_
, pref_id
));
178 ScanResults::iterator entry
= scan_results_
.find(pref_id
);
179 entry
->second
.selected
= selected
;
182 void MediaGalleriesScanResultController::DidClickOpenFolderViewer(
183 MediaGalleryPrefId pref_id
) {
184 ScanResults::const_iterator entry
= scan_results_
.find(pref_id
);
185 if (entry
== scan_results_
.end()) {
189 platform_util::OpenItem(GetProfile(), entry
->second
.pref_info
.AbsolutePath(),
190 platform_util::OPEN_FOLDER
,
191 platform_util::OpenOperationCallback());
194 void MediaGalleriesScanResultController::DidForgetEntry(
195 MediaGalleryPrefId pref_id
) {
196 media_galleries::UsageCount(media_galleries::ADD_SCAN_RESULTS_FORGET_GALLERY
);
197 results_to_remove_
.insert(pref_id
);
198 scan_results_
.erase(pref_id
);
199 dialog_
->UpdateGalleries();
202 base::string16
MediaGalleriesScanResultController::GetAcceptButtonText() const {
203 return l10n_util::GetStringUTF16(
204 IDS_MEDIA_GALLERIES_SCAN_RESULT_DIALOG_CONFIRM
);
207 void MediaGalleriesScanResultController::DialogFinished(bool accepted
) {
208 // No longer interested in preference updates (and the below code generates
210 // |preferences_| may be NULL in tests.
212 preferences_
->RemoveGalleryChangeObserver(this);
215 DCHECK(preferences_
);
216 media_galleries::UsageCount(media_galleries::ADD_SCAN_RESULTS_ACCEPTED
);
219 for (ScanResults::const_iterator it
= scan_results_
.begin();
220 it
!= scan_results_
.end();
222 if (it
->second
.selected
) {
223 bool changed
= preferences_
->SetGalleryPermissionForExtension(
224 *extension_
, it
->first
, true);
231 UMA_HISTOGRAM_PERCENTAGE("MediaGalleries.ScanGalleriesGranted",
232 (granted
* 100 / total
));
234 for (MediaGalleryPrefIdSet::const_iterator it
= results_to_remove_
.begin();
235 it
!= results_to_remove_
.end();
237 preferences_
->ForgetGalleryById(*it
);
240 media_galleries::UsageCount(media_galleries::ADD_SCAN_RESULTS_CANCELLED
);
247 ui::MenuModel
* MediaGalleriesScanResultController::GetContextMenu(
248 MediaGalleryPrefId id
) {
249 context_menu_
->set_pref_id(id
);
250 return context_menu_
.get();
253 content::WebContents
* MediaGalleriesScanResultController::WebContents() {
254 return web_contents_
;
258 void MediaGalleriesScanResultController::UpdateScanResultsFromPreferences(
259 MediaGalleriesPreferences
* preferences
,
260 const extensions::Extension
* extension
,
261 MediaGalleryPrefIdSet ignore_list
,
262 ScanResults
* scan_results
) {
263 DCHECK(preferences
->IsInitialized());
264 const MediaGalleriesPrefInfoMap
& galleries
= preferences
->known_galleries();
265 MediaGalleryPrefIdSet permitted
=
266 preferences
->GalleriesForExtension(*extension
);
268 // Add or update any scan results that the extension doesn't already have
269 // access to or isn't in |ignore_list|.
270 for (MediaGalleriesPrefInfoMap::const_iterator it
= galleries
.begin();
271 it
!= galleries
.end();
273 const MediaGalleryPrefInfo
& gallery
= it
->second
;
274 if ((gallery
.audio_count
|| gallery
.image_count
|| gallery
.video_count
) &&
275 !gallery
.IsBlackListedType() &&
276 !ContainsKey(permitted
, gallery
.pref_id
) &&
277 !ContainsKey(ignore_list
, gallery
.pref_id
)) {
278 ScanResults::iterator existing
= scan_results
->find(gallery
.pref_id
);
279 if (existing
== scan_results
->end()) {
280 // Default to selected.
281 (*scan_results
)[gallery
.pref_id
] = Entry(gallery
, true);
283 // Update pref_info, in case anything has been updated.
284 existing
->second
.pref_info
= gallery
;
289 // Remove anything from |scan_results| that's no longer valid or the user
290 // already has access to.
291 std::list
<ScanResults::iterator
> to_remove
;
292 for (ScanResults::iterator it
= scan_results
->begin();
293 it
!= scan_results
->end();
295 MediaGalleriesPrefInfoMap::const_iterator pref_gallery
=
296 galleries
.find(it
->first
);
297 if (pref_gallery
== galleries
.end() ||
298 pref_gallery
->second
.IsBlackListedType() ||
299 ContainsKey(permitted
, it
->first
)) {
300 to_remove
.push_back(it
);
303 while (!to_remove
.empty()) {
304 scan_results
->erase(to_remove
.front());
305 to_remove
.pop_front();
309 void MediaGalleriesScanResultController::OnPreferencesInitialized() {
310 // These may be NULL in tests.
311 if (StorageMonitor::GetInstance())
312 StorageMonitor::GetInstance()->AddObserver(this);
314 preferences_
->AddGalleryChangeObserver(this);
315 UpdateScanResultsFromPreferences(preferences_
, extension_
,
316 results_to_remove_
, &scan_results_
);
319 dialog_
.reset(create_dialog_callback_
.Run(this));
322 void MediaGalleriesScanResultController::OnPreferenceUpdate(
323 const std::string
& extension_id
) {
324 if (extension_id
== extension_
->id()) {
325 UpdateScanResultsFromPreferences(preferences_
, extension_
,
326 results_to_remove_
, &scan_results_
);
327 dialog_
->UpdateGalleries();
331 void MediaGalleriesScanResultController::OnRemovableDeviceUpdate(
332 const std::string device_id
) {
333 for (ScanResults::const_iterator it
= scan_results_
.begin();
334 it
!= scan_results_
.end();
336 if (it
->second
.pref_info
.device_id
== device_id
) {
337 dialog_
->UpdateGalleries();
343 Profile
* MediaGalleriesScanResultController::GetProfile() const {
344 return Profile::FromBrowserContext(web_contents_
->GetBrowserContext());
347 void MediaGalleriesScanResultController::OnRemovableStorageAttached(
348 const StorageInfo
& info
) {
349 OnRemovableDeviceUpdate(info
.device_id());
352 void MediaGalleriesScanResultController::OnRemovableStorageDetached(
353 const StorageInfo
& info
) {
354 OnRemovableDeviceUpdate(info
.device_id());
357 void MediaGalleriesScanResultController::OnPermissionAdded(
358 MediaGalleriesPreferences
* /*pref*/,
359 const std::string
& extension_id
,
360 MediaGalleryPrefId
/*pref_id*/) {
361 OnPreferenceUpdate(extension_id
);
364 void MediaGalleriesScanResultController::OnPermissionRemoved(
365 MediaGalleriesPreferences
* /*pref*/,
366 const std::string
& extension_id
,
367 MediaGalleryPrefId
/*pref_id*/) {
368 OnPreferenceUpdate(extension_id
);
371 void MediaGalleriesScanResultController::OnGalleryAdded(
372 MediaGalleriesPreferences
* /*prefs*/,
373 MediaGalleryPrefId
/*pref_id*/) {
374 OnPreferenceUpdate(extension_
->id());
377 void MediaGalleriesScanResultController::OnGalleryRemoved(
378 MediaGalleriesPreferences
* /*prefs*/,
379 MediaGalleryPrefId
/*pref_id*/) {
380 OnPreferenceUpdate(extension_
->id());
383 void MediaGalleriesScanResultController::OnGalleryInfoUpdated(
384 MediaGalleriesPreferences
* /*prefs*/,
385 MediaGalleryPrefId
/*pref_id*/) {
386 OnPreferenceUpdate(extension_
->id());