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