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 #import <Foundation/Foundation.h>
6 #import <ImageCaptureCore/ImageCaptureCore.h>
8 #include "base/file_util.h"
9 #include "base/files/file_path.h"
10 #include "base/files/scoped_temp_dir.h"
11 #include "base/mac/foundation_util.h"
12 #include "base/memory/weak_ptr.h"
13 #include "base/run_loop.h"
14 #include "chrome/browser/storage_monitor/image_capture_device.h"
15 #include "chrome/browser/storage_monitor/image_capture_device_manager.h"
16 #include "chrome/browser/storage_monitor/test_storage_monitor.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "content/public/test/test_browser_thread_bundle.h"
19 #include "testing/gtest/include/gtest/gtest.h"
21 #if !defined(MAC_OS_X_VERSION_10_7) || \
22 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
24 @interface NSObject (ICCameraDeviceDelegateLionAPI)
25 - (void)deviceDidBecomeReadyWithCompleteContentCatalog:(ICDevice*)device;
26 - (void)didDownloadFile:(ICCameraFile*)file
28 options:(NSDictionary*)options
29 contextInfo:(void*)contextInfo;
36 const char kDeviceId[] = "id";
37 const char kTestFileContents[] = "test";
41 // Private ICCameraDevice method needed to properly initialize the object.
42 @interface NSObject (PrivateAPIICCameraDevice)
43 - (id)initWithDictionary:(id)properties;
46 @interface MockICCameraDevice : ICCameraDevice {
48 base::scoped_nsobject<NSMutableArray> allMediaFiles_;
51 - (void)addMediaFile:(ICCameraFile*)file;
55 @implementation MockICCameraDevice
58 if ((self = [super initWithDictionary:[NSDictionary dictionary]])) {
63 - (NSString*)mountPoint {
71 - (NSString*)UUIDString {
72 return base::SysUTF8ToNSString(kDeviceId);
75 - (ICDeviceType)type {
76 return ICDeviceTypeCamera;
79 - (void)requestOpenSession {
82 - (void)requestCloseSession {
85 - (NSArray*)mediaFiles {
86 return allMediaFiles_;
89 - (void)addMediaFile:(ICCameraFile*)file {
90 if (!allMediaFiles_.get())
91 allMediaFiles_.reset([[NSMutableArray alloc] init]);
92 [allMediaFiles_ addObject:file];
95 // This method does approximately what the internal ImageCapture platform
96 // library is observed to do: take the download save-as filename and mangle
97 // it to attach an extension, then return that new filename to the caller
99 - (void)requestDownloadFile:(ICCameraFile*)file
100 options:(NSDictionary*)options
101 downloadDelegate:(id<ICCameraDeviceDownloadDelegate>)downloadDelegate
102 didDownloadSelector:(SEL)selector
103 contextInfo:(void*)contextInfo {
104 base::FilePath saveDir(base::SysNSStringToUTF8(
105 [[options objectForKey:ICDownloadsDirectoryURL] path]));
106 std::string saveAsFilename =
107 base::SysNSStringToUTF8([options objectForKey:ICSaveAsFilename]);
108 // It appears that the ImageCapture library adds an extension to the requested
109 // filename. Do that here to require a rename.
110 saveAsFilename += ".jpg";
111 base::FilePath toBeSaved = saveDir.Append(saveAsFilename);
112 ASSERT_EQ(static_cast<int>(strlen(kTestFileContents)),
113 file_util::WriteFile(toBeSaved, kTestFileContents,
114 strlen(kTestFileContents)));
116 NSMutableDictionary* returnOptions =
117 [NSMutableDictionary dictionaryWithDictionary:options];
118 [returnOptions setObject:base::SysUTF8ToNSString(saveAsFilename)
119 forKey:ICSavedFilename];
121 [static_cast<NSObject<ICCameraDeviceDownloadDelegate>*>(downloadDelegate)
124 options:returnOptions
125 contextInfo:contextInfo];
130 @interface MockICCameraFolder : ICCameraFolder {
132 base::scoped_nsobject<NSString> name_;
135 - (id)initWithName:(NSString*)name;
139 @implementation MockICCameraFolder
141 - (id)initWithName:(NSString*)name {
142 if ((self = [super init])) {
143 name_.reset([name retain]);
152 - (ICCameraFolder*)parentFolder {
158 @interface MockICCameraFile : ICCameraFile {
160 base::scoped_nsobject<NSString> name_;
161 base::scoped_nsobject<NSDate> date_;
162 base::scoped_nsobject<MockICCameraFolder> parent_;
165 - (id)init:(NSString*)name;
166 - (void)setParent:(NSString*)parent;
170 @implementation MockICCameraFile
172 - (id)init:(NSString*)name {
173 if ((self = [super init])) {
174 name_.reset([name retain]);
175 date_.reset([[NSDate dateWithNaturalLanguageString:@"12/12/12"] retain]);
180 - (void)setParent:(NSString*)parent {
181 parent_.reset([[MockICCameraFolder alloc] initWithName:parent]);
184 - (ICCameraFolder*)parentFolder {
185 return parent_.get();
193 return base::mac::CFToNSCast(kUTTypeImage);
196 - (NSDate*)modificationDate {
200 - (NSDate*)creationDate {
210 class TestCameraListener
211 : public ImageCaptureDeviceListener,
212 public base::SupportsWeakPtr<TestCameraListener> {
217 last_error_(base::PLATFORM_FILE_ERROR_INVALID_URL) {}
218 virtual ~TestCameraListener() {}
220 virtual void ItemAdded(const std::string& name,
221 const base::PlatformFileInfo& info) OVERRIDE {
222 items_.push_back(name);
225 virtual void NoMoreItems() OVERRIDE {
229 virtual void DownloadedFile(const std::string& name,
230 base::PlatformFileError error) OVERRIDE {
231 EXPECT_TRUE(content::BrowserThread::CurrentlyOn(
232 content::BrowserThread::UI));
233 downloads_.push_back(name);
237 virtual void DeviceRemoved() OVERRIDE {
241 std::vector<std::string> items() const { return items_; }
242 std::vector<std::string> downloads() const { return downloads_; }
243 bool completed() const { return completed_; }
244 bool removed() const { return removed_; }
245 base::PlatformFileError last_error() const { return last_error_; }
248 std::vector<std::string> items_;
249 std::vector<std::string> downloads_;
252 base::PlatformFileError last_error_;
255 class ImageCaptureDeviceManagerTest : public testing::Test {
257 virtual void SetUp() OVERRIDE {
258 monitor_ = TestStorageMonitor::CreateAndInstall();
261 virtual void TearDown() OVERRIDE {
262 TestStorageMonitor::RemoveSingleton();
265 MockICCameraDevice* AttachDevice(ImageCaptureDeviceManager* manager) {
266 // Ownership will be passed to the device browser delegate.
267 base::scoped_nsobject<MockICCameraDevice> device(
268 [[MockICCameraDevice alloc] init]);
269 id<ICDeviceBrowserDelegate> delegate = manager->device_browser();
270 [delegate deviceBrowser:nil didAddDevice:device moreComing:NO];
271 return device.autorelease();
274 void DetachDevice(ImageCaptureDeviceManager* manager,
275 ICCameraDevice* device) {
276 id<ICDeviceBrowserDelegate> delegate = manager->device_browser();
277 [delegate deviceBrowser:nil didRemoveDevice:device moreGoing:NO];
281 content::TestBrowserThreadBundle thread_bundle_;
282 TestStorageMonitor* monitor_;
283 TestCameraListener listener_;
286 TEST_F(ImageCaptureDeviceManagerTest, TestAttachDetach) {
287 ImageCaptureDeviceManager manager;
288 manager.SetNotifications(monitor_->receiver());
289 ICCameraDevice* device = AttachDevice(&manager);
290 std::vector<StorageInfo> devices = monitor_->GetAllAvailableStorages();
292 ASSERT_EQ(1U, devices.size());
293 EXPECT_EQ(std::string("ic:") + kDeviceId, devices[0].device_id());
295 DetachDevice(&manager, device);
296 devices = monitor_->GetAllAvailableStorages();
297 ASSERT_EQ(0U, devices.size());
300 TEST_F(ImageCaptureDeviceManagerTest, OpenCamera) {
301 ImageCaptureDeviceManager manager;
302 manager.SetNotifications(monitor_->receiver());
303 ICCameraDevice* device = AttachDevice(&manager);
305 EXPECT_FALSE(ImageCaptureDeviceManager::deviceForUUID(
308 base::scoped_nsobject<ImageCaptureDevice> camera(
309 [ImageCaptureDeviceManager::deviceForUUID(kDeviceId) retain]);
311 [camera setListener:listener_.AsWeakPtr()];
314 base::scoped_nsobject<MockICCameraFile> picture1(
315 [[MockICCameraFile alloc] init:@"pic1"]);
316 [camera cameraDevice:nil didAddItem:picture1];
317 base::scoped_nsobject<MockICCameraFile> picture2(
318 [[MockICCameraFile alloc] init:@"pic2"]);
319 [camera cameraDevice:nil didAddItem:picture2];
320 ASSERT_EQ(2U, listener_.items().size());
321 EXPECT_EQ("pic1", listener_.items()[0]);
322 EXPECT_EQ("pic2", listener_.items()[1]);
323 EXPECT_FALSE(listener_.completed());
325 [camera deviceDidBecomeReadyWithCompleteContentCatalog:nil];
327 ASSERT_EQ(2U, listener_.items().size());
328 EXPECT_TRUE(listener_.completed());
331 DetachDevice(&manager, device);
332 EXPECT_FALSE(ImageCaptureDeviceManager::deviceForUUID(kDeviceId));
335 TEST_F(ImageCaptureDeviceManagerTest, RemoveCamera) {
336 ImageCaptureDeviceManager manager;
337 manager.SetNotifications(monitor_->receiver());
338 ICCameraDevice* device = AttachDevice(&manager);
340 base::scoped_nsobject<ImageCaptureDevice> camera(
341 [ImageCaptureDeviceManager::deviceForUUID(kDeviceId) retain]);
343 [camera setListener:listener_.AsWeakPtr()];
346 [camera didRemoveDevice:device];
347 EXPECT_TRUE(listener_.removed());
350 TEST_F(ImageCaptureDeviceManagerTest, DownloadFile) {
351 ImageCaptureDeviceManager manager;
352 manager.SetNotifications(monitor_->receiver());
353 MockICCameraDevice* device = AttachDevice(&manager);
355 base::scoped_nsobject<ImageCaptureDevice> camera(
356 [ImageCaptureDeviceManager::deviceForUUID(kDeviceId) retain]);
358 [camera setListener:listener_.AsWeakPtr()];
361 std::string kTestFileName("pic1");
363 base::scoped_nsobject<MockICCameraFile> picture1(
364 [[MockICCameraFile alloc] init:base::SysUTF8ToNSString(kTestFileName)]);
365 [device addMediaFile:picture1];
366 [camera cameraDevice:nil didAddItem:picture1];
368 base::ScopedTempDir temp_dir;
369 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
371 EXPECT_EQ(0U, listener_.downloads().size());
373 // Test that a nonexistent file we ask to be downloaded will
374 // return us a not-found error.
375 base::FilePath temp_file = temp_dir.path().Append("tempfile");
376 [camera downloadFile:std::string("nonexistent") localPath:temp_file];
377 base::RunLoop().RunUntilIdle();
378 ASSERT_EQ(1U, listener_.downloads().size());
379 EXPECT_EQ("nonexistent", listener_.downloads()[0]);
380 EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, listener_.last_error());
382 // Test that an existing file we ask to be downloaded will end up in
383 // the location we specify. The mock system will copy testing file
384 // contents to a separate filename, mimicking the ImageCaptureCore
385 // library behavior. Our code then renames the file onto the requested
387 [camera downloadFile:kTestFileName localPath:temp_file];
388 base::RunLoop().RunUntilIdle();
390 ASSERT_EQ(2U, listener_.downloads().size());
391 EXPECT_EQ(kTestFileName, listener_.downloads()[1]);
392 ASSERT_EQ(base::PLATFORM_FILE_OK, listener_.last_error());
393 char file_contents[5];
394 ASSERT_EQ(4, base::ReadFile(temp_file, file_contents,
395 strlen(kTestFileContents)));
396 EXPECT_EQ(kTestFileContents,
397 std::string(file_contents, strlen(kTestFileContents)));
399 [camera didRemoveDevice:device];
402 TEST_F(ImageCaptureDeviceManagerTest, TestSubdirectories) {
403 ImageCaptureDeviceManager manager;
404 manager.SetNotifications(monitor_->receiver());
405 MockICCameraDevice* device = AttachDevice(&manager);
407 base::scoped_nsobject<ImageCaptureDevice> camera(
408 [ImageCaptureDeviceManager::deviceForUUID(kDeviceId) retain]);
410 [camera setListener:listener_.AsWeakPtr()];
413 std::string kTestFileName("pic1");
414 base::scoped_nsobject<MockICCameraFile> picture1(
415 [[MockICCameraFile alloc] init:base::SysUTF8ToNSString(kTestFileName)]);
416 [picture1 setParent:base::SysUTF8ToNSString("dir")];
417 [device addMediaFile:picture1];
418 [camera cameraDevice:nil didAddItem:picture1];
420 base::ScopedTempDir temp_dir;
421 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
422 base::FilePath temp_file = temp_dir.path().Append("tempfile");
424 [camera downloadFile:("dir/" + kTestFileName) localPath:temp_file];
425 base::RunLoop().RunUntilIdle();
427 char file_contents[5];
428 ASSERT_EQ(4, base::ReadFile(temp_file, file_contents,
429 strlen(kTestFileContents)));
430 EXPECT_EQ(kTestFileContents,
431 std::string(file_contents, strlen(kTestFileContents)));
433 [camera didRemoveDevice:device];