Upstream version 7.36.149.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/scoped_nsobject.h"
14 #import "media/video/capture/mac/avfoundation_glue.h"
15
16 namespace {
17
18 // This class is used to keep track of system devices names and their types.
19 class DeviceInfo {
20  public:
21   enum DeviceType {
22     kAudio,
23     kVideo,
24     kMuxed,
25     kUnknown,
26     kInvalid
27   };
28
29   DeviceInfo(std::string unique_id, DeviceType type)
30       : unique_id_(unique_id), type_(type) {}
31
32   // Operator== is needed here to use this class in a std::find. A given
33   // |unique_id_| always has the same |type_| so for comparison purposes the
34   // latter can be safely ignored.
35   bool operator==(const DeviceInfo& device) const {
36     return unique_id_ == device.unique_id_;
37   }
38
39   const std::string& unique_id() const { return unique_id_; }
40   DeviceType type() const { return type_; }
41
42  private:
43   std::string unique_id_;
44   DeviceType type_;
45   // Allow generated copy constructor and assignment.
46 };
47
48 // Base abstract class used by DeviceMonitorMac to interact with either a QTKit
49 // or an AVFoundation implementation of events and notifications.
50 class DeviceMonitorMacImpl {
51  public:
52   explicit DeviceMonitorMacImpl(content::DeviceMonitorMac* monitor)
53       : monitor_(monitor),
54         cached_devices_(),
55         device_arrival_(nil),
56         device_removal_(nil) {
57     DCHECK(monitor);
58     // Initialise the devices_cache_ with a not-valid entry. For the case in
59     // which there is one single device in the system and we get notified when
60     // it gets removed, this will prevent the system from thinking that no
61     // devices were added nor removed and not notifying the |monitor_|.
62     cached_devices_.push_back(DeviceInfo("invalid", DeviceInfo::kInvalid));
63   }
64   virtual ~DeviceMonitorMacImpl() {}
65
66   virtual void OnDeviceChanged() = 0;
67
68   // Method called by the default notification center when a device is removed
69   // or added to the system. It will compare the |cached_devices_| with the
70   // current situation, update it, and, if there's an update, signal to
71   // |monitor_| with the appropriate device type.
72   void ConsolidateDevicesListAndNotify(
73       const std::vector<DeviceInfo>& snapshot_devices);
74
75  protected:
76   content::DeviceMonitorMac* monitor_;
77   std::vector<DeviceInfo> cached_devices_;
78
79   // Handles to NSNotificationCenter block observers.
80   id device_arrival_;
81   id device_removal_;
82
83  private:
84   DISALLOW_COPY_AND_ASSIGN(DeviceMonitorMacImpl);
85 };
86
87 void DeviceMonitorMacImpl::ConsolidateDevicesListAndNotify(
88     const std::vector<DeviceInfo>& snapshot_devices) {
89   bool video_device_added = false;
90   bool audio_device_added = false;
91   bool video_device_removed = false;
92   bool audio_device_removed = false;
93
94   // Compare the current system devices snapshot with the ones cached to detect
95   // additions, present in the former but not in the latter. If we find a device
96   // in snapshot_devices entry also present in cached_devices, we remove it from
97   // the latter vector.
98   std::vector<DeviceInfo>::const_iterator it;
99   for (it = snapshot_devices.begin(); it != snapshot_devices.end(); ++it) {
100     std::vector<DeviceInfo>::iterator cached_devices_iterator =
101         std::find(cached_devices_.begin(), cached_devices_.end(), *it);
102     if (cached_devices_iterator == cached_devices_.end()) {
103       video_device_added |= ((it->type() == DeviceInfo::kVideo) ||
104                              (it->type() == DeviceInfo::kMuxed));
105       audio_device_added |= ((it->type() == DeviceInfo::kAudio) ||
106                              (it->type() == DeviceInfo::kMuxed));
107       DVLOG(1) << "Device has been added, id: " << it->unique_id();
108     } else {
109       cached_devices_.erase(cached_devices_iterator);
110     }
111   }
112   // All the remaining entries in cached_devices are removed devices.
113   for (it = cached_devices_.begin(); it != cached_devices_.end(); ++it) {
114     video_device_removed |= ((it->type() == DeviceInfo::kVideo) ||
115                              (it->type() == DeviceInfo::kMuxed) ||
116                              (it->type() == DeviceInfo::kInvalid));
117     audio_device_removed |= ((it->type() == DeviceInfo::kAudio) ||
118                              (it->type() == DeviceInfo::kMuxed) ||
119                              (it->type() == DeviceInfo::kInvalid));
120     DVLOG(1) << "Device has been removed, id: " << it->unique_id();
121   }
122   // Update the cached devices with the current system snapshot.
123   cached_devices_ = snapshot_devices;
124
125   if (video_device_added || video_device_removed)
126     monitor_->NotifyDeviceChanged(base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE);
127   if (audio_device_added || audio_device_removed)
128     monitor_->NotifyDeviceChanged(base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE);
129 }
130
131 class QTKitMonitorImpl : public DeviceMonitorMacImpl {
132  public:
133   explicit QTKitMonitorImpl(content::DeviceMonitorMac* monitor);
134   virtual ~QTKitMonitorImpl();
135
136   virtual void OnDeviceChanged() OVERRIDE;
137  private:
138   void CountDevices();
139   void OnAttributeChanged(NSNotification* notification);
140
141   id device_change_;
142 };
143
144 QTKitMonitorImpl::QTKitMonitorImpl(content::DeviceMonitorMac* monitor)
145     : DeviceMonitorMacImpl(monitor) {
146   NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
147   device_arrival_ =
148       [nc addObserverForName:QTCaptureDeviceWasConnectedNotification
149                       object:nil
150                        queue:nil
151                   usingBlock:^(NSNotification* notification) {
152                       OnDeviceChanged();}];
153   device_removal_ =
154       [nc addObserverForName:QTCaptureDeviceWasDisconnectedNotification
155                       object:nil
156                        queue:nil
157                   usingBlock:^(NSNotification* notification) {
158                       OnDeviceChanged();}];
159   device_change_ =
160       [nc addObserverForName:QTCaptureDeviceAttributeDidChangeNotification
161                       object:nil
162                        queue:nil
163                   usingBlock:^(NSNotification* notification) {
164                       OnAttributeChanged(notification);}];
165 }
166
167 QTKitMonitorImpl::~QTKitMonitorImpl() {
168   NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
169   [nc removeObserver:device_arrival_];
170   [nc removeObserver:device_removal_];
171   [nc removeObserver:device_change_];
172 }
173
174 void QTKitMonitorImpl::OnAttributeChanged(
175     NSNotification* notification) {
176   if ([[[notification userInfo]
177          objectForKey:QTCaptureDeviceChangedAttributeKey]
178       isEqualToString:QTCaptureDeviceSuspendedAttribute]) {
179     OnDeviceChanged();
180   }
181 }
182
183 void QTKitMonitorImpl::OnDeviceChanged() {
184   std::vector<DeviceInfo> snapshot_devices;
185
186   NSArray* devices = [QTCaptureDevice inputDevices];
187   for (QTCaptureDevice* device in devices) {
188     DeviceInfo::DeviceType device_type = DeviceInfo::kUnknown;
189     // Act as if suspended video capture devices are not attached.  For
190     // example, a laptop's internal webcam is suspended when the lid is closed.
191     if ([device hasMediaType:QTMediaTypeVideo] &&
192         ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
193         boolValue]) {
194       device_type = DeviceInfo::kVideo;
195     } else if ([device hasMediaType:QTMediaTypeMuxed] &&
196         ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
197         boolValue]) {
198       device_type = DeviceInfo::kMuxed;
199     } else if ([device hasMediaType:QTMediaTypeSound] &&
200         ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
201         boolValue]) {
202       device_type = DeviceInfo::kAudio;
203     }
204     snapshot_devices.push_back(
205         DeviceInfo([[device uniqueID] UTF8String], device_type));
206   }
207   ConsolidateDevicesListAndNotify(snapshot_devices);
208 }
209
210 // Forward declaration for use by CrAVFoundationDeviceObserver.
211 class AVFoundationMonitorImpl;
212
213 }  // namespace
214
215 // This class is a Key-Value Observer (KVO) shim. It is needed because C++
216 // classes cannot observe Key-Values directly. This class is used by
217 // AVfoundationMonitorImpl and executed in its |device_task_runner_|, a.k.a.
218 // "Device Thread". -stopObserving is called dutifully on -dealloc on UI thread.
219 @interface CrAVFoundationDeviceObserver : NSObject {
220  @private
221   AVFoundationMonitorImpl* receiver_;
222   // Member to keep track of the devices we are already monitoring.
223   std::set<CrAVCaptureDevice*> monitoredDevices_;
224 }
225
226 - (id)initWithChangeReceiver:(AVFoundationMonitorImpl*)receiver;
227 - (void)startObserving:(CrAVCaptureDevice*)device;
228 - (void)stopObserving:(CrAVCaptureDevice*)device;
229
230 @end
231
232 namespace {
233
234 // AVFoundation implementation of the Mac Device Monitor, registers as a global
235 // device connect/disconnect observer and plugs suspend/wake up device observers
236 // per device. This class is created and lives in UI thread; device enumeration
237 // and operations involving |suspend_observer_| happen on |device_task_runner_|.
238 class AVFoundationMonitorImpl : public DeviceMonitorMacImpl {
239  public:
240   AVFoundationMonitorImpl(
241       content::DeviceMonitorMac* monitor,
242       const scoped_refptr<base::SingleThreadTaskRunner>& device_task_runner);
243   virtual ~AVFoundationMonitorImpl();
244
245   virtual void OnDeviceChanged() OVERRIDE;
246
247  private:
248   void OnDeviceChangedOnDeviceThread(
249       const scoped_refptr<base::MessageLoopProxy>& ui_thread);
250   void StartObserverOnDeviceThread();
251
252   base::ThreadChecker thread_checker_;
253
254   // {Video,AudioInput}DeviceManager's "Device" thread task runner used for
255   // device enumeration, valid after MediaStreamManager calls StartMonitoring().
256   const scoped_refptr<base::SingleThreadTaskRunner> device_task_runner_;
257
258   // Created and executed in |device_task_runnner_|.
259   base::scoped_nsobject<CrAVFoundationDeviceObserver> suspend_observer_;
260
261   DISALLOW_COPY_AND_ASSIGN(AVFoundationMonitorImpl);
262 };
263
264 AVFoundationMonitorImpl::AVFoundationMonitorImpl(
265     content::DeviceMonitorMac* monitor,
266     const scoped_refptr<base::SingleThreadTaskRunner>& device_task_runner)
267     : DeviceMonitorMacImpl(monitor),
268       device_task_runner_(device_task_runner) {
269   NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
270   device_arrival_ =
271       [nc addObserverForName:AVFoundationGlue::
272           AVCaptureDeviceWasConnectedNotification()
273                       object:nil
274                        queue:nil
275                   usingBlock:^(NSNotification* notification) {
276                       OnDeviceChanged();}];
277   device_removal_ =
278       [nc addObserverForName:AVFoundationGlue::
279           AVCaptureDeviceWasDisconnectedNotification()
280                       object:nil
281                        queue:nil
282                   usingBlock:^(NSNotification* notification) {
283                       OnDeviceChanged();}];
284   device_task_runner_->PostTask(FROM_HERE,
285       base::Bind(&AVFoundationMonitorImpl::StartObserverOnDeviceThread,
286           base::Unretained(this)));
287 }
288
289 AVFoundationMonitorImpl::~AVFoundationMonitorImpl() {
290   NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
291   [nc removeObserver:device_arrival_];
292   [nc removeObserver:device_removal_];
293 }
294
295 void AVFoundationMonitorImpl::OnDeviceChanged() {
296   DCHECK(thread_checker_.CalledOnValidThread());
297   device_task_runner_->PostTask(FROM_HERE,
298       base::Bind(&AVFoundationMonitorImpl::OnDeviceChangedOnDeviceThread,
299           base::Unretained(this),
300           base::MessageLoop::current()->message_loop_proxy()));
301 }
302
303 void AVFoundationMonitorImpl::OnDeviceChangedOnDeviceThread(
304     const scoped_refptr<base::MessageLoopProxy>& ui_thread) {
305   DCHECK(device_task_runner_->BelongsToCurrentThread());
306   NSArray* devices = [AVCaptureDeviceGlue devices];
307   std::vector<DeviceInfo> snapshot_devices;
308   for (CrAVCaptureDevice* device in devices) {
309     [suspend_observer_ startObserving:device];
310     BOOL suspended = [device respondsToSelector:@selector(isSuspended)] &&
311         [device isSuspended];
312     DeviceInfo::DeviceType device_type = DeviceInfo::kUnknown;
313     if ([device hasMediaType:AVFoundationGlue::AVMediaTypeVideo()]) {
314       if (suspended)
315         continue;
316       device_type = DeviceInfo::kVideo;
317     } else if ([device hasMediaType:AVFoundationGlue::AVMediaTypeMuxed()]) {
318       device_type = suspended ? DeviceInfo::kAudio : DeviceInfo::kMuxed;
319     } else if ([device hasMediaType:AVFoundationGlue::AVMediaTypeAudio()]) {
320       device_type = DeviceInfo::kAudio;
321     }
322     snapshot_devices.push_back(DeviceInfo([[device uniqueID] UTF8String],
323                                           device_type));
324   }
325   // Post the consolidation of enumerated devices to be done on UI thread.
326   ui_thread->PostTask(FROM_HERE,
327       base::Bind(&DeviceMonitorMacImpl::ConsolidateDevicesListAndNotify,
328           base::Unretained(this), snapshot_devices));
329 }
330
331 void AVFoundationMonitorImpl::StartObserverOnDeviceThread() {
332   DCHECK(device_task_runner_->BelongsToCurrentThread());
333   suspend_observer_.reset([[CrAVFoundationDeviceObserver alloc]
334                                initWithChangeReceiver:this]);
335   for (CrAVCaptureDevice* device in [AVCaptureDeviceGlue devices])
336     [suspend_observer_ startObserving:device];
337 }
338
339 }  // namespace
340
341 @implementation CrAVFoundationDeviceObserver
342
343 - (id)initWithChangeReceiver:(AVFoundationMonitorImpl*)receiver {
344   if ((self = [super init])) {
345     DCHECK(receiver != NULL);
346     receiver_ = receiver;
347   }
348   return self;
349 }
350
351 - (void)dealloc {
352   std::set<CrAVCaptureDevice*>::iterator it = monitoredDevices_.begin();
353   while (it != monitoredDevices_.end())
354     [self stopObserving:*it++];
355   [super dealloc];
356 }
357
358 - (void)startObserving:(CrAVCaptureDevice*)device {
359   DCHECK(device != nil);
360   // Skip this device if there are already observers connected to it.
361   if (std::find(monitoredDevices_.begin(), monitoredDevices_.end(), device) !=
362           monitoredDevices_.end()) {
363     return;
364   }
365   [device addObserver:self
366            forKeyPath:@"suspended"
367               options:0
368               context:device];
369   [device addObserver:self
370            forKeyPath:@"connected"
371               options:0
372               context:device];
373   monitoredDevices_.insert(device);
374 }
375
376 - (void)stopObserving:(CrAVCaptureDevice*)device {
377   DCHECK(device != nil);
378   std::set<CrAVCaptureDevice*>::iterator found =
379       std::find(monitoredDevices_.begin(), monitoredDevices_.end(), device);
380   DCHECK(found != monitoredDevices_.end());
381   [device removeObserver:self
382               forKeyPath:@"suspended"];
383   [device removeObserver:self
384               forKeyPath:@"connected"];
385   monitoredDevices_.erase(found);
386 }
387
388 - (void)observeValueForKeyPath:(NSString*)keyPath
389                       ofObject:(id)object
390                         change:(NSDictionary*)change
391                        context:(void*)context {
392   if ([keyPath isEqual:@"suspended"])
393     receiver_->OnDeviceChanged();
394   if ([keyPath isEqual:@"connected"])
395     [self stopObserving:static_cast<CrAVCaptureDevice*>(context)];
396 }
397
398 @end  // @implementation CrAVFoundationDeviceObserver
399
400 namespace content {
401
402 DeviceMonitorMac::DeviceMonitorMac() {
403   // Both QTKit and AVFoundation do not need to be fired up until the user
404   // exercises a GetUserMedia. Bringing up either library and enumerating the
405   // devices in the system is an operation taking in the range of hundred of ms,
406   // so it is triggered explicitly from MediaStreamManager::StartMonitoring().
407 }
408
409 DeviceMonitorMac::~DeviceMonitorMac() {}
410
411 void DeviceMonitorMac::StartMonitoring(
412     const scoped_refptr<base::SingleThreadTaskRunner>& device_task_runner) {
413   DCHECK(thread_checker_.CalledOnValidThread());
414   if (AVFoundationGlue::IsAVFoundationSupported()) {
415     DVLOG(1) << "Monitoring via AVFoundation";
416     device_monitor_impl_.reset(new AVFoundationMonitorImpl(this,
417                                                            device_task_runner));
418   } else {
419     DVLOG(1) << "Monitoring via QTKit";
420     device_monitor_impl_.reset(new QTKitMonitorImpl(this));
421   }
422 }
423
424 void DeviceMonitorMac::NotifyDeviceChanged(
425     base::SystemMonitor::DeviceType type) {
426   DCHECK(thread_checker_.CalledOnValidThread());
427   // TODO(xians): Remove the global variable for SystemMonitor.
428   base::SystemMonitor::Get()->ProcessDevicesChanged(type);
429 }
430
431 }  // namespace content