Add ICU message format support
[chromium-blink-merge.git] / content / browser / device_monitor_mac.mm
blobc3c713c5714154547c99c9f860a85e9b91640ef3
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 "content/browser/device_monitor_mac.h"
7 #import <QTKit/QTKit.h>
9 #include <set>
11 #include "base/bind_helpers.h"
12 #include "base/logging.h"
13 #include "base/mac/bind_objc_block.h"
14 #include "base/mac/scoped_nsobject.h"
15 #include "base/profiler/scoped_tracker.h"
16 #include "base/threading/thread_checker.h"
17 #include "content/public/browser/browser_thread.h"
18 #import "media/base/mac/avfoundation_glue.h"
20 using content::BrowserThread;
22 namespace {
24 // This class is used to keep track of system devices names and their types.
25 class DeviceInfo {
26  public:
27   enum DeviceType {
28     kAudio,
29     kVideo,
30     kMuxed,
31     kUnknown,
32     kInvalid
33   };
35   DeviceInfo(std::string unique_id, DeviceType type)
36       : unique_id_(unique_id), type_(type) {}
38   // Operator== is needed here to use this class in a std::find. A given
39   // |unique_id_| always has the same |type_| so for comparison purposes the
40   // latter can be safely ignored.
41   bool operator==(const DeviceInfo& device) const {
42     return unique_id_ == device.unique_id_;
43   }
45   const std::string& unique_id() const { return unique_id_; }
46   DeviceType type() const { return type_; }
48  private:
49   std::string unique_id_;
50   DeviceType type_;
51   // Allow generated copy constructor and assignment.
54 // Base abstract class used by DeviceMonitorMac to interact with either a QTKit
55 // or an AVFoundation implementation of events and notifications.
56 class DeviceMonitorMacImpl {
57  public:
58   explicit DeviceMonitorMacImpl(content::DeviceMonitorMac* monitor)
59       : monitor_(monitor),
60         cached_devices_(),
61         device_arrival_(nil),
62         device_removal_(nil) {
63     DCHECK(monitor);
64     // Initialise the devices_cache_ with a not-valid entry. For the case in
65     // which there is one single device in the system and we get notified when
66     // it gets removed, this will prevent the system from thinking that no
67     // devices were added nor removed and not notifying the |monitor_|.
68     cached_devices_.push_back(DeviceInfo("invalid", DeviceInfo::kInvalid));
69   }
70   virtual ~DeviceMonitorMacImpl() {}
72   virtual void OnDeviceChanged() = 0;
74   // Method called by the default notification center when a device is removed
75   // or added to the system. It will compare the |cached_devices_| with the
76   // current situation, update it, and, if there's an update, signal to
77   // |monitor_| with the appropriate device type.
78   void ConsolidateDevicesListAndNotify(
79       const std::vector<DeviceInfo>& snapshot_devices);
81  protected:
82   content::DeviceMonitorMac* monitor_;
83   std::vector<DeviceInfo> cached_devices_;
85   // Handles to NSNotificationCenter block observers.
86   id device_arrival_;
87   id device_removal_;
89  private:
90   DISALLOW_COPY_AND_ASSIGN(DeviceMonitorMacImpl);
93 void DeviceMonitorMacImpl::ConsolidateDevicesListAndNotify(
94     const std::vector<DeviceInfo>& snapshot_devices) {
95   bool video_device_added = false;
96   bool audio_device_added = false;
97   bool video_device_removed = false;
98   bool audio_device_removed = false;
100   // Compare the current system devices snapshot with the ones cached to detect
101   // additions, present in the former but not in the latter. If we find a device
102   // in snapshot_devices entry also present in cached_devices, we remove it from
103   // the latter vector.
104   std::vector<DeviceInfo>::const_iterator it;
105   for (it = snapshot_devices.begin(); it != snapshot_devices.end(); ++it) {
106     std::vector<DeviceInfo>::iterator cached_devices_iterator =
107         std::find(cached_devices_.begin(), cached_devices_.end(), *it);
108     if (cached_devices_iterator == cached_devices_.end()) {
109       video_device_added |= ((it->type() == DeviceInfo::kVideo) ||
110                              (it->type() == DeviceInfo::kMuxed));
111       audio_device_added |= ((it->type() == DeviceInfo::kAudio) ||
112                              (it->type() == DeviceInfo::kMuxed));
113       DVLOG(1) << "Device has been added, id: " << it->unique_id();
114     } else {
115       cached_devices_.erase(cached_devices_iterator);
116     }
117   }
118   // All the remaining entries in cached_devices are removed devices.
119   for (it = cached_devices_.begin(); it != cached_devices_.end(); ++it) {
120     video_device_removed |= ((it->type() == DeviceInfo::kVideo) ||
121                              (it->type() == DeviceInfo::kMuxed) ||
122                              (it->type() == DeviceInfo::kInvalid));
123     audio_device_removed |= ((it->type() == DeviceInfo::kAudio) ||
124                              (it->type() == DeviceInfo::kMuxed) ||
125                              (it->type() == DeviceInfo::kInvalid));
126     DVLOG(1) << "Device has been removed, id: " << it->unique_id();
127   }
128   // Update the cached devices with the current system snapshot.
129   cached_devices_ = snapshot_devices;
131   if (video_device_added || video_device_removed)
132     monitor_->NotifyDeviceChanged(base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE);
133   if (audio_device_added || audio_device_removed)
134     monitor_->NotifyDeviceChanged(base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE);
137 class QTKitMonitorImpl : public DeviceMonitorMacImpl {
138  public:
139   explicit QTKitMonitorImpl(content::DeviceMonitorMac* monitor);
140   ~QTKitMonitorImpl() override;
142   void OnDeviceChanged() override;
144  private:
145   void CountDevices();
146   void OnAttributeChanged(NSNotification* notification);
148   id device_change_;
151 QTKitMonitorImpl::QTKitMonitorImpl(content::DeviceMonitorMac* monitor)
152     : DeviceMonitorMacImpl(monitor) {
153   NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
154   device_arrival_ =
155       [nc addObserverForName:QTCaptureDeviceWasConnectedNotification
156                       object:nil
157                        queue:nil
158                   usingBlock:^(NSNotification* notification) {
159                       OnDeviceChanged();}];
160   device_removal_ =
161       [nc addObserverForName:QTCaptureDeviceWasDisconnectedNotification
162                       object:nil
163                        queue:nil
164                   usingBlock:^(NSNotification* notification) {
165                       OnDeviceChanged();}];
166   device_change_ =
167       [nc addObserverForName:QTCaptureDeviceAttributeDidChangeNotification
168                       object:nil
169                        queue:nil
170                   usingBlock:^(NSNotification* notification) {
171                       OnAttributeChanged(notification);}];
174 QTKitMonitorImpl::~QTKitMonitorImpl() {
175   NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
176   [nc removeObserver:device_arrival_];
177   [nc removeObserver:device_removal_];
178   [nc removeObserver:device_change_];
181 void QTKitMonitorImpl::OnAttributeChanged(
182     NSNotification* notification) {
183   if ([[[notification userInfo]
184          objectForKey:QTCaptureDeviceChangedAttributeKey]
185       isEqualToString:QTCaptureDeviceSuspendedAttribute]) {
186     OnDeviceChanged();
187   }
190 void QTKitMonitorImpl::OnDeviceChanged() {
191   std::vector<DeviceInfo> snapshot_devices;
193   NSArray* devices = [QTCaptureDevice inputDevices];
194   for (QTCaptureDevice* device in devices) {
195     DeviceInfo::DeviceType device_type = DeviceInfo::kUnknown;
196     // Act as if suspended video capture devices are not attached.  For
197     // example, a laptop's internal webcam is suspended when the lid is closed.
198     if ([device hasMediaType:QTMediaTypeVideo] &&
199         ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
200         boolValue]) {
201       device_type = DeviceInfo::kVideo;
202     } else if ([device hasMediaType:QTMediaTypeMuxed] &&
203         ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
204         boolValue]) {
205       device_type = DeviceInfo::kMuxed;
206     } else if ([device hasMediaType:QTMediaTypeSound] &&
207         ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
208         boolValue]) {
209       device_type = DeviceInfo::kAudio;
210     }
211     snapshot_devices.push_back(
212         DeviceInfo([[device uniqueID] UTF8String], device_type));
213   }
214   ConsolidateDevicesListAndNotify(snapshot_devices);
217 // Forward declaration for use by CrAVFoundationDeviceObserver.
218 class SuspendObserverDelegate;
220 }  // namespace
222 // This class is a Key-Value Observer (KVO) shim. It is needed because C++
223 // classes cannot observe Key-Values directly. Created, manipulated, and
224 // destroyed on the UI Thread by SuspendObserverDelegate.
225 @interface CrAVFoundationDeviceObserver : NSObject {
226  @private
227   // Callback for device changed, has to run on Device Thread.
228   base::Closure onDeviceChangedCallback_;
230   // Member to keep track of the devices we are already monitoring.
231   std::set<base::scoped_nsobject<CrAVCaptureDevice> > monitoredDevices_;
234 - (id)initWithOnChangedCallback:(const base::Closure&)callback;
235 - (void)startObserving:(base::scoped_nsobject<CrAVCaptureDevice>)device;
236 - (void)stopObserving:(CrAVCaptureDevice*)device;
237 - (void)clearOnDeviceChangedCallback;
239 @end
241 namespace {
243 // This class owns and manages the lifetime of a CrAVFoundationDeviceObserver.
244 // It is created and destroyed in UI thread by AVFoundationMonitorImpl, and it
245 // operates on this thread except for the expensive device enumerations which
246 // are run on Device Thread.
247 class SuspendObserverDelegate :
248     public base::RefCountedThreadSafe<SuspendObserverDelegate> {
249  public:
250   explicit SuspendObserverDelegate(DeviceMonitorMacImpl* monitor);
252   // Create |suspend_observer_| for all devices and register OnDeviceChanged()
253   // as its change callback. Schedule bottom half in DoStartObserver().
254   void StartObserver(
255       const scoped_refptr<base::SingleThreadTaskRunner>& device_thread);
256   // Enumerate devices in |device_thread| and run the bottom half in
257   // DoOnDeviceChange(). |suspend_observer_| calls back here on suspend event,
258   // and our parent AVFoundationMonitorImpl calls on connect/disconnect device.
259   void OnDeviceChanged(
260       const scoped_refptr<base::SingleThreadTaskRunner>& device_thread);
261   // Remove the device monitor's weak reference. Remove ourselves as suspend
262   // notification observer from |suspend_observer_|.
263   void ResetDeviceMonitor();
265  private:
266   friend class base::RefCountedThreadSafe<SuspendObserverDelegate>;
268   virtual ~SuspendObserverDelegate();
270   // Bottom half of StartObserver(), starts |suspend_observer_| for all devices.
271   // Assumes that |devices| has been retained prior to being called, and
272   // releases it internally.
273   void DoStartObserver(NSArray* devices);
274   // Bottom half of OnDeviceChanged(), starts |suspend_observer_| for current
275   // devices and composes a snapshot of them to send it to
276   // |avfoundation_monitor_impl_|. Assumes that |devices| has been retained
277   // prior to being called, and releases it internally.
278   void DoOnDeviceChanged(NSArray* devices);
280   base::scoped_nsobject<CrAVFoundationDeviceObserver> suspend_observer_;
281   DeviceMonitorMacImpl* avfoundation_monitor_impl_;
284 SuspendObserverDelegate::SuspendObserverDelegate(DeviceMonitorMacImpl* monitor)
285     : avfoundation_monitor_impl_(monitor) {
286   DCHECK_CURRENTLY_ON(BrowserThread::UI);
289 void SuspendObserverDelegate::StartObserver(
290       const scoped_refptr<base::SingleThreadTaskRunner>& device_thread) {
291   DCHECK_CURRENTLY_ON(BrowserThread::UI);
293   base::Closure on_device_changed_callback =
294       base::Bind(&SuspendObserverDelegate::OnDeviceChanged,
295                  this, device_thread);
296   suspend_observer_.reset([[CrAVFoundationDeviceObserver alloc]
297       initWithOnChangedCallback:on_device_changed_callback]);
299   // Enumerate the devices in Device thread and post the observers start to be
300   // done on UI thread. The devices array is retained in |device_thread| and
301   // released in DoStartObserver().
302   base::PostTaskAndReplyWithResult(
303       device_thread.get(),
304       FROM_HERE,
305       base::BindBlock(^{ return [[AVCaptureDeviceGlue devices] retain]; }),
306       base::Bind(&SuspendObserverDelegate::DoStartObserver, this));
309 void SuspendObserverDelegate::OnDeviceChanged(
310       const scoped_refptr<base::SingleThreadTaskRunner>& device_thread) {
311   DCHECK_CURRENTLY_ON(BrowserThread::UI);
312   // Enumerate the devices in Device thread and post the consolidation of the
313   // new devices and the old ones to be done on UI thread. The devices array
314   // is retained in |device_thread| and released in DoOnDeviceChanged().
315   PostTaskAndReplyWithResult(
316       device_thread.get(),
317       FROM_HERE,
318       base::BindBlock(^{ return [[AVCaptureDeviceGlue devices] retain]; }),
319       base::Bind(&SuspendObserverDelegate::DoOnDeviceChanged, this));
322 void SuspendObserverDelegate::ResetDeviceMonitor() {
323   DCHECK_CURRENTLY_ON(BrowserThread::UI);
324   avfoundation_monitor_impl_ = NULL;
325   [suspend_observer_ clearOnDeviceChangedCallback];
328 SuspendObserverDelegate::~SuspendObserverDelegate() {
329   DCHECK_CURRENTLY_ON(BrowserThread::UI);
332 void SuspendObserverDelegate::DoStartObserver(NSArray* devices) {
333   DCHECK_CURRENTLY_ON(BrowserThread::UI);
334   base::scoped_nsobject<NSArray> auto_release(devices);
335   for (CrAVCaptureDevice* device in devices) {
336     base::scoped_nsobject<CrAVCaptureDevice> device_ptr([device retain]);
337     [suspend_observer_ startObserving:device_ptr];
338   }
341 void SuspendObserverDelegate::DoOnDeviceChanged(NSArray* devices) {
342   DCHECK_CURRENTLY_ON(BrowserThread::UI);
343   base::scoped_nsobject<NSArray> auto_release(devices);
344   std::vector<DeviceInfo> snapshot_devices;
345   for (CrAVCaptureDevice* device in devices) {
346     base::scoped_nsobject<CrAVCaptureDevice> device_ptr([device retain]);
347     [suspend_observer_ startObserving:device_ptr];
349     BOOL suspended = [device respondsToSelector:@selector(isSuspended)] &&
350         [device isSuspended];
351     DeviceInfo::DeviceType device_type = DeviceInfo::kUnknown;
352     if ([device hasMediaType:AVFoundationGlue::AVMediaTypeVideo()]) {
353       if (suspended)
354         continue;
355       device_type = DeviceInfo::kVideo;
356     } else if ([device hasMediaType:AVFoundationGlue::AVMediaTypeMuxed()]) {
357       device_type = suspended ? DeviceInfo::kAudio : DeviceInfo::kMuxed;
358     } else if ([device hasMediaType:AVFoundationGlue::AVMediaTypeAudio()]) {
359       device_type = DeviceInfo::kAudio;
360     }
361     snapshot_devices.push_back(DeviceInfo([[device uniqueID] UTF8String],
362                                           device_type));
363   }
364   // Make sure no references are held to |devices| when
365   // ConsolidateDevicesListAndNotify is called since the VideoCaptureManager
366   // and AudioCaptureManagers also enumerates the available devices but on
367   // another thread.
368   auto_release.reset();
369   // |avfoundation_monitor_impl_| might have been NULLed asynchronously before
370   // arriving at this line.
371   if (avfoundation_monitor_impl_) {
372     avfoundation_monitor_impl_->ConsolidateDevicesListAndNotify(
373         snapshot_devices);
374   }
377 // AVFoundation implementation of the Mac Device Monitor, registers as a global
378 // device connect/disconnect observer and plugs suspend/wake up device observers
379 // per device. This class is created and lives in UI thread. Owns a
380 // SuspendObserverDelegate that notifies when a device is suspended/resumed.
381 class AVFoundationMonitorImpl : public DeviceMonitorMacImpl {
382  public:
383   AVFoundationMonitorImpl(
384       content::DeviceMonitorMac* monitor,
385       const scoped_refptr<base::SingleThreadTaskRunner>& device_task_runner);
386   ~AVFoundationMonitorImpl() override;
388   void OnDeviceChanged() override;
390  private:
391   // {Video,AudioInput}DeviceManager's "Device" thread task runner used for
392   // posting tasks to |suspend_observer_delegate_|; valid after
393   // MediaStreamManager calls StartMonitoring().
394   const scoped_refptr<base::SingleThreadTaskRunner> device_task_runner_;
396   scoped_refptr<SuspendObserverDelegate> suspend_observer_delegate_;
398   DISALLOW_COPY_AND_ASSIGN(AVFoundationMonitorImpl);
401 AVFoundationMonitorImpl::AVFoundationMonitorImpl(
402     content::DeviceMonitorMac* monitor,
403     const scoped_refptr<base::SingleThreadTaskRunner>& device_task_runner)
404     : DeviceMonitorMacImpl(monitor),
405       device_task_runner_(device_task_runner),
406       suspend_observer_delegate_(new SuspendObserverDelegate(this)) {
407   DCHECK_CURRENTLY_ON(BrowserThread::UI);
408   NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
409   device_arrival_ =
410       [nc addObserverForName:AVFoundationGlue::
411           AVCaptureDeviceWasConnectedNotification()
412                       object:nil
413                        queue:nil
414                   usingBlock:^(NSNotification* notification) {
415                       OnDeviceChanged();}];
416   device_removal_ =
417       [nc addObserverForName:AVFoundationGlue::
418           AVCaptureDeviceWasDisconnectedNotification()
419                       object:nil
420                        queue:nil
421                   usingBlock:^(NSNotification* notification) {
422                       OnDeviceChanged();}];
423   suspend_observer_delegate_->StartObserver(device_task_runner_);
426 AVFoundationMonitorImpl::~AVFoundationMonitorImpl() {
427   DCHECK_CURRENTLY_ON(BrowserThread::UI);
428   suspend_observer_delegate_->ResetDeviceMonitor();
429   NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
430   [nc removeObserver:device_arrival_];
431   [nc removeObserver:device_removal_];
434 void AVFoundationMonitorImpl::OnDeviceChanged() {
435   DCHECK_CURRENTLY_ON(BrowserThread::UI);
436   suspend_observer_delegate_->OnDeviceChanged(device_task_runner_);
439 }  // namespace
441 @implementation CrAVFoundationDeviceObserver
443 - (id)initWithOnChangedCallback:(const base::Closure&)callback {
444   DCHECK_CURRENTLY_ON(BrowserThread::UI);
445   if ((self = [super init])) {
446     DCHECK(!callback.is_null());
447     onDeviceChangedCallback_ = callback;
448   }
449   return self;
452 - (void)dealloc {
453   DCHECK_CURRENTLY_ON(BrowserThread::UI);
454   std::set<base::scoped_nsobject<CrAVCaptureDevice> >::iterator it =
455       monitoredDevices_.begin();
456   while (it != monitoredDevices_.end())
457     [self removeObservers:*(it++)];
458   [super dealloc];
461 - (void)startObserving:(base::scoped_nsobject<CrAVCaptureDevice>)device {
462   DCHECK_CURRENTLY_ON(BrowserThread::UI);
463   DCHECK(device != nil);
464   // Skip this device if there are already observers connected to it.
465   if (std::find(monitoredDevices_.begin(), monitoredDevices_.end(), device) !=
466       monitoredDevices_.end()) {
467     return;
468   }
469   [device addObserver:self
470            forKeyPath:@"suspended"
471               options:0
472               context:device.get()];
473   [device addObserver:self
474            forKeyPath:@"connected"
475               options:0
476               context:device.get()];
477   monitoredDevices_.insert(device);
480 - (void)stopObserving:(CrAVCaptureDevice*)device {
481   DCHECK_CURRENTLY_ON(BrowserThread::UI);
482   DCHECK(device != nil);
484   std::set<base::scoped_nsobject<CrAVCaptureDevice> >::iterator found =
485       std::find(monitoredDevices_.begin(), monitoredDevices_.end(), device);
486   DCHECK(found != monitoredDevices_.end());
487   [self removeObservers:*found];
488   monitoredDevices_.erase(found);
491 - (void)clearOnDeviceChangedCallback {
492   DCHECK_CURRENTLY_ON(BrowserThread::UI);
493   onDeviceChangedCallback_.Reset();
496 - (void)removeObservers:(CrAVCaptureDevice*)device {
497   DCHECK_CURRENTLY_ON(BrowserThread::UI);
498   // Check sanity of |device| via its -observationInfo. http://crbug.com/371271.
499   if ([device observationInfo]) {
500     [device removeObserver:self
501                 forKeyPath:@"suspended"];
502     [device removeObserver:self
503                 forKeyPath:@"connected"];
504   }
507 - (void)observeValueForKeyPath:(NSString*)keyPath
508                       ofObject:(id)object
509                         change:(NSDictionary*)change
510                        context:(void*)context {
511   DCHECK_CURRENTLY_ON(BrowserThread::UI);
512   if ([keyPath isEqual:@"suspended"])
513     onDeviceChangedCallback_.Run();
514   if ([keyPath isEqual:@"connected"])
515     [self stopObserving:static_cast<CrAVCaptureDevice*>(context)];
518 @end  // @implementation CrAVFoundationDeviceObserver
520 namespace content {
522 DeviceMonitorMac::DeviceMonitorMac() {
523   // Both QTKit and AVFoundation do not need to be fired up until the user
524   // exercises a GetUserMedia. Bringing up either library and enumerating the
525   // devices in the system is an operation taking in the range of hundred of ms,
526   // so it is triggered explicitly from MediaStreamManager::StartMonitoring().
529 DeviceMonitorMac::~DeviceMonitorMac() {}
531 void DeviceMonitorMac::StartMonitoring(
532     const scoped_refptr<base::SingleThreadTaskRunner>& device_task_runner) {
533   DCHECK(thread_checker_.CalledOnValidThread());
534   if (AVFoundationGlue::IsAVFoundationSupported()) {
535     // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/458404
536     // is fixed.
537     tracked_objects::ScopedTracker tracking_profile(
538         FROM_HERE_WITH_EXPLICIT_FUNCTION(
539             "458404 DeviceMonitorMac::StartMonitoring::AVFoundation"));
540     DVLOG(1) << "Monitoring via AVFoundation";
541     device_monitor_impl_.reset(new AVFoundationMonitorImpl(this,
542                                                            device_task_runner));
543   } else {
544     // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/458404
545     // is fixed.
546     tracked_objects::ScopedTracker tracking_profile(
547         FROM_HERE_WITH_EXPLICIT_FUNCTION(
548             "458404 DeviceMonitorMac::StartMonitoring::QTKit"));
549     DVLOG(1) << "Monitoring via QTKit";
550     device_monitor_impl_.reset(new QTKitMonitorImpl(this));
551   }
554 void DeviceMonitorMac::NotifyDeviceChanged(
555     base::SystemMonitor::DeviceType type) {
556   DCHECK(thread_checker_.CalledOnValidThread());
557   // TODO(xians): Remove the global variable for SystemMonitor.
558   base::SystemMonitor::Get()->ProcessDevicesChanged(type);
561 }  // namespace content