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.
5 #include "base/auto_reset.h"
6 #include "base/callback.h"
7 #include "base/file_util.h"
8 #include "base/files/scoped_temp_dir.h"
9 #include "base/json/json_writer.h"
10 #include "base/numerics/safe_conversions.h"
11 #include "base/path_service.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/values.h"
15 #include "chrome/browser/apps/app_browsertest_util.h"
16 #include "chrome/browser/browser_process.h"
17 #include "chrome/browser/extensions/api/media_galleries/media_galleries_api.h"
18 #include "chrome/browser/media_galleries/media_file_system_registry.h"
19 #include "chrome/browser/media_galleries/media_folder_finder.h"
20 #include "chrome/browser/media_galleries/media_galleries_preferences.h"
21 #include "chrome/browser/media_galleries/media_galleries_scan_result_dialog_controller.h"
22 #include "chrome/browser/media_galleries/media_galleries_test_util.h"
23 #include "chrome/browser/media_galleries/media_scan_manager.h"
24 #include "chrome/common/chrome_paths.h"
25 #include "components/storage_monitor/storage_info.h"
26 #include "components/storage_monitor/storage_monitor.h"
27 #include "content/public/browser/web_contents.h"
28 #include "content/public/test/test_utils.h"
29 #include "extensions/browser/extension_system.h"
30 #include "extensions/common/extension.h"
31 #include "media/base/test_data_util.h"
33 #if defined(OS_WIN) || defined(OS_MACOSX)
34 #include "chrome/browser/media_galleries/fileapi/picasa_finder.h"
35 #include "chrome/common/media_galleries/picasa_test_util.h"
36 #include "chrome/common/media_galleries/picasa_types.h"
37 #include "chrome/common/media_galleries/pmp_test_util.h"
40 #if defined(OS_MACOSX)
41 #include "base/mac/foundation_util.h"
42 #include "base/strings/sys_string_conversions.h"
43 #include "chrome/browser/media_galleries/fileapi/iapps_finder_impl.h"
46 using extensions::PlatformAppBrowserTest;
47 using storage_monitor::StorageInfo;
48 using storage_monitor::StorageMonitor;
52 // Dummy device properties.
53 const char kDeviceId[] = "testDeviceId";
54 const char kDeviceName[] = "foobar";
55 #if defined(FILE_PATH_USES_DRIVE_LETTERS)
56 base::FilePath::CharType kDevicePath[] = FILE_PATH_LITERAL("C:\\qux");
58 base::FilePath::CharType kDevicePath[] = FILE_PATH_LITERAL("/qux");
61 class DoNothingMediaFolderFinder : public MediaFolderFinder {
63 explicit DoNothingMediaFolderFinder(
64 const MediaFolderFinderResultsCallback& callback)
65 : MediaFolderFinder(callback) {
67 virtual ~DoNothingMediaFolderFinder() {}
69 static MediaFolderFinder* CreateDoNothingMediaFolderFinder(
70 const MediaFolderFinderResultsCallback& callback) {
71 return new DoNothingMediaFolderFinder(callback);
74 virtual void StartScan() OVERRIDE {}
81 class TestMediaGalleriesAddScanResultsFunction
82 : public extensions::MediaGalleriesAddScanResultsFunction {
84 static ExtensionFunction* Factory() {
85 return new TestMediaGalleriesAddScanResultsFunction;
89 virtual ~TestMediaGalleriesAddScanResultsFunction() {}
91 // Accepts the dialog as soon as it is created.
92 virtual MediaGalleriesScanResultDialogController* MakeDialog(
93 content::WebContents* web_contents,
94 const extensions::Extension& extension,
95 const base::Closure& on_finish) OVERRIDE {
96 MediaGalleriesScanResultDialogController* controller =
97 extensions::MediaGalleriesAddScanResultsFunction::MakeDialog(
98 web_contents, extension, on_finish);
99 controller->dialog_->AcceptDialogForTesting();
100 // The dialog is closing or closed so don't return it.
105 class MediaGalleriesPlatformAppBrowserTest : public PlatformAppBrowserTest {
107 MediaGalleriesPlatformAppBrowserTest() : test_jpg_size_(0) {}
108 virtual ~MediaGalleriesPlatformAppBrowserTest() {}
110 virtual void SetUpOnMainThread() OVERRIDE {
111 PlatformAppBrowserTest::SetUpOnMainThread();
112 ensure_media_directories_exists_.reset(new EnsureMediaDirectoriesExists);
115 ASSERT_TRUE(base::GetFileSize(GetCommonDataDir().AppendASCII("test.jpg"),
117 test_jpg_size_ = base::checked_cast<int>(file_size);
120 virtual void TearDownOnMainThread() OVERRIDE {
121 ensure_media_directories_exists_.reset();
122 PlatformAppBrowserTest::TearDownOnMainThread();
125 bool RunMediaGalleriesTest(const std::string& extension_name) {
126 base::ListValue empty_list_value;
127 return RunMediaGalleriesTestWithArg(extension_name, empty_list_value);
130 bool RunMediaGalleriesTestWithArg(const std::string& extension_name,
131 const base::ListValue& custom_arg_value) {
132 // Copy the test data for this test into a temporary directory. Then add
133 // a common_injected.js to the temporary copy and run it.
134 const char kTestDir[] = "api_test/media_galleries/";
135 base::FilePath from_dir =
136 test_data_dir_.AppendASCII(kTestDir + extension_name);
137 from_dir = from_dir.NormalizePathSeparators();
139 base::ScopedTempDir temp_dir;
140 if (!temp_dir.CreateUniqueTempDir())
143 if (!base::CopyDirectory(from_dir, temp_dir.path(), true))
146 base::FilePath common_js_path(
147 GetCommonDataDir().AppendASCII("common_injected.js"));
148 base::FilePath inject_js_path(
149 temp_dir.path().AppendASCII(extension_name)
150 .AppendASCII("common_injected.js"));
151 if (!base::CopyFile(common_js_path, inject_js_path))
154 const char* custom_arg = NULL;
155 std::string json_string;
156 if (!custom_arg_value.empty()) {
157 base::JSONWriter::Write(&custom_arg_value, &json_string);
158 custom_arg = json_string.c_str();
161 base::AutoReset<base::FilePath> reset(&test_data_dir_, temp_dir.path());
162 bool result = RunPlatformAppTestWithArg(extension_name, custom_arg);
163 content::RunAllPendingInMessageLoop(); // avoid race on exit in registry.
167 void AttachFakeDevice() {
168 device_id_ = StorageInfo::MakeDeviceId(
169 StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM, kDeviceId);
171 StorageMonitor::GetInstance()->receiver()->ProcessAttach(
172 StorageInfo(device_id_, kDevicePath, base::ASCIIToUTF16(kDeviceName),
173 base::string16(), base::string16(), 0));
174 content::RunAllPendingInMessageLoop();
177 void DetachFakeDevice() {
178 StorageMonitor::GetInstance()->receiver()->ProcessDetach(device_id_);
179 content::RunAllPendingInMessageLoop();
182 // Called if test only wants a single gallery it creates.
183 void RemoveAllGalleries() {
184 MediaGalleriesPreferences* preferences = GetAndInitializePreferences();
186 // Make a copy, as the iterator would be invalidated otherwise.
187 const MediaGalleriesPrefInfoMap galleries =
188 preferences->known_galleries();
189 for (MediaGalleriesPrefInfoMap::const_iterator it = galleries.begin();
190 it != galleries.end(); ++it) {
191 preferences->ForgetGalleryById(it->first);
195 // This function makes a single fake gallery. This is needed to test platforms
196 // with no default media galleries, such as CHROMEOS. This fake gallery is
197 // pre-populated with a test.jpg and test.txt.
198 void MakeSingleFakeGallery() {
199 ASSERT_FALSE(fake_gallery_temp_dir_.IsValid());
200 ASSERT_TRUE(fake_gallery_temp_dir_.CreateUniqueTempDir());
202 MediaGalleriesPreferences* preferences = GetAndInitializePreferences();
204 MediaGalleryPrefInfo gallery_info;
205 ASSERT_FALSE(preferences->LookUpGalleryByPath(fake_gallery_temp_dir_.path(),
207 preferences->AddGallery(gallery_info.device_id,
209 MediaGalleryPrefInfo::kAutoDetected,
210 gallery_info.volume_label,
211 gallery_info.vendor_name,
212 gallery_info.model_name,
213 gallery_info.total_size_in_bytes,
214 gallery_info.last_attach_time,
217 content::RunAllPendingInMessageLoop();
219 // Valid file, should show up in JS as a FileEntry.
220 AddFileToSingleFakeGallery(GetCommonDataDir().AppendASCII("test.jpg"));
222 // Invalid file, should not show up as a FileEntry in JS at all.
223 AddFileToSingleFakeGallery(GetCommonDataDir().AppendASCII("test.txt"));
226 void AddFileToSingleFakeGallery(const base::FilePath& source_path) {
227 ASSERT_TRUE(fake_gallery_temp_dir_.IsValid());
229 ASSERT_TRUE(base::CopyFile(
231 fake_gallery_temp_dir_.path().Append(source_path.BaseName())));
234 #if defined(OS_WIN) || defined(OS_MACOSX)
235 void PopulatePicasaTestData(const base::FilePath& picasa_app_data_root) {
236 base::FilePath picasa_database_path =
237 picasa::MakePicasaDatabasePath(picasa_app_data_root);
238 base::FilePath picasa_temp_dir_path =
239 picasa_database_path.DirName().AppendASCII(picasa::kPicasaTempDirName);
240 ASSERT_TRUE(base::CreateDirectory(picasa_database_path));
241 ASSERT_TRUE(base::CreateDirectory(picasa_temp_dir_path));
243 // Create fake folder directories.
244 base::FilePath folders_root =
245 ensure_media_directories_exists_->GetFakePicasaFoldersRootPath();
246 base::FilePath fake_folder_1 = folders_root.AppendASCII("folder1");
247 base::FilePath fake_folder_2 = folders_root.AppendASCII("folder2");
248 ASSERT_TRUE(base::CreateDirectory(fake_folder_1));
249 ASSERT_TRUE(base::CreateDirectory(fake_folder_2));
251 // Write folder and album contents.
252 picasa::WriteTestAlbumTable(
253 picasa_database_path, fake_folder_1, fake_folder_2);
254 picasa::WriteTestAlbumsImagesIndex(fake_folder_1, fake_folder_2);
256 base::FilePath test_jpg_path = GetCommonDataDir().AppendASCII("test.jpg");
257 ASSERT_TRUE(base::CopyFile(
258 test_jpg_path, fake_folder_1.AppendASCII("InBoth.jpg")));
259 ASSERT_TRUE(base::CopyFile(
260 test_jpg_path, fake_folder_1.AppendASCII("InSecondAlbumOnly.jpg")));
261 ASSERT_TRUE(base::CopyFile(
262 test_jpg_path, fake_folder_2.AppendASCII("InFirstAlbumOnly.jpg")));
264 #endif // defined(OS_WIN) || defined(OS_MACOSX)
266 #if defined(OS_MACOSX)
267 void PopulateIPhotoTestData(const base::FilePath& iphoto_data_root) {
268 std::string xml_contents = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
269 "<plist version=\"1.0\">"
272 " <key>List of Albums</key>"
276 " <key>AlbumId</key>"
277 " <integer>1</integer>"
278 " <key>AlbumName</key>"
279 " <string>Album1</string>"
280 " <key>KeyList</key>\n"
282 " <string>1</string>"
283 " <string>2</string>"
288 " <key>AlbumId</key>"
289 " <integer>2</integer>"
290 " <key>AlbumName</key>"
291 " <string>Album2</string>"
292 " <key>KeyList</key>\n"
294 " <string>2</string>"
300 " <key>Master Image List</key>\n"
305 " <key>MediaType</key>"
306 " <string>Image</string>"
307 " <key>Caption</key>"
308 " <string>caption 1</string>"
310 " <string>1</string>"
311 " <key>ModDateAsTimerInterval</key>"
312 " <string>386221543.0000</string>"
313 " <key>DateAsTimerInterval</key>"
314 " <string>386221543.0000</string>"
315 " <key>DateAsTimerIntervalGMT</key>"
316 " <string>385123456.00</string>"
317 " <key>ImagePath</key>"
318 " <string>$path1</string>"
319 " <key>ThumbPath</key>"
320 " <string>/thumb/path</string>\n"
325 " <key>MediaType</key>"
326 " <string>Image</string>"
327 " <key>Caption</key>"
328 " <string>caption 2</string>"
330 " <string>2</string>"
331 " <key>ModDateAsTimerInterval</key>"
332 " <string>386221543.0000</string>"
333 " <key>DateAsTimerInterval</key>"
334 " <string>386221543.0000</string>"
335 " <key>DateAsTimerIntervalGMT</key>"
336 " <string>385123456.00</string>"
337 " <key>ImagePath</key>"
338 " <string>$path2</string>"
339 " <key>ThumbPath</key>"
340 " <string>/thumb/path2</string>\n"
343 " </dict>\n" // Master Image List
348 base::FilePath test_jpg_path = GetCommonDataDir().AppendASCII("test.jpg");
349 ASSERT_TRUE(base::CreateDirectory(iphoto_data_root));
350 base::FilePath first_only_jpg =
351 iphoto_data_root.AppendASCII("InFirstAlbumOnly.jpg");
352 base::FilePath in_both_jpg = iphoto_data_root.AppendASCII("InBoth.jpg");
353 ASSERT_TRUE(base::CopyFile(test_jpg_path, first_only_jpg));
354 ASSERT_TRUE(base::CopyFile(test_jpg_path, in_both_jpg));
355 ReplaceFirstSubstringAfterOffset(
356 &xml_contents, 0, std::string("$path1"), first_only_jpg.value());
357 ReplaceFirstSubstringAfterOffset(
358 &xml_contents, 0, std::string("$path2"), in_both_jpg.value());
360 base::FilePath album_xml = iphoto_data_root.AppendASCII("AlbumData.xml");
361 ASSERT_NE(-1, base::WriteFile(album_xml,
362 xml_contents.c_str(), xml_contents.size()));
364 #endif // defined(OS_MACOSX)
366 base::FilePath GetCommonDataDir() const {
367 return test_data_dir_.AppendASCII("api_test")
368 .AppendASCII("media_galleries")
369 .AppendASCII("common");
372 int num_galleries() const {
373 return ensure_media_directories_exists_->num_galleries();
376 int test_jpg_size() const { return test_jpg_size_; }
378 EnsureMediaDirectoriesExists* ensure_media_directories_exists() const {
379 return ensure_media_directories_exists_.get();
382 void InstallDoNothingFolderFinder() {
383 MediaScanManager * scan_manager =
384 g_browser_process->media_file_system_registry()->media_scan_manager();
385 scan_manager->SetMediaFolderFinderFactory(base::Bind(
386 &DoNothingMediaFolderFinder::CreateDoNothingMediaFolderFinder));
389 void SetRootsForFolderFinder(const std::vector<base::FilePath>& roots) {
390 MediaScanManager* scan_manager =
391 g_browser_process->media_file_system_registry()->media_scan_manager();
392 scan_manager->SetMediaFolderFinderFactory(base::Bind(
393 &MediaGalleriesPlatformAppBrowserTest::CreateMediaFolderFinderWithRoots,
398 static MediaFolderFinder* CreateMediaFolderFinderWithRoots(
399 const std::vector<base::FilePath>& roots,
400 const MediaFolderFinder::MediaFolderFinderResultsCallback& callback) {
401 MediaFolderFinder* finder = new MediaFolderFinder(callback);
402 finder->SetRootsForTesting(roots);
406 MediaGalleriesPreferences* GetAndInitializePreferences() {
407 MediaGalleriesPreferences* preferences =
408 g_browser_process->media_file_system_registry()->GetPreferences(
409 browser()->profile());
410 base::RunLoop runloop;
411 preferences->EnsureInitialized(runloop.QuitClosure());
416 std::string device_id_;
417 base::ScopedTempDir fake_gallery_temp_dir_;
419 scoped_ptr<EnsureMediaDirectoriesExists> ensure_media_directories_exists_;
422 // Flaky on WinXP Tests(1): http://crbug.com/354425
423 #if defined(OS_WIN) && defined(ARCH_CPU_X86)
424 #define MAYBE_MediaGalleriesNoAccess DISABLED_MediaGalleriesNoAccess
426 #define MAYBE_MediaGalleriesNoAccess MediaGalleriesNoAccess
428 IN_PROC_BROWSER_TEST_F(MediaGalleriesPlatformAppBrowserTest,
429 MAYBE_MediaGalleriesNoAccess) {
430 MakeSingleFakeGallery();
432 base::ListValue custom_args;
433 custom_args.AppendInteger(num_galleries() + 1);
435 ASSERT_TRUE(RunMediaGalleriesTestWithArg("no_access", custom_args))
439 IN_PROC_BROWSER_TEST_F(MediaGalleriesPlatformAppBrowserTest, NoGalleriesRead) {
440 ASSERT_TRUE(RunMediaGalleriesTest("no_galleries")) << message_;
443 IN_PROC_BROWSER_TEST_F(MediaGalleriesPlatformAppBrowserTest,
445 ASSERT_TRUE(RunMediaGalleriesTest("no_galleries_copy_to")) << message_;
448 IN_PROC_BROWSER_TEST_F(MediaGalleriesPlatformAppBrowserTest,
449 MediaGalleriesRead) {
450 RemoveAllGalleries();
451 MakeSingleFakeGallery();
452 base::ListValue custom_args;
453 custom_args.AppendInteger(test_jpg_size());
455 ASSERT_TRUE(RunMediaGalleriesTestWithArg("read_access", custom_args))
459 // Test is flaky, it fails only on a certain bot, namely WinXP Tests(1).
460 // See crbug.com/354425 .
461 #if defined(OS_WIN) && defined(ARCH_CPU_X86)
462 #define MAYBE_MediaGalleriesCopyTo DISABLED_MediaGalleriesCopyTo
464 #define MAYBE_MediaGalleriesCopyTo MediaGalleriesCopyTo
466 IN_PROC_BROWSER_TEST_F(MediaGalleriesPlatformAppBrowserTest,
467 MAYBE_MediaGalleriesCopyTo) {
468 RemoveAllGalleries();
469 MakeSingleFakeGallery();
470 ASSERT_TRUE(RunMediaGalleriesTest("copy_to_access")) << message_;
473 IN_PROC_BROWSER_TEST_F(MediaGalleriesPlatformAppBrowserTest,
474 MediaGalleriesDelete) {
475 MakeSingleFakeGallery();
476 base::ListValue custom_args;
477 custom_args.AppendInteger(num_galleries() + 1);
478 ASSERT_TRUE(RunMediaGalleriesTestWithArg("delete_access", custom_args))
482 IN_PROC_BROWSER_TEST_F(MediaGalleriesPlatformAppBrowserTest,
483 MediaGalleriesAccessAttached) {
486 base::ListValue custom_args;
487 custom_args.AppendInteger(num_galleries() + 1);
488 custom_args.AppendString(kDeviceName);
490 ASSERT_TRUE(RunMediaGalleriesTestWithArg("access_attached", custom_args))
496 #if defined(OS_WIN)|| defined(OS_MACOSX)
497 IN_PROC_BROWSER_TEST_F(MediaGalleriesPlatformAppBrowserTest,
498 PicasaDefaultLocation) {
500 PopulatePicasaTestData(
501 ensure_media_directories_exists()->GetFakeLocalAppDataPath());
502 #elif defined(OS_MACOSX)
503 PopulatePicasaTestData(
504 ensure_media_directories_exists()->GetFakeAppDataPath());
507 base::ListValue custom_args;
508 custom_args.AppendInteger(test_jpg_size());
509 ASSERT_TRUE(RunMediaGalleriesTestWithArg("picasa", custom_args)) << message_;
512 IN_PROC_BROWSER_TEST_F(MediaGalleriesPlatformAppBrowserTest,
513 PicasaCustomLocation) {
514 base::ScopedTempDir custom_picasa_app_data_root;
515 ASSERT_TRUE(custom_picasa_app_data_root.CreateUniqueTempDir());
516 ensure_media_directories_exists()->SetCustomPicasaAppDataPath(
517 custom_picasa_app_data_root.path());
518 PopulatePicasaTestData(custom_picasa_app_data_root.path());
520 base::ListValue custom_args;
521 custom_args.AppendInteger(test_jpg_size());
522 ASSERT_TRUE(RunMediaGalleriesTestWithArg("picasa", custom_args)) << message_;
524 #endif // defined(OS_WIN) || defined(OS_MACOSX)
526 #if defined(OS_MACOSX)
527 IN_PROC_BROWSER_TEST_F(MediaGalleriesPlatformAppBrowserTest,
529 PopulateIPhotoTestData(
530 ensure_media_directories_exists()->GetFakeIPhotoRootPath());
532 base::ListValue custom_args;
533 custom_args.AppendInteger(test_jpg_size());
534 ASSERT_TRUE(RunMediaGalleriesTestWithArg("iphoto", custom_args)) << message_;
536 iapps::SetMacPreferencesForTesting(NULL);
538 #endif // defined(OS_MACOSX)
540 IN_PROC_BROWSER_TEST_F(MediaGalleriesPlatformAppBrowserTest, CancelScan) {
541 InstallDoNothingFolderFinder();
542 ASSERT_TRUE(RunMediaGalleriesTest("cancel_scan")) << message_;
545 // The scan result dialog is not implemented on GTK because it is going away
547 #if defined (TOOLKIT_GTK)
548 #define MAYBE_Scan DISABLED_Scan
550 #define MAYBE_Scan Scan
551 #endif // defined (USE_AURA) || defined(OS_MACOSX)
553 IN_PROC_BROWSER_TEST_F(MediaGalleriesPlatformAppBrowserTest, MAYBE_Scan) {
554 base::ScopedTempDir scan_root;
555 ASSERT_TRUE(scan_root.CreateUniqueTempDir());
556 std::vector<base::FilePath> roots;
557 roots.push_back(scan_root.path());
558 SetRootsForFolderFinder(roots);
560 // Override addScanResults so that the dialog is accepted as soon as it is
562 ASSERT_TRUE(ExtensionFunctionDispatcher::OverrideFunction(
563 "mediaGalleries.addScanResults",
564 &TestMediaGalleriesAddScanResultsFunction::Factory));
566 // Add some files and directories to the scan root for testing. Only the
567 // "f" directory should be found.
568 std::string dummy_data;
569 dummy_data.resize(1);
570 ASSERT_TRUE(base::CreateDirectory(scan_root.path().AppendASCII("a/b")));
571 ASSERT_EQ(static_cast<int>(dummy_data.size()),
572 base::WriteFile(scan_root.path().AppendASCII("a/b/c.jpg"),
573 dummy_data.c_str(), dummy_data.size()));
574 ASSERT_TRUE(base::CreateDirectory(scan_root.path().AppendASCII("a/d")));
575 dummy_data.resize(201 * 1024); // 200k is the min size for the folder finder.
576 ASSERT_EQ(static_cast<int>(dummy_data.size()),
577 base::WriteFile(scan_root.path().AppendASCII("a/d/e.txt"),
578 dummy_data.c_str(), dummy_data.size()));
579 ASSERT_TRUE(base::CreateDirectory(scan_root.path().AppendASCII("f")));
580 ASSERT_EQ(static_cast<int>(dummy_data.size()),
581 base::WriteFile(scan_root.path().AppendASCII("f/g.jpg"),
582 dummy_data.c_str(), dummy_data.size()));
584 ASSERT_TRUE(RunMediaGalleriesTest("scan")) << message_;
587 IN_PROC_BROWSER_TEST_F(MediaGalleriesPlatformAppBrowserTest, GetMetadata) {
588 RemoveAllGalleries();
589 MakeSingleFakeGallery();
591 AddFileToSingleFakeGallery(media::GetTestDataFilePath("90rotation.mp4"));
592 AddFileToSingleFakeGallery(media::GetTestDataFilePath("id3_png_test.mp3"));
594 base::ListValue custom_args;
595 #if defined(USE_PROPRIETARY_CODECS)
596 custom_args.AppendBoolean(true);
598 custom_args.AppendBoolean(false);
600 ASSERT_TRUE(RunMediaGalleriesTestWithArg("media_metadata", custom_args))