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/files/file_path.h"
9 #include "base/files/file_util.h"
10 #include "base/files/scoped_temp_dir.h"
11 #include "base/mac/foundation_util.h"
12 #include "base/mac/sdk_forward_declarations.h"
13 #include "base/memory/weak_ptr.h"
14 #include "base/run_loop.h"
15 #include "components/storage_monitor/image_capture_device.h"
16 #include "components/storage_monitor/image_capture_device_manager.h"
17 #include "components/storage_monitor/test_storage_monitor.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "content/public/test/test_browser_thread_bundle.h"
20 #include "testing/gtest/include/gtest/gtest.h"
24 const char kDeviceId[] = "id";
25 const char kTestFileContents[] = "test";
29 // Private ICCameraDevice method needed to properly initialize the object.
30 @interface NSObject (PrivateAPIICCameraDevice)
31 - (id)initWithDictionary:(id)properties;
34 @interface MockICCameraDevice : ICCameraDevice {
36 base::scoped_nsobject<NSMutableArray> allMediaFiles_;
39 - (void)addMediaFile:(ICCameraFile*)file;
43 @implementation MockICCameraDevice
46 if ((self = [super initWithDictionary:[NSDictionary dictionary]])) {
51 - (NSString*)mountPoint {
59 - (NSString*)UUIDString {
60 return base::SysUTF8ToNSString(kDeviceId);
63 - (ICDeviceType)type {
64 return ICDeviceTypeCamera;
67 - (void)requestOpenSession {
70 - (void)requestCloseSession {
73 - (NSArray*)mediaFiles {
74 return allMediaFiles_;
77 - (void)addMediaFile:(ICCameraFile*)file {
78 if (!allMediaFiles_.get())
79 allMediaFiles_.reset([[NSMutableArray alloc] init]);
80 [allMediaFiles_ addObject:file];
83 // This method does approximately what the internal ImageCapture platform
84 // library is observed to do: take the download save-as filename and mangle
85 // it to attach an extension, then return that new filename to the caller
87 - (void)requestDownloadFile:(ICCameraFile*)file
88 options:(NSDictionary*)options
89 downloadDelegate:(id<ICCameraDeviceDownloadDelegate>)downloadDelegate
90 didDownloadSelector:(SEL)selector
91 contextInfo:(void*)contextInfo {
92 base::FilePath saveDir(base::SysNSStringToUTF8(
93 [[options objectForKey:ICDownloadsDirectoryURL] path]));
94 std::string saveAsFilename =
95 base::SysNSStringToUTF8([options objectForKey:ICSaveAsFilename]);
96 // It appears that the ImageCapture library adds an extension to the requested
97 // filename. Do that here to require a rename.
98 saveAsFilename += ".jpg";
99 base::FilePath toBeSaved = saveDir.Append(saveAsFilename);
100 ASSERT_EQ(static_cast<int>(strlen(kTestFileContents)),
101 base::WriteFile(toBeSaved, kTestFileContents,
102 strlen(kTestFileContents)));
104 NSMutableDictionary* returnOptions =
105 [NSMutableDictionary dictionaryWithDictionary:options];
106 [returnOptions setObject:base::SysUTF8ToNSString(saveAsFilename)
107 forKey:ICSavedFilename];
109 [static_cast<NSObject<ICCameraDeviceDownloadDelegate>*>(downloadDelegate)
112 options:returnOptions
113 contextInfo:contextInfo];
118 @interface MockICCameraFolder : ICCameraFolder {
120 base::scoped_nsobject<NSString> name_;
123 - (id)initWithName:(NSString*)name;
127 @implementation MockICCameraFolder
129 - (id)initWithName:(NSString*)name {
130 if ((self = [super init])) {
131 name_.reset([name retain]);
140 - (ICCameraFolder*)parentFolder {
146 @interface MockICCameraFile : ICCameraFile {
148 base::scoped_nsobject<NSString> name_;
149 base::scoped_nsobject<NSDate> date_;
150 base::scoped_nsobject<MockICCameraFolder> parent_;
153 - (id)init:(NSString*)name;
154 - (void)setParent:(NSString*)parent;
158 @implementation MockICCameraFile
160 - (id)init:(NSString*)name {
161 if ((self = [super init])) {
162 name_.reset([name retain]);
163 date_.reset([[NSDate dateWithNaturalLanguageString:@"12/12/12"] retain]);
168 - (void)setParent:(NSString*)parent {
169 parent_.reset([[MockICCameraFolder alloc] initWithName:parent]);
172 - (ICCameraFolder*)parentFolder {
173 return parent_.get();
181 return base::mac::CFToNSCast(kUTTypeImage);
184 - (NSDate*)modificationDate {
188 - (NSDate*)creationDate {
198 namespace storage_monitor {
200 class TestCameraListener
201 : public ImageCaptureDeviceListener,
202 public base::SupportsWeakPtr<TestCameraListener> {
207 last_error_(base::File::FILE_ERROR_INVALID_URL) {}
208 ~TestCameraListener() override {}
210 void ItemAdded(const std::string& name,
211 const base::File::Info& info) override {
212 items_.push_back(name);
215 void NoMoreItems() override { completed_ = true; }
217 void DownloadedFile(const std::string& name,
218 base::File::Error error) override {
219 EXPECT_TRUE(content::BrowserThread::CurrentlyOn(
220 content::BrowserThread::UI));
221 downloads_.push_back(name);
225 void DeviceRemoved() override { removed_ = true; }
227 std::vector<std::string> items() const { return items_; }
228 std::vector<std::string> downloads() const { return downloads_; }
229 bool completed() const { return completed_; }
230 bool removed() const { return removed_; }
231 base::File::Error last_error() const { return last_error_; }
234 std::vector<std::string> items_;
235 std::vector<std::string> downloads_;
238 base::File::Error last_error_;
241 class ImageCaptureDeviceManagerTest : public testing::Test {
243 virtual void SetUp() override {
244 monitor_ = TestStorageMonitor::CreateAndInstall();
247 virtual void TearDown() override {
248 TestStorageMonitor::Destroy();
251 MockICCameraDevice* AttachDevice(ImageCaptureDeviceManager* manager) {
252 // Ownership will be passed to the device browser delegate.
253 base::scoped_nsobject<MockICCameraDevice> device(
254 [[MockICCameraDevice alloc] init]);
255 id<ICDeviceBrowserDelegate> delegate = manager->device_browser();
256 [delegate deviceBrowser:nil didAddDevice:device moreComing:NO];
257 return device.autorelease();
260 void DetachDevice(ImageCaptureDeviceManager* manager,
261 ICCameraDevice* device) {
262 id<ICDeviceBrowserDelegate> delegate = manager->device_browser();
263 [delegate deviceBrowser:nil didRemoveDevice:device moreGoing:NO];
267 content::TestBrowserThreadBundle thread_bundle_;
268 TestStorageMonitor* monitor_;
269 TestCameraListener listener_;
272 TEST_F(ImageCaptureDeviceManagerTest, TestAttachDetach) {
273 ImageCaptureDeviceManager manager;
274 manager.SetNotifications(monitor_->receiver());
275 ICCameraDevice* device = AttachDevice(&manager);
276 std::vector<StorageInfo> devices = monitor_->GetAllAvailableStorages();
278 ASSERT_EQ(1U, devices.size());
279 EXPECT_EQ(std::string("ic:") + kDeviceId, devices[0].device_id());
281 DetachDevice(&manager, device);
282 devices = monitor_->GetAllAvailableStorages();
283 ASSERT_EQ(0U, devices.size());
286 TEST_F(ImageCaptureDeviceManagerTest, OpenCamera) {
287 ImageCaptureDeviceManager manager;
288 manager.SetNotifications(monitor_->receiver());
289 ICCameraDevice* device = AttachDevice(&manager);
291 EXPECT_FALSE(ImageCaptureDeviceManager::deviceForUUID(
294 base::scoped_nsobject<ImageCaptureDevice> camera(
295 [ImageCaptureDeviceManager::deviceForUUID(kDeviceId) retain]);
297 [camera setListener:listener_.AsWeakPtr()];
300 base::scoped_nsobject<MockICCameraFile> picture1(
301 [[MockICCameraFile alloc] init:@"pic1"]);
302 [camera cameraDevice:nil didAddItem:picture1];
303 base::scoped_nsobject<MockICCameraFile> picture2(
304 [[MockICCameraFile alloc] init:@"pic2"]);
305 [camera cameraDevice:nil didAddItem:picture2];
306 ASSERT_EQ(2U, listener_.items().size());
307 EXPECT_EQ("pic1", listener_.items()[0]);
308 EXPECT_EQ("pic2", listener_.items()[1]);
309 EXPECT_FALSE(listener_.completed());
311 [camera deviceDidBecomeReadyWithCompleteContentCatalog:nil];
313 ASSERT_EQ(2U, listener_.items().size());
314 EXPECT_TRUE(listener_.completed());
317 DetachDevice(&manager, device);
318 EXPECT_FALSE(ImageCaptureDeviceManager::deviceForUUID(kDeviceId));
321 TEST_F(ImageCaptureDeviceManagerTest, RemoveCamera) {
322 ImageCaptureDeviceManager manager;
323 manager.SetNotifications(monitor_->receiver());
324 ICCameraDevice* device = AttachDevice(&manager);
326 base::scoped_nsobject<ImageCaptureDevice> camera(
327 [ImageCaptureDeviceManager::deviceForUUID(kDeviceId) retain]);
329 [camera setListener:listener_.AsWeakPtr()];
332 [camera didRemoveDevice:device];
333 EXPECT_TRUE(listener_.removed());
336 TEST_F(ImageCaptureDeviceManagerTest, DownloadFile) {
337 ImageCaptureDeviceManager manager;
338 manager.SetNotifications(monitor_->receiver());
339 MockICCameraDevice* device = AttachDevice(&manager);
341 base::scoped_nsobject<ImageCaptureDevice> camera(
342 [ImageCaptureDeviceManager::deviceForUUID(kDeviceId) retain]);
344 [camera setListener:listener_.AsWeakPtr()];
347 std::string kTestFileName("pic1");
349 base::scoped_nsobject<MockICCameraFile> picture1(
350 [[MockICCameraFile alloc] init:base::SysUTF8ToNSString(kTestFileName)]);
351 [device addMediaFile:picture1];
352 [camera cameraDevice:nil didAddItem:picture1];
354 base::ScopedTempDir temp_dir;
355 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
357 EXPECT_EQ(0U, listener_.downloads().size());
359 // Test that a nonexistent file we ask to be downloaded will
360 // return us a not-found error.
361 base::FilePath temp_file = temp_dir.path().Append("tempfile");
362 [camera downloadFile:std::string("nonexistent") localPath:temp_file];
363 base::RunLoop().RunUntilIdle();
364 ASSERT_EQ(1U, listener_.downloads().size());
365 EXPECT_EQ("nonexistent", listener_.downloads()[0]);
366 EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, listener_.last_error());
368 // Test that an existing file we ask to be downloaded will end up in
369 // the location we specify. The mock system will copy testing file
370 // contents to a separate filename, mimicking the ImageCaptureCore
371 // library behavior. Our code then renames the file onto the requested
373 [camera downloadFile:kTestFileName localPath:temp_file];
374 base::RunLoop().RunUntilIdle();
376 ASSERT_EQ(2U, listener_.downloads().size());
377 EXPECT_EQ(kTestFileName, listener_.downloads()[1]);
378 ASSERT_EQ(base::File::FILE_OK, listener_.last_error());
379 char file_contents[5];
380 ASSERT_EQ(4, base::ReadFile(temp_file, file_contents,
381 strlen(kTestFileContents)));
382 EXPECT_EQ(kTestFileContents,
383 std::string(file_contents, strlen(kTestFileContents)));
385 [camera didRemoveDevice:device];
388 TEST_F(ImageCaptureDeviceManagerTest, TestSubdirectories) {
389 ImageCaptureDeviceManager manager;
390 manager.SetNotifications(monitor_->receiver());
391 MockICCameraDevice* device = AttachDevice(&manager);
393 base::scoped_nsobject<ImageCaptureDevice> camera(
394 [ImageCaptureDeviceManager::deviceForUUID(kDeviceId) retain]);
396 [camera setListener:listener_.AsWeakPtr()];
399 std::string kTestFileName("pic1");
400 base::scoped_nsobject<MockICCameraFile> picture1(
401 [[MockICCameraFile alloc] init:base::SysUTF8ToNSString(kTestFileName)]);
402 [picture1 setParent:base::SysUTF8ToNSString("dir")];
403 [device addMediaFile:picture1];
404 [camera cameraDevice:nil didAddItem:picture1];
406 base::ScopedTempDir temp_dir;
407 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
408 base::FilePath temp_file = temp_dir.path().Append("tempfile");
410 [camera downloadFile:("dir/" + kTestFileName) localPath:temp_file];
411 base::RunLoop().RunUntilIdle();
413 char file_contents[5];
414 ASSERT_EQ(4, base::ReadFile(temp_file, file_contents,
415 strlen(kTestFileContents)));
416 EXPECT_EQ(kTestFileContents,
417 std::string(file_contents, strlen(kTestFileContents)));
419 [camera didRemoveDevice:device];
422 } // namespace storage_monitor