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 #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 "components/storage_monitor/image_capture_device.h"
15 #include "components/storage_monitor/image_capture_device_manager.h"
16 #include "components/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 base::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 namespace storage_monitor {
212 class TestCameraListener
213 : public ImageCaptureDeviceListener,
214 public base::SupportsWeakPtr<TestCameraListener> {
219 last_error_(base::File::FILE_ERROR_INVALID_URL) {}
220 virtual ~TestCameraListener() {}
222 virtual void ItemAdded(const std::string& name,
223 const base::File::Info& info) OVERRIDE {
224 items_.push_back(name);
227 virtual void NoMoreItems() OVERRIDE {
231 virtual void DownloadedFile(const std::string& name,
232 base::File::Error error) OVERRIDE {
233 EXPECT_TRUE(content::BrowserThread::CurrentlyOn(
234 content::BrowserThread::UI));
235 downloads_.push_back(name);
239 virtual void DeviceRemoved() OVERRIDE {
243 std::vector<std::string> items() const { return items_; }
244 std::vector<std::string> downloads() const { return downloads_; }
245 bool completed() const { return completed_; }
246 bool removed() const { return removed_; }
247 base::File::Error last_error() const { return last_error_; }
250 std::vector<std::string> items_;
251 std::vector<std::string> downloads_;
254 base::File::Error last_error_;
257 class ImageCaptureDeviceManagerTest : public testing::Test {
259 virtual void SetUp() OVERRIDE {
260 monitor_ = TestStorageMonitor::CreateAndInstall();
263 virtual void TearDown() OVERRIDE {
264 TestStorageMonitor::Destroy();
267 MockICCameraDevice* AttachDevice(ImageCaptureDeviceManager* manager) {
268 // Ownership will be passed to the device browser delegate.
269 base::scoped_nsobject<MockICCameraDevice> device(
270 [[MockICCameraDevice alloc] init]);
271 id<ICDeviceBrowserDelegate> delegate = manager->device_browser();
272 [delegate deviceBrowser:nil didAddDevice:device moreComing:NO];
273 return device.autorelease();
276 void DetachDevice(ImageCaptureDeviceManager* manager,
277 ICCameraDevice* device) {
278 id<ICDeviceBrowserDelegate> delegate = manager->device_browser();
279 [delegate deviceBrowser:nil didRemoveDevice:device moreGoing:NO];
283 content::TestBrowserThreadBundle thread_bundle_;
284 TestStorageMonitor* monitor_;
285 TestCameraListener listener_;
288 TEST_F(ImageCaptureDeviceManagerTest, TestAttachDetach) {
289 ImageCaptureDeviceManager manager;
290 manager.SetNotifications(monitor_->receiver());
291 ICCameraDevice* device = AttachDevice(&manager);
292 std::vector<StorageInfo> devices = monitor_->GetAllAvailableStorages();
294 ASSERT_EQ(1U, devices.size());
295 EXPECT_EQ(std::string("ic:") + kDeviceId, devices[0].device_id());
297 DetachDevice(&manager, device);
298 devices = monitor_->GetAllAvailableStorages();
299 ASSERT_EQ(0U, devices.size());
302 TEST_F(ImageCaptureDeviceManagerTest, OpenCamera) {
303 ImageCaptureDeviceManager manager;
304 manager.SetNotifications(monitor_->receiver());
305 ICCameraDevice* device = AttachDevice(&manager);
307 EXPECT_FALSE(ImageCaptureDeviceManager::deviceForUUID(
310 base::scoped_nsobject<ImageCaptureDevice> camera(
311 [ImageCaptureDeviceManager::deviceForUUID(kDeviceId) retain]);
313 [camera setListener:listener_.AsWeakPtr()];
316 base::scoped_nsobject<MockICCameraFile> picture1(
317 [[MockICCameraFile alloc] init:@"pic1"]);
318 [camera cameraDevice:nil didAddItem:picture1];
319 base::scoped_nsobject<MockICCameraFile> picture2(
320 [[MockICCameraFile alloc] init:@"pic2"]);
321 [camera cameraDevice:nil didAddItem:picture2];
322 ASSERT_EQ(2U, listener_.items().size());
323 EXPECT_EQ("pic1", listener_.items()[0]);
324 EXPECT_EQ("pic2", listener_.items()[1]);
325 EXPECT_FALSE(listener_.completed());
327 [camera deviceDidBecomeReadyWithCompleteContentCatalog:nil];
329 ASSERT_EQ(2U, listener_.items().size());
330 EXPECT_TRUE(listener_.completed());
333 DetachDevice(&manager, device);
334 EXPECT_FALSE(ImageCaptureDeviceManager::deviceForUUID(kDeviceId));
337 TEST_F(ImageCaptureDeviceManagerTest, RemoveCamera) {
338 ImageCaptureDeviceManager manager;
339 manager.SetNotifications(monitor_->receiver());
340 ICCameraDevice* device = AttachDevice(&manager);
342 base::scoped_nsobject<ImageCaptureDevice> camera(
343 [ImageCaptureDeviceManager::deviceForUUID(kDeviceId) retain]);
345 [camera setListener:listener_.AsWeakPtr()];
348 [camera didRemoveDevice:device];
349 EXPECT_TRUE(listener_.removed());
352 TEST_F(ImageCaptureDeviceManagerTest, DownloadFile) {
353 ImageCaptureDeviceManager manager;
354 manager.SetNotifications(monitor_->receiver());
355 MockICCameraDevice* device = AttachDevice(&manager);
357 base::scoped_nsobject<ImageCaptureDevice> camera(
358 [ImageCaptureDeviceManager::deviceForUUID(kDeviceId) retain]);
360 [camera setListener:listener_.AsWeakPtr()];
363 std::string kTestFileName("pic1");
365 base::scoped_nsobject<MockICCameraFile> picture1(
366 [[MockICCameraFile alloc] init:base::SysUTF8ToNSString(kTestFileName)]);
367 [device addMediaFile:picture1];
368 [camera cameraDevice:nil didAddItem:picture1];
370 base::ScopedTempDir temp_dir;
371 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
373 EXPECT_EQ(0U, listener_.downloads().size());
375 // Test that a nonexistent file we ask to be downloaded will
376 // return us a not-found error.
377 base::FilePath temp_file = temp_dir.path().Append("tempfile");
378 [camera downloadFile:std::string("nonexistent") localPath:temp_file];
379 base::RunLoop().RunUntilIdle();
380 ASSERT_EQ(1U, listener_.downloads().size());
381 EXPECT_EQ("nonexistent", listener_.downloads()[0]);
382 EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, listener_.last_error());
384 // Test that an existing file we ask to be downloaded will end up in
385 // the location we specify. The mock system will copy testing file
386 // contents to a separate filename, mimicking the ImageCaptureCore
387 // library behavior. Our code then renames the file onto the requested
389 [camera downloadFile:kTestFileName localPath:temp_file];
390 base::RunLoop().RunUntilIdle();
392 ASSERT_EQ(2U, listener_.downloads().size());
393 EXPECT_EQ(kTestFileName, listener_.downloads()[1]);
394 ASSERT_EQ(base::File::FILE_OK, listener_.last_error());
395 char file_contents[5];
396 ASSERT_EQ(4, base::ReadFile(temp_file, file_contents,
397 strlen(kTestFileContents)));
398 EXPECT_EQ(kTestFileContents,
399 std::string(file_contents, strlen(kTestFileContents)));
401 [camera didRemoveDevice:device];
404 TEST_F(ImageCaptureDeviceManagerTest, TestSubdirectories) {
405 ImageCaptureDeviceManager manager;
406 manager.SetNotifications(monitor_->receiver());
407 MockICCameraDevice* device = AttachDevice(&manager);
409 base::scoped_nsobject<ImageCaptureDevice> camera(
410 [ImageCaptureDeviceManager::deviceForUUID(kDeviceId) retain]);
412 [camera setListener:listener_.AsWeakPtr()];
415 std::string kTestFileName("pic1");
416 base::scoped_nsobject<MockICCameraFile> picture1(
417 [[MockICCameraFile alloc] init:base::SysUTF8ToNSString(kTestFileName)]);
418 [picture1 setParent:base::SysUTF8ToNSString("dir")];
419 [device addMediaFile:picture1];
420 [camera cameraDevice:nil didAddItem:picture1];
422 base::ScopedTempDir temp_dir;
423 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
424 base::FilePath temp_file = temp_dir.path().Append("tempfile");
426 [camera downloadFile:("dir/" + kTestFileName) localPath:temp_file];
427 base::RunLoop().RunUntilIdle();
429 char file_contents[5];
430 ASSERT_EQ(4, base::ReadFile(temp_file, file_contents,
431 strlen(kTestFileContents)));
432 EXPECT_EQ(kTestFileContents,
433 std::string(file_contents, strlen(kTestFileContents)));
435 [camera didRemoveDevice:device];
438 } // namespace storage_monitor