diff options
author | gbillock@chromium.org <gbillock@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-01-10 00:15:19 +0000 |
---|---|---|
committer | gbillock@chromium.org <gbillock@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-01-10 00:15:19 +0000 |
commit | c43a0d65861efed2d4e80342e045cae91343be49 (patch) | |
tree | 0c33d007484fe5cc3cd17214e8b2b06f906f5afd /chrome | |
parent | 69450a09061d300a3317e3762092155d543ad2ac (diff) | |
download | chromium_src-c43a0d65861efed2d4e80342e045cae91343be49.zip chromium_src-c43a0d65861efed2d4e80342e045cae91343be49.tar.gz chromium_src-c43a0d65861efed2d4e80342e045cae91343be49.tar.bz2 |
[Media Galleries] Add an ImageCaptureCore listener for Mac. (part 2)
This listener uses the ImageCapture API to watch for attach and detach
events of PTP devices and other devices which can be read through the
library. It forwards such notifications through the SystemMonitor.
Also provides an API for clients to access such devices directly and
retrieve the media contents from them.
R=thestig@chromium.org,sail@chromium.org
BUG=151681
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=175471
Review URL: https://chromiumcodereview.appspot.com/11442057
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@175938 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/browser/chrome_browser_main_mac.h | 3 | ||||
-rw-r--r-- | chrome/browser/chrome_browser_main_mac.mm | 2 | ||||
-rw-r--r-- | chrome/browser/system_monitor/disk_info_mac.h | 5 | ||||
-rw-r--r-- | chrome/browser/system_monitor/disk_info_mac.mm | 13 | ||||
-rw-r--r-- | chrome/browser/system_monitor/image_capture_device.h | 74 | ||||
-rw-r--r-- | chrome/browser/system_monitor/image_capture_device.mm | 202 | ||||
-rw-r--r-- | chrome/browser/system_monitor/image_capture_device_manager.h | 42 | ||||
-rw-r--r-- | chrome/browser/system_monitor/image_capture_device_manager.mm | 139 | ||||
-rw-r--r-- | chrome/browser/system_monitor/image_capture_device_manager_unittest.mm | 354 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 5 | ||||
-rw-r--r-- | chrome/chrome_tests_unit.gypi | 1 |
11 files changed, 822 insertions, 18 deletions
diff --git a/chrome/browser/chrome_browser_main_mac.h b/chrome/browser/chrome_browser_main_mac.h index 46388d8..82f73c6 100644 --- a/chrome/browser/chrome_browser_main_mac.h +++ b/chrome/browser/chrome_browser_main_mac.h @@ -9,6 +9,7 @@ #include "base/memory/ref_counted.h" namespace chrome { +class ImageCaptureDeviceManager; class RemovableDeviceNotificationsMac; } @@ -31,6 +32,8 @@ class ChromeBrowserMainPartsMac : public ChromeBrowserMainPartsPosix { scoped_refptr<chrome::RemovableDeviceNotificationsMac> removable_device_notifications_mac_; + scoped_ptr<chrome::ImageCaptureDeviceManager> image_capture_device_manager_; + DISALLOW_COPY_AND_ASSIGN(ChromeBrowserMainPartsMac); }; diff --git a/chrome/browser/chrome_browser_main_mac.mm b/chrome/browser/chrome_browser_main_mac.mm index fc4bb44..27658c0 100644 --- a/chrome/browser/chrome_browser_main_mac.mm +++ b/chrome/browser/chrome_browser_main_mac.mm @@ -22,6 +22,7 @@ #include "chrome/browser/mac/keychain_reauthorize.h" #import "chrome/browser/mac/keystone_glue.h" #include "chrome/browser/metrics/metrics_service.h" +#include "chrome/browser/system_monitor/image_capture_device_manager.h" #include "chrome/browser/system_monitor/removable_device_notifications_mac.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_switches.h" @@ -283,6 +284,7 @@ void ChromeBrowserMainPartsMac::PreMainMessageLoopStart() { void ChromeBrowserMainPartsMac::PreProfileInit() { removable_device_notifications_mac_ = new chrome::RemovableDeviceNotificationsMac(); + image_capture_device_manager_.reset(new chrome::ImageCaptureDeviceManager); ChromeBrowserMainPartsPosix::PreProfileInit(); } diff --git a/chrome/browser/system_monitor/disk_info_mac.h b/chrome/browser/system_monitor/disk_info_mac.h index 106c2c7..2c04648 100644 --- a/chrome/browser/system_monitor/disk_info_mac.h +++ b/chrome/browser/system_monitor/disk_info_mac.h @@ -22,11 +22,6 @@ class DiskInfoMac { // dictionary. This function must be called on the file thread. static DiskInfoMac BuildDiskInfoOnFileThread(CFDictionaryRef dict); - // Construct a disk info object from info from an ImageCature device. - static DiskInfoMac BuildDiskInfoFromICDevice(std::string device_id, - string16 device_name, - FilePath mount_point); - const std::string& bsd_name() const { return bsd_name_; } const std::string& device_id() const { return device_id_; } const std::string& model_name() const { return model_name_; } diff --git a/chrome/browser/system_monitor/disk_info_mac.mm b/chrome/browser/system_monitor/disk_info_mac.mm index 77d21c1..f404a95 100644 --- a/chrome/browser/system_monitor/disk_info_mac.mm +++ b/chrome/browser/system_monitor/disk_info_mac.mm @@ -114,17 +114,4 @@ DiskInfoMac DiskInfoMac::BuildDiskInfoOnFileThread(CFDictionaryRef dict) { return info; } -// TODO(gbillock): Make sure this gets test coverage. -// static -DiskInfoMac DiskInfoMac::BuildDiskInfoFromICDevice(std::string device_id, - string16 device_name, - FilePath mount_point) { - DiskInfoMac info; - info.device_id_ = device_id; - info.device_name_ = device_name; - info.mount_point_ = mount_point; - info.type_ = MediaStorageUtil::MAC_IMAGE_CAPTURE; - return info; -} - } // namesapce chrome diff --git a/chrome/browser/system_monitor/image_capture_device.h b/chrome/browser/system_monitor/image_capture_device.h new file mode 100644 index 0000000..9d8c610 --- /dev/null +++ b/chrome/browser/system_monitor/image_capture_device.h @@ -0,0 +1,74 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_SYSTEM_MONITOR_IMAGE_CAPTURE_DEVICE_H_ +#define CHROME_BROWSER_SYSTEM_MONITOR_IMAGE_CAPTURE_DEVICE_H_ + +#import <Foundation/Foundation.h> +#import <ImageCaptureCore/ImageCaptureCore.h> + +#include "base/file_path.h" +#include "base/mac/cocoa_protocols.h" +#include "base/mac/foundation_util.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_nsobject.h" +#include "base/memory/weak_ptr.h" +#include "base/platform_file.h" +#include "base/string_util.h" +#include "base/synchronization/lock.h" +#include "base/sys_string_conversions.h" + +// Clients use this listener interface to get notifications about +// events happening as a particular ImageCapture device is interacted with. +// Clients drive the interaction through the ImageCaptureDeviceManager +// and the ImageCaptureDevice classes, and get notifications of +// events through this interface. +class ImageCaptureDeviceListener { + public: + virtual ~ImageCaptureDeviceListener() {} + + // Get a notification that a particular item has been found on the device. + // These calls will come automatically after a new device is initialized. + virtual void ItemAdded(const std::string& name, + const base::PlatformFileInfo& info) = 0; + + // Called when there are no more items to retrieve. + virtual void NoMoreItems() = 0; + + // Called upon completion of a file download request. The |path| is the + // requested download file. Note: in NOT_FOUND error case, can be called + // inline with the download request. + virtual void DownloadedFile(const std::string& name, + base::PlatformFileError error) = 0; + + // Called to let the client know the device is removed. The client should + // set the ImageCaptureDevice listener to null upon receiving this call. + virtual void DeviceRemoved() = 0; +}; + +// Interface to a camera device found by ImageCaptureCore. This class manages a +// session to the camera and provides the backing interactions to present the +// media files on it to the filesystem delegate. FilePaths will be artificial, +// like "/$device_id/" + name. +// Note that all interactions with this class must happen on the UI thread. +@interface ImageCaptureDevice + : NSObject<ICCameraDeviceDelegate, ICCameraDeviceDownloadDelegate> { + @private + scoped_nsobject<ICCameraDevice> camera_; + base::WeakPtr<ImageCaptureDeviceListener> listener_; +} + +- (id)initWithCameraDevice:(ICCameraDevice*)cameraDevice; +- (void)setListener:(base::WeakPtr<ImageCaptureDeviceListener>)listener; +- (void)open; +- (void)close; + +// Download the given |file| to the provided |local_path|. Completion notice +// will be sent to the listener's DownloadedFile method. +- (void)downloadFile:(const std::string&)name + localPath:(const FilePath&)localPath; + +@end + +#endif // CHROME_BROWSER_SYSTEM_MONITOR_IMAGE_CAPTURE_DEVICE_H_ diff --git a/chrome/browser/system_monitor/image_capture_device.mm b/chrome/browser/system_monitor/image_capture_device.mm new file mode 100644 index 0000000..d1b0c80 --- /dev/null +++ b/chrome/browser/system_monitor/image_capture_device.mm @@ -0,0 +1,202 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "chrome/browser/system_monitor/image_capture_device.h" + +#include "base/file_util.h" +#include "base/mac/mac_util.h" +#include "base/system_monitor/system_monitor.h" +#include "chrome/browser/system_monitor/media_storage_util.h" +#include "content/public/browser/browser_thread.h" + +namespace { + +void RenameFile(const FilePath& downloaded_filename, + const FilePath& desired_filename, + base::PlatformFileError* result) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); + bool success = file_util::ReplaceFile(downloaded_filename, desired_filename); + *result = success ? base::PLATFORM_FILE_OK + : base::PLATFORM_FILE_ERROR_NOT_FOUND; +} + +void ReturnRenameResultToListener( + base::WeakPtr<ImageCaptureDeviceListener> listener, + const std::string& name, + base::PlatformFileError* result) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + scoped_ptr<base::PlatformFileError> result_deleter(result); + if (listener) + listener->DownloadedFile(name, *result); +} + +base::Time NSDateToBaseTime(NSDate* date) { + return base::Time::FromDoubleT([date timeIntervalSince1970]); +} + +} // namespace + +@implementation ImageCaptureDevice + +- (id)initWithCameraDevice:(ICCameraDevice*)cameraDevice { + if ((self = [super init])) { + camera_.reset([cameraDevice retain]); + [camera_ setDelegate:self]; + } + return self; +} + +- (void)dealloc { + // Make sure the session was closed and listener set to null + // before destruction. + DCHECK(![camera_ delegate]); + DCHECK(!listener_); + [super dealloc]; +} + +- (void)setListener:(base::WeakPtr<ImageCaptureDeviceListener>)listener { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + listener_ = listener; +} + +- (void)open { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + DCHECK(listener_); + [camera_ requestOpenSession]; +} + +- (void)close { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + [camera_ requestCloseSession]; + [camera_ setDelegate:nil]; + listener_.reset(); +} + +- (void)downloadFile:(const std::string&)name + localPath:(const FilePath&)localPath { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + // Find the file with that name and start download. + for (ICCameraItem* item in [camera_ mediaFiles]) { + std::string itemName = base::SysNSStringToUTF8([item name]); + if (itemName == name) { + // To create save options for ImageCapture, we need to + // split the target filename into directory/name + // and encode the directory as a URL. + NSString* saveDirectory = + base::mac::FilePathToNSString(localPath.DirName()); + NSString* saveFilename = + base::mac::FilePathToNSString(localPath.BaseName()); + + NSMutableDictionary* options = + [NSMutableDictionary dictionaryWithCapacity:3]; + [options setObject:[NSURL fileURLWithPath:saveDirectory isDirectory:YES] + forKey:ICDownloadsDirectoryURL]; + [options setObject:saveFilename forKey:ICSaveAsFilename]; + [options setObject:[NSNumber numberWithBool:YES] forKey:ICOverwrite]; + + [camera_ requestDownloadFile:base::mac::ObjCCastStrict<ICCameraFile>(item) + options:options + downloadDelegate:self + didDownloadSelector: + @selector(didDownloadFile:error:options:contextInfo:) + contextInfo:NULL]; + return; + } + } + + if (listener_) + listener_->DownloadedFile(name, base::PLATFORM_FILE_ERROR_NOT_FOUND); +} + +- (void)cameraDevice:(ICCameraDevice*)camera didAddItem:(ICCameraItem*)item { + std::string name = base::SysNSStringToUTF8([item name]); + base::PlatformFileInfo info; + if ([[item UTI] isEqualToString:base::mac::CFToNSCast(kUTTypeFolder)]) + info.is_directory = true; + else + info.size = [base::mac::ObjCCastStrict<ICCameraFile>(item) fileSize]; + info.last_modified = NSDateToBaseTime([item modificationDate]); + info.creation_time = NSDateToBaseTime([item creationDate]); + info.last_accessed = info.last_modified; + + if (listener_) + listener_->ItemAdded(name, info); +} + +- (void)cameraDevice:(ICCameraDevice*)camera didAddItems:(NSArray*)items { + for (ICCameraItem* item in items) + [self cameraDevice:camera didAddItem:item]; +} + +- (void)didRemoveDevice:(ICDevice*)device { + device.delegate = NULL; + if (listener_) + listener_->DeviceRemoved(); +} + +// Notifies that a session was opened with the given device; potentially +// with an error. +- (void)device:(ICDevice*)device didOpenSessionWithError:(NSError*)error { + if (error) + [self didRemoveDevice:camera_]; +} + +- (void)device:(ICDevice*)device didEncounterError:(NSError*)error { + if (error && listener_) + listener_->DeviceRemoved(); +} + +// When this message is received, all media metadata is now loaded. +- (void)deviceDidBecomeReadyWithCompleteContentCatalog:(ICDevice*)device { + if (listener_) + listener_->NoMoreItems(); +} + +// For 10.7 and earlier, this method has the semantics of +// deviceDidBecomeReadyWithCompleteContentCatalog. For 10.8++, it has +// the semantics of notifying that the device can receive commands. +- (void)deviceDidBecomeReady:(ICDevice*)device { + if (listener_ && base::mac::IsOSLionOrEarlier()) + listener_->NoMoreItems(); +} + +- (void)didDownloadFile:(ICCameraFile*)file + error:(NSError*)error + options:(NSDictionary*)options + contextInfo:(void*)contextInfo { + std::string name = base::SysNSStringToUTF8([file name]); + + if (error) { + if (listener_) + listener_->DownloadedFile(name, base::PLATFORM_FILE_ERROR_FAILED); + return; + } + + std::string savedFilename = + base::SysNSStringToUTF8([options objectForKey:ICSavedFilename]); + std::string saveAsFilename = + base::SysNSStringToUTF8([options objectForKey:ICSaveAsFilename]); + if (savedFilename == saveAsFilename) { + if (listener_) + listener_->DownloadedFile(name, base::PLATFORM_FILE_OK); + return; + } + + // ImageCapture did not save the file into the name we gave it in the + // options. It picks a new name according to its best lights, so we need + // to rename the file. + FilePath saveDir(base::SysNSStringToUTF8( + [[options objectForKey:ICDownloadsDirectoryURL] path])); + FilePath saveAsPath = saveDir.Append(saveAsFilename); + FilePath savedPath = saveDir.Append(savedFilename); + // Shared result value from file-copy closure to tell-listener closure. + base::PlatformFileError* copyResult = new base::PlatformFileError(); + content::BrowserThread::PostTaskAndReply( + content::BrowserThread::FILE, + FROM_HERE, + base::Bind(&RenameFile, savedPath, saveAsPath, copyResult), + base::Bind(&ReturnRenameResultToListener, listener_, name, copyResult)); +} + +@end // ImageCaptureDevice diff --git a/chrome/browser/system_monitor/image_capture_device_manager.h b/chrome/browser/system_monitor/image_capture_device_manager.h new file mode 100644 index 0000000..a10ae06 --- /dev/null +++ b/chrome/browser/system_monitor/image_capture_device_manager.h @@ -0,0 +1,42 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_SYSTEM_MONITOR_IMAGE_CAPTURE_DEVICE_MANAGER_H_ +#define CHROME_BROWSER_SYSTEM_MONITOR_IMAGE_CAPTURE_DEVICE_MANAGER_H_ + +#import <Foundation/Foundation.h> +#include <string> + +#include "base/memory/scoped_nsobject.h" + +@protocol ICDeviceBrowserDelegate; +@class ImageCaptureDevice; +@class ImageCaptureDeviceManagerImpl; + +namespace chrome { + +// Upon creation, begins monitoring for any attached devices using the +// ImageCapture API. Notifies clients of the presence of such devices +// (i.e. cameras, USB cards) using the SystemMonitor and makes them +// available using |deviceForUUID|. +class ImageCaptureDeviceManager { + public: + ImageCaptureDeviceManager(); + ~ImageCaptureDeviceManager(); + + // The UUIDs passed here are available in the device attach notifications + // given through SystemMonitor. They're gotten by cracking the device ID + // and taking the unique ID output. + static ImageCaptureDevice* deviceForUUID(const std::string& uuid); + + // Returns a weak pointer to the internal ImageCapture interface protocol. + id<ICDeviceBrowserDelegate> device_browser(); + + private: + scoped_nsobject<ImageCaptureDeviceManagerImpl> device_browser_; +}; + +} // namespace chrome + +#endif // CHROME_BROWSER_SYSTEM_MONITOR_IMAGE_CAPTURE_DEVICE_MANAGER_H_ diff --git a/chrome/browser/system_monitor/image_capture_device_manager.mm b/chrome/browser/system_monitor/image_capture_device_manager.mm new file mode 100644 index 0000000..dda843e --- /dev/null +++ b/chrome/browser/system_monitor/image_capture_device_manager.mm @@ -0,0 +1,139 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/system_monitor/image_capture_device_manager.h" + +#import <ImageCaptureCore/ImageCaptureCore.h> + +#include "base/file_util.h" +#include "base/system_monitor/system_monitor.h" +#include "chrome/browser/system_monitor/disk_info_mac.h" +#import "chrome/browser/system_monitor/image_capture_device.h" +#include "chrome/browser/system_monitor/media_storage_util.h" +#include "content/public/browser/browser_thread.h" + +namespace { + +chrome::ImageCaptureDeviceManager* g_image_capture_device_manager = NULL; + +} // namespace + +// This class is the surface for the Mac ICDeviceBrowser ImageCaptureCore API. +// Owned by the ChromeBrowserParts and has browser process lifetime. Upon +// creation, it gets a list of attached media volumes (asynchronously) which +// it will eventually forward to the SystemMonitor as removable storage +// notifications. It will also set up an ImageCaptureCore listener to be +// told when new devices/volumes are discovered and existing ones are removed. +@interface ImageCaptureDeviceManagerImpl + : NSObject<ICDeviceBrowserDelegate> { + @private + scoped_nsobject<ICDeviceBrowser> deviceBrowser_; + scoped_nsobject<NSMutableArray> cameras_; +} + +- (void)close; + +// The UUIDs passed here are available in the device attach notifications +// given through SystemMonitor. They're gotten by cracking the device ID +// and taking the unique ID output. +- (ImageCaptureDevice*)deviceForUUID:(const std::string&)uuid; + +@end + +@implementation ImageCaptureDeviceManagerImpl + +- (id)init { + if ((self = [super init])) { + cameras_.reset([[NSMutableArray alloc] init]); + + deviceBrowser_.reset([[ICDeviceBrowser alloc] init]); + [deviceBrowser_ setDelegate:self]; + [deviceBrowser_ setBrowsedDeviceTypeMask: + [deviceBrowser_ browsedDeviceTypeMask] | + ICDeviceTypeMaskCamera | ICDeviceLocationTypeMaskLocal]; + [deviceBrowser_ start]; + } + return self; +} + +- (void)close { + [deviceBrowser_ setDelegate:nil]; + [deviceBrowser_ stop]; + deviceBrowser_.reset(); + cameras_.reset(); +} + +- (ImageCaptureDevice*) deviceForUUID:(const std::string&)uuid { + for (ICCameraDevice* camera in cameras_.get()) { + NSString* camera_id = [camera UUIDString]; + if (base::SysNSStringToUTF8(camera_id) == uuid) { + return [[[ImageCaptureDevice alloc] + initWithCameraDevice:camera] autorelease]; + } + } + return nil; +} + +- (void)deviceBrowser:(ICDeviceBrowser*)browser + didAddDevice:(ICDevice*)addedDevice + moreComing:(BOOL)moreComing { + if (!(addedDevice.type & ICDeviceTypeCamera)) + return; + + ICCameraDevice* cameraDevice = + base::mac::ObjCCastStrict<ICCameraDevice>(addedDevice); + + [cameras_ addObject:addedDevice]; + + // TODO(gbillock): use [cameraDevice mountPoint] here when possible. + base::SystemMonitor::Get()->ProcessRemovableStorageAttached( + chrome::MediaStorageUtil::MakeDeviceId( + chrome::MediaStorageUtil::MAC_IMAGE_CAPTURE, + base::SysNSStringToUTF8([cameraDevice UUIDString])), + base::SysNSStringToUTF16([cameraDevice name]), ""); +} + +- (void)deviceBrowser:(ICDeviceBrowser*)browser + didRemoveDevice:(ICDevice*)device + moreGoing:(BOOL)moreGoing { + if (!(device.type & ICDeviceTypeCamera)) + return; + + std::string uuid = base::SysNSStringToUTF8([device UUIDString]); + + // May delete |device|. + [cameras_ removeObject:device]; + + base::SystemMonitor::Get()->ProcessRemovableStorageDetached( + chrome::MediaStorageUtil::MakeDeviceId( + chrome::MediaStorageUtil::MAC_IMAGE_CAPTURE, uuid)); +} + +@end // ImageCaptureDeviceManagerImpl + +namespace chrome { + +ImageCaptureDeviceManager::ImageCaptureDeviceManager() { + device_browser_.reset([[ImageCaptureDeviceManagerImpl alloc] init]); + g_image_capture_device_manager = this; +} + +ImageCaptureDeviceManager::~ImageCaptureDeviceManager() { + g_image_capture_device_manager = NULL; + [device_browser_ close]; +} + +// static +ImageCaptureDevice* ImageCaptureDeviceManager::deviceForUUID( + const std::string& uuid) { + ImageCaptureDeviceManagerImpl* manager = + g_image_capture_device_manager->device_browser_; + return [manager deviceForUUID:uuid]; +} + +id<ICDeviceBrowserDelegate> ImageCaptureDeviceManager::device_browser() { + return device_browser_.get(); +} + +} // namespace chrome diff --git a/chrome/browser/system_monitor/image_capture_device_manager_unittest.mm b/chrome/browser/system_monitor/image_capture_device_manager_unittest.mm new file mode 100644 index 0000000..7d4b12f --- /dev/null +++ b/chrome/browser/system_monitor/image_capture_device_manager_unittest.mm @@ -0,0 +1,354 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + + +#import <Foundation/Foundation.h> +#import <ImageCaptureCore/ImageCaptureCore.h> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/mac/foundation_util.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop.h" +#include "base/system_monitor/system_monitor.h" +#include "chrome/browser/system_monitor/image_capture_device.h" +#include "chrome/browser/system_monitor/image_capture_device_manager.h" +#include "content/public/test/test_browser_thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const char kDeviceId[] = "id"; +const char kTestFileContents[] = "test"; + +} // namespace + +// Private ICCameraDevice method needed to properly initialize the object. +@interface NSObject (PrivateAPIICCameraDevice) +- (id)initWithDictionary:(id)properties; +@end + +@interface MockICCameraDevice : ICCameraDevice { + @private + scoped_nsobject<NSMutableArray> allMediaFiles_; +} + +- (void)addMediaFile:(ICCameraFile*)file; + +@end + +@implementation MockICCameraDevice + +- (id)init { + if ((self = [super initWithDictionary:[NSDictionary dictionary]])) { + } + return self; +} + +- (NSString*)mountPoint { + return @"mountPoint"; +} + +- (NSString*)name { + return @"name"; +} + +- (NSString*)UUIDString { + return base::SysUTF8ToNSString(kDeviceId); +} + +- (ICDeviceType)type { + return ICDeviceTypeCamera; +} + +- (void)requestOpenSession { +} + +- (void)requestCloseSession { +} + +- (NSArray*)mediaFiles { + return allMediaFiles_; +} + +- (void)addMediaFile:(ICCameraFile*)file { + if (!allMediaFiles_.get()) + allMediaFiles_.reset([[NSMutableArray alloc] init]); + [allMediaFiles_ addObject:file]; +} + +// This method does approximately what the internal ImageCapture platform +// library is observed to do: take the download save-as filename and mangle +// it to attach an extension, then return that new filename to the caller +// in the options. +- (void)requestDownloadFile:(ICCameraFile*)file + options:(NSDictionary*)options + downloadDelegate:(id<ICCameraDeviceDownloadDelegate>)downloadDelegate + didDownloadSelector:(SEL)selector + contextInfo:(void*)contextInfo { + FilePath saveDir(base::SysNSStringToUTF8( + [[options objectForKey:ICDownloadsDirectoryURL] path])); + std::string saveAsFilename = + base::SysNSStringToUTF8([options objectForKey:ICSaveAsFilename]); + // It appears that the ImageCapture library adds an extension to the requested + // filename. Do that here to require a rename. + saveAsFilename += ".jpg"; + FilePath toBeSaved = saveDir.Append(saveAsFilename); + ASSERT_EQ(static_cast<int>(strlen(kTestFileContents)), + file_util::WriteFile(toBeSaved, kTestFileContents, + strlen(kTestFileContents))); + + NSMutableDictionary* returnOptions = + [NSMutableDictionary dictionaryWithDictionary:options]; + [returnOptions setObject:base::SysUTF8ToNSString(saveAsFilename) + forKey:ICSavedFilename]; + + [downloadDelegate didDownloadFile:file + error:nil + options:returnOptions + contextInfo:contextInfo]; +} + +@end + +@interface MockICCameraFile : ICCameraFile { + @private + scoped_nsobject<NSString> name_; + scoped_nsobject<NSDate> date_; +} + +- (id)init:(NSString*)name; + +@end + +@implementation MockICCameraFile + +- (id)init:(NSString*)name { + if ((self = [super init])) { + name_.reset([name retain]); + date_.reset([[NSDate dateWithNaturalLanguageString:@"12/12/12"] retain]); + } + return self; +} + +- (NSString*)name { + return name_.get(); +} + +- (NSString*)UTI { + return base::mac::CFToNSCast(kUTTypeImage); +} + +- (NSDate*)modificationDate { + return date_.get(); +} + +- (NSDate*)creationDate { + return date_.get(); +} + +- (off_t)fileSize { + return 1000; +} + +@end + +class TestCameraListener + : public ImageCaptureDeviceListener, + public base::SupportsWeakPtr<TestCameraListener> { + public: + TestCameraListener() + : completed_(false), + removed_(false), + last_error_(base::PLATFORM_FILE_ERROR_INVALID_URL) {} + virtual ~TestCameraListener() {} + + virtual void ItemAdded(const std::string& name, + const base::PlatformFileInfo& info) OVERRIDE { + items_.push_back(name); + } + + virtual void NoMoreItems() OVERRIDE { + completed_ = true; + } + + virtual void DownloadedFile(const std::string& name, + base::PlatformFileError error) OVERRIDE { + EXPECT_TRUE(content::BrowserThread::CurrentlyOn( + content::BrowserThread::UI)); + downloads_.push_back(name); + last_error_ = error; + } + + virtual void DeviceRemoved() OVERRIDE { + removed_ = true; + } + + std::vector<std::string> items() const { return items_; } + std::vector<std::string> downloads() const { return downloads_; } + bool completed() const { return completed_; } + bool removed() const { return removed_; } + base::PlatformFileError last_error() const { return last_error_; } + + private: + std::vector<std::string> items_; + std::vector<std::string> downloads_; + bool completed_; + bool removed_; + base::PlatformFileError last_error_; +}; + +class ImageCaptureDeviceManagerTest : public testing::Test { + public: + virtual void SetUp() OVERRIDE { + base::SystemMonitor::AllocateSystemIOPorts(); + system_monitor_.reset(new base::SystemMonitor()); + ui_thread_.reset(new content::TestBrowserThread( + content::BrowserThread::UI, &message_loop_)); + } + + MockICCameraDevice* AttachDevice( + chrome::ImageCaptureDeviceManager* manager) { + // Ownership will be passed to the device browser delegate. + scoped_nsobject<MockICCameraDevice> device( + [[MockICCameraDevice alloc] init]); + id<ICDeviceBrowserDelegate> delegate = manager->device_browser(); + [delegate deviceBrowser:nil didAddDevice:device moreComing:NO]; + return device.autorelease(); + } + + void DetachDevice(chrome::ImageCaptureDeviceManager* manager, + ICCameraDevice* device) { + id<ICDeviceBrowserDelegate> delegate = manager->device_browser(); + [delegate deviceBrowser:nil didRemoveDevice:device moreGoing:NO]; + } + + protected: + MessageLoopForUI message_loop_; + scoped_ptr<content::TestBrowserThread> ui_thread_; + scoped_ptr<base::SystemMonitor> system_monitor_; + TestCameraListener listener_; +}; + +TEST_F(ImageCaptureDeviceManagerTest, TestAttachDetach) { + chrome::ImageCaptureDeviceManager manager; + ICCameraDevice* device = AttachDevice(&manager); + std::vector<base::SystemMonitor::RemovableStorageInfo> devices = + system_monitor_->GetAttachedRemovableStorage(); + + ASSERT_EQ(1U, devices.size()); + EXPECT_EQ(std::string("ic:") + kDeviceId, devices[0].device_id); + + DetachDevice(&manager, device); + devices = system_monitor_->GetAttachedRemovableStorage(); + ASSERT_EQ(0U, devices.size()); +}; + +TEST_F(ImageCaptureDeviceManagerTest, OpenCamera) { + chrome::ImageCaptureDeviceManager manager; + ICCameraDevice* device = AttachDevice(&manager); + + EXPECT_FALSE(chrome::ImageCaptureDeviceManager::deviceForUUID( + "nonexistent")); + + scoped_nsobject<ImageCaptureDevice> camera( + [chrome::ImageCaptureDeviceManager::deviceForUUID(kDeviceId) + retain]); + + [camera setListener:listener_.AsWeakPtr()]; + [camera open]; + + scoped_nsobject<MockICCameraFile> picture1( + [[MockICCameraFile alloc] init:@"pic1"]); + [camera cameraDevice:nil didAddItem:picture1]; + scoped_nsobject<MockICCameraFile> picture2( + [[MockICCameraFile alloc] init:@"pic2"]); + [camera cameraDevice:nil didAddItem:picture2]; + ASSERT_EQ(2U, listener_.items().size()); + EXPECT_EQ("pic1", listener_.items()[0]); + EXPECT_EQ("pic2", listener_.items()[1]); + EXPECT_FALSE(listener_.completed()); + + [camera deviceDidBecomeReadyWithCompleteContentCatalog:nil]; + ASSERT_EQ(2U, listener_.items().size()); + EXPECT_TRUE(listener_.completed()); + + [camera close]; + DetachDevice(&manager, device); + EXPECT_FALSE(chrome::ImageCaptureDeviceManager::deviceForUUID( + kDeviceId)); +} + +TEST_F(ImageCaptureDeviceManagerTest, RemoveCamera) { + chrome::ImageCaptureDeviceManager manager; + ICCameraDevice* device = AttachDevice(&manager); + + scoped_nsobject<ImageCaptureDevice> camera( + [chrome::ImageCaptureDeviceManager::deviceForUUID(kDeviceId) + retain]); + + [camera setListener:listener_.AsWeakPtr()]; + [camera open]; + + [camera didRemoveDevice:device]; + EXPECT_TRUE(listener_.removed()); +} + +TEST_F(ImageCaptureDeviceManagerTest, DownloadFile) { + scoped_ptr<content::TestBrowserThread> file_thread_( + new content::TestBrowserThread( + content::BrowserThread::FILE, &message_loop_)); + + chrome::ImageCaptureDeviceManager manager; + MockICCameraDevice* device = AttachDevice(&manager); + + scoped_nsobject<ImageCaptureDevice> camera( + [chrome::ImageCaptureDeviceManager::deviceForUUID(kDeviceId) + retain]); + + [camera setListener:listener_.AsWeakPtr()]; + [camera open]; + + std::string kTestFileName("pic1"); + + scoped_nsobject<MockICCameraFile> picture1( + [[MockICCameraFile alloc] + init:base::SysUTF8ToNSString(kTestFileName)]); + [device addMediaFile:picture1]; + [camera cameraDevice:nil didAddItem:picture1]; + + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + + EXPECT_EQ(0U, listener_.downloads().size()); + + // Test that a nonexistent file we ask to be downloaded will + // return us a not-found error. + FilePath temp_file = temp_dir.path().Append("tempfile"); + [camera downloadFile:std::string("nonexistent") localPath:temp_file]; + message_loop_.RunUntilIdle(); + ASSERT_EQ(1U, listener_.downloads().size()); + EXPECT_EQ("nonexistent", listener_.downloads()[0]); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, listener_.last_error()); + + // Test that an existing file we ask to be downloaded will end up in + // the location we specify. The mock system will copy testing file + // contents to a separate filename, mimicking the ImageCaptureCore + // library behavior. Our code then renames the file onto the requested + // destination. + [camera downloadFile:kTestFileName localPath:temp_file]; + message_loop_.RunUntilIdle(); + + ASSERT_EQ(2U, listener_.downloads().size()); + EXPECT_EQ(kTestFileName, listener_.downloads()[1]); + ASSERT_EQ(base::PLATFORM_FILE_OK, listener_.last_error()); + char file_contents[5]; + ASSERT_EQ(4, file_util::ReadFile(temp_file, file_contents, + strlen(kTestFileContents))); + EXPECT_EQ(kTestFileContents, + std::string(file_contents, strlen(kTestFileContents))); + + [camera didRemoveDevice:device]; +} diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index df497c9..5c4710f 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -2046,6 +2046,10 @@ 'browser/sync_file_system/sync_file_system_service.h', 'browser/system_monitor/disk_info_mac.h', 'browser/system_monitor/disk_info_mac.mm', + 'browser/system_monitor/image_capture_device.h', + 'browser/system_monitor/image_capture_device.mm', + 'browser/system_monitor/image_capture_device_manager.h', + 'browser/system_monitor/image_capture_device_manager.mm', 'browser/system_monitor/media_device_notifications_utils.cc', 'browser/system_monitor/media_device_notifications_utils.h', 'browser/system_monitor/media_storage_util.cc', @@ -2832,6 +2836,7 @@ '$(SDKROOT)/System/Library/Frameworks/AudioUnit.framework', '$(SDKROOT)/System/Library/Frameworks/DiskArbitration.framework', '$(SDKROOT)/System/Library/Frameworks/IOKit.framework', + '$(SDKROOT)/System/Library/Frameworks/ImageCaptureCore.framework', '$(SDKROOT)/System/Library/Frameworks/OpenGL.framework', '$(SDKROOT)/System/Library/Frameworks/QuartzCore.framework', '$(SDKROOT)/System/Library/Frameworks/SecurityInterface.framework', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index 309e0d8..defd2f9 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -1142,6 +1142,7 @@ 'browser/sync_file_system/sync_file_system_service_unittest.cc', 'browser/sync_file_system/sync_file_system_test_util.cc', 'browser/sync_file_system/sync_file_system_test_util.h', + 'browser/system_monitor/image_capture_device_manager_unittest.mm', 'browser/system_monitor/media_device_notifications_utils_unittest.cc', 'browser/system_monitor/media_storage_util_unittest.cc', 'browser/system_monitor/media_transfer_protocol_device_observer_linux_unittest.cc', |