Convert remaining WebContentsObservers loading callbacks to use RFH.
[chromium-blink-merge.git] / content / browser / device_monitor_mac.mm
blobeb429afe6402bb07bfc3f100ec31e9c05e2665e9
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/scoped_nsobject.h"
14 #include "base/threading/thread_checker.h"
15 #include "content/public/browser/browser_thread.h"
16 #import "media/video/capture/mac/avfoundation_glue.h"
18 namespace {
20 // This class is used to keep track of system devices names and their types.
21 class DeviceInfo {
22  public:
23   enum DeviceType {
24     kAudio,
25     kVideo,
26     kMuxed,
27     kUnknown,
28     kInvalid
29   };
31   DeviceInfo(std::string unique_id, DeviceType type)
32       : unique_id_(unique_id), type_(type) {}
34   // Operator== is needed here to use this class in a std::find. A given
35   // |unique_id_| always has the same |type_| so for comparison purposes the
36   // latter can be safely ignored.
37   bool operator==(const DeviceInfo& device) const {
38     return unique_id_ == device.unique_id_;
39   }
41   const std::string& unique_id() const { return unique_id_; }
42   DeviceType type() const { return type_; }
44  private:
45   std::string unique_id_;
46   DeviceType type_;
47   // Allow generated copy constructor and assignment.
50 // Base abstract class used by DeviceMonitorMac to interact with either a QTKit
51 // or an AVFoundation implementation of events and notifications.
52 class DeviceMonitorMacImpl {
53  public:
54   explicit DeviceMonitorMacImpl(content::DeviceMonitorMac* monitor)
55       : monitor_(monitor),
56         cached_devices_(),
57         device_arrival_(nil),
58         device_removal_(nil) {
59     DCHECK(monitor);
60     // Initialise the devices_cache_ with a not-valid entry. For the case in
61     // which there is one single device in the system and we get notified when
62     // it gets removed, this will prevent the system from thinking that no
63     // devices were added nor removed and not notifying the |monitor_|.
64     cached_devices_.push_back(DeviceInfo("invalid", DeviceInfo::kInvalid));
65   }
66   virtual ~DeviceMonitorMacImpl() {}
68   virtual void OnDeviceChanged() = 0;
70   // Method called by the default notification center when a device is removed
71   // or added to the system. It will compare the |cached_devices_| with the
72   // current situation, update it, and, if there's an update, signal to
73   // |monitor_| with the appropriate device type.
74   void ConsolidateDevicesListAndNotify(
75       const std::vector<DeviceInfo>& snapshot_devices);
77  protected:
78   content::DeviceMonitorMac* monitor_;
79   std::vector<DeviceInfo> cached_devices_;
81   // Handles to NSNotificationCenter block observers.
82   id device_arrival_;
83   id device_removal_;
85  private:
86   DISALLOW_COPY_AND_ASSIGN(DeviceMonitorMacImpl);
89 void DeviceMonitorMacImpl::ConsolidateDevicesListAndNotify(
90     const std::vector<DeviceInfo>& snapshot_devices) {
91   bool video_device_added = false;
92   bool audio_device_added = false;
93   bool video_device_removed = false;
94   bool audio_device_removed = false;
96   // Compare the current system devices snapshot with the ones cached to detect
97   // additions, present in the former but not in the latter. If we find a device
98   // in snapshot_devices entry also present in cached_devices, we remove it from
99   // the latter vector.
100   std::vector<DeviceInfo>::const_iterator it;
101   for (it = snapshot_devices.begin(); it != snapshot_devices.end(); ++it) {
102     std::vector<DeviceInfo>::iterator cached_devices_iterator =
103         std::find(cached_devices_.begin(), cached_devices_.end(), *it);
104     if (cached_devices_iterator == cached_devices_.end()) {
105       video_device_added |= ((it->type() == DeviceInfo::kVideo) ||
106                              (it->type() == DeviceInfo::kMuxed));
107       audio_device_added |= ((it->type() == DeviceInfo::kAudio) ||
108                              (it->type() == DeviceInfo::kMuxed));
109       DVLOG(1) << "Device has been added, id: " << it->unique_id();
110     } else {
111       cached_devices_.erase(cached_devices_iterator);
112     }
113   }
114   // All the remaining entries in cached_devices are removed devices.
115   for (it = cached_devices_.begin(); it != cached_devices_.end(); ++it) {
116     video_device_removed |= ((it->type() == DeviceInfo::kVideo) ||
117                              (it->type() == DeviceInfo::kMuxed) ||
118                              (it->type() == DeviceInfo::kInvalid));
119     audio_device_removed |= ((it->type() == DeviceInfo::kAudio) ||
120                              (it->type() == DeviceInfo::kMuxed) ||
121                              (it->type() == DeviceInfo::kInvalid));
122     DVLOG(1) << "Device has been removed, id: " << it->unique_id();
123   }
124   // Update the cached devices with the current system snapshot.
125   cached_devices_ = snapshot_devices;
127   if (video_device_added || video_device_removed)
128     monitor_->NotifyDeviceChanged(base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE);
129   if (audio_device_added || audio_device_removed)
130     monitor_->NotifyDeviceChanged(base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE);
133 class QTKitMonitorImpl : public DeviceMonitorMacImpl {
134  public:
135   explicit QTKitMonitorImpl(content::DeviceMonitorMac* monitor);
136   virtual ~QTKitMonitorImpl();
138   virtual void OnDeviceChanged() OVERRIDE;
139  private:
140   void CountDevices();
141   void OnAttributeChanged(NSNotification* notification);
143   id device_change_;
146 QTKitMonitorImpl::QTKitMonitorImpl(content::DeviceMonitorMac* monitor)
147     : DeviceMonitorMacImpl(monitor) {
148   NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
149   device_arrival_ =
150       [nc addObserverForName:QTCaptureDeviceWasConnectedNotification
151                       object:nil
152                        queue:nil
153                   usingBlock:^(NSNotification* notification) {
154                       OnDeviceChanged();}];
155   device_removal_ =
156       [nc addObserverForName:QTCaptureDeviceWasDisconnectedNotification
157                       object:nil
158                        queue:nil
159                   usingBlock:^(NSNotification* notification) {
160                       OnDeviceChanged();}];
161   device_change_ =
162       [nc addObserverForName:QTCaptureDeviceAttributeDidChangeNotification
163                       object:nil
164                        queue:nil
165                   usingBlock:^(NSNotification* notification) {
166                       OnAttributeChanged(notification);}];
169 QTKitMonitorImpl::~QTKitMonitorImpl() {
170   NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
171   [nc removeObserver:device_arrival_];
172   [nc removeObserver:device_removal_];
173   [nc removeObserver:device_change_];
176 void QTKitMonitorImpl::OnAttributeChanged(
177     NSNotification* notification) {
178   if ([[[notification userInfo]
179          objectForKey:QTCaptureDeviceChangedAttributeKey]
180       isEqualToString:QTCaptureDeviceSuspendedAttribute]) {
181     OnDeviceChanged();
182   }
185 void QTKitMonitorImpl::OnDeviceChanged() {
186   std::vector<DeviceInfo> snapshot_devices;
188   NSArray* devices = [QTCaptureDevice inputDevices];
189   for (QTCaptureDevice* device in devices) {
190     DeviceInfo::DeviceType device_type = DeviceInfo::kUnknown;
191     // Act as if suspended video capture devices are not attached.  For
192     // example, a laptop's internal webcam is suspended when the lid is closed.
193     if ([device hasMediaType:QTMediaTypeVideo] &&
194         ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
195         boolValue]) {
196       device_type = DeviceInfo::kVideo;
197     } else if ([device hasMediaType:QTMediaTypeMuxed] &&
198         ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
199         boolValue]) {
200       device_type = DeviceInfo::kMuxed;
201     } else if ([device hasMediaType:QTMediaTypeSound] &&
202         ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
203         boolValue]) {
204       device_type = DeviceInfo::kAudio;
205     }
206     snapshot_devices.push_back(
207         DeviceInfo([[device uniqueID] UTF8String], device_type));
208   }
209   ConsolidateDevicesListAndNotify(snapshot_devices);
212 // Forward declaration for use by CrAVFoundationDeviceObserver.
213 class SuspendObserverDelegate;
215 }  // namespace
217 // This class is a Key-Value Observer (KVO) shim. It is needed because C++
218 // classes cannot observe Key-Values directly. Created, manipulated, and
219 // destroyed on the Device Thread by SuspendedObserverDelegate.
220 @interface CrAVFoundationDeviceObserver : NSObject {
221  @private
222   SuspendObserverDelegate* receiver_;  // weak
223   // Member to keep track of the devices we are already monitoring.
224   std::set<CrAVCaptureDevice*> monitoredDevices_;
227 - (id)initWithChangeReceiver:(SuspendObserverDelegate*)receiver;
228 - (void)startObserving:(CrAVCaptureDevice*)device;
229 - (void)stopObserving:(CrAVCaptureDevice*)device;
231 @end
233 namespace {
235 // This class owns and manages the lifetime of a CrAVFoundationDeviceObserver.
236 // Provides a callback for this device observer to indicate that there has been
237 // a device change of some kind. Created by AVFoundationMonitorImpl in UI thread
238 // but living in Device Thread.
239 class SuspendObserverDelegate :
240     public base::RefCountedThreadSafe<SuspendObserverDelegate> {
241  public:
242   explicit SuspendObserverDelegate(DeviceMonitorMacImpl* monitor)
243       : avfoundation_monitor_impl_(monitor) {
244     device_thread_checker_.DetachFromThread();
245   }
247   void OnDeviceChanged();
248   void StartObserver();
249   void ResetDeviceMonitorOnUIThread();
251  private:
252   friend class base::RefCountedThreadSafe<SuspendObserverDelegate>;
254   virtual ~SuspendObserverDelegate() {}
256   void OnDeviceChangedOnUIThread(
257       const std::vector<DeviceInfo>& snapshot_devices);
259   base::ThreadChecker device_thread_checker_;
260   base::scoped_nsobject<CrAVFoundationDeviceObserver> suspend_observer_;
261   DeviceMonitorMacImpl* avfoundation_monitor_impl_;
264 void SuspendObserverDelegate::OnDeviceChanged() {
265   DCHECK(device_thread_checker_.CalledOnValidThread());
266   NSArray* devices = [AVCaptureDeviceGlue devices];
267   std::vector<DeviceInfo> snapshot_devices;
268   for (CrAVCaptureDevice* device in devices) {
269     [suspend_observer_ startObserving:device];
270     BOOL suspended = [device respondsToSelector:@selector(isSuspended)] &&
271         [device isSuspended];
272     DeviceInfo::DeviceType device_type = DeviceInfo::kUnknown;
273     if ([device hasMediaType:AVFoundationGlue::AVMediaTypeVideo()]) {
274       if (suspended)
275         continue;
276       device_type = DeviceInfo::kVideo;
277     } else if ([device hasMediaType:AVFoundationGlue::AVMediaTypeMuxed()]) {
278       device_type = suspended ? DeviceInfo::kAudio : DeviceInfo::kMuxed;
279     } else if ([device hasMediaType:AVFoundationGlue::AVMediaTypeAudio()]) {
280       device_type = DeviceInfo::kAudio;
281     }
282     snapshot_devices.push_back(DeviceInfo([[device uniqueID] UTF8String],
283                                           device_type));
284   }
285   // Post the consolidation of enumerated devices to be done on UI thread.
286   content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
287       base::Bind(&SuspendObserverDelegate::OnDeviceChangedOnUIThread,
288           this, snapshot_devices));
291 void SuspendObserverDelegate::StartObserver() {
292   DCHECK(device_thread_checker_.CalledOnValidThread());
293   suspend_observer_.reset([[CrAVFoundationDeviceObserver alloc]
294                                initWithChangeReceiver:this]);
295   for (CrAVCaptureDevice* device in [AVCaptureDeviceGlue devices])
296     [suspend_observer_ startObserving:device];
299 void SuspendObserverDelegate::ResetDeviceMonitorOnUIThread() {
300   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
301   avfoundation_monitor_impl_ = NULL;
304 void SuspendObserverDelegate::OnDeviceChangedOnUIThread(
305     const std::vector<DeviceInfo>& snapshot_devices) {
306   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
307   // |avfoundation_monitor_impl_| might have been NULLed asynchronously before
308   // arriving at this line.
309   if (avfoundation_monitor_impl_) {
310     avfoundation_monitor_impl_->ConsolidateDevicesListAndNotify(
311         snapshot_devices);
312   }
315 // AVFoundation implementation of the Mac Device Monitor, registers as a global
316 // device connect/disconnect observer and plugs suspend/wake up device observers
317 // per device. Owns a SuspendObserverDelegate living in |device_task_runner_|
318 // and gets notified when a device is suspended/resumed. This class is created
319 // and lives in UI thread;
320 class AVFoundationMonitorImpl : public DeviceMonitorMacImpl {
321  public:
322   AVFoundationMonitorImpl(
323       content::DeviceMonitorMac* monitor,
324       const scoped_refptr<base::SingleThreadTaskRunner>& device_task_runner);
325   virtual ~AVFoundationMonitorImpl();
327   virtual void OnDeviceChanged() OVERRIDE;
329  private:
330   base::ThreadChecker thread_checker_;
332   // {Video,AudioInput}DeviceManager's "Device" thread task runner used for
333   // posting tasks to |suspend_observer_delegate_|; valid after
334   // MediaStreamManager calls StartMonitoring().
335   const scoped_refptr<base::SingleThreadTaskRunner> device_task_runner_;
337   scoped_refptr<SuspendObserverDelegate> suspend_observer_delegate_;
339   DISALLOW_COPY_AND_ASSIGN(AVFoundationMonitorImpl);
342 AVFoundationMonitorImpl::AVFoundationMonitorImpl(
343     content::DeviceMonitorMac* monitor,
344     const scoped_refptr<base::SingleThreadTaskRunner>& device_task_runner)
345     : DeviceMonitorMacImpl(monitor),
346       device_task_runner_(device_task_runner),
347       suspend_observer_delegate_(new SuspendObserverDelegate(this)) {
348   NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
349   device_arrival_ =
350       [nc addObserverForName:AVFoundationGlue::
351           AVCaptureDeviceWasConnectedNotification()
352                       object:nil
353                        queue:nil
354                   usingBlock:^(NSNotification* notification) {
355                       OnDeviceChanged();}];
356   device_removal_ =
357       [nc addObserverForName:AVFoundationGlue::
358           AVCaptureDeviceWasDisconnectedNotification()
359                       object:nil
360                        queue:nil
361                   usingBlock:^(NSNotification* notification) {
362                       OnDeviceChanged();}];
363   device_task_runner_->PostTask(FROM_HERE,
364       base::Bind(&SuspendObserverDelegate::StartObserver,
365                  suspend_observer_delegate_));
368 AVFoundationMonitorImpl::~AVFoundationMonitorImpl() {
369   DCHECK(thread_checker_.CalledOnValidThread());
370   suspend_observer_delegate_->ResetDeviceMonitorOnUIThread();
371   NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
372   [nc removeObserver:device_arrival_];
373   [nc removeObserver:device_removal_];
376 void AVFoundationMonitorImpl::OnDeviceChanged() {
377   DCHECK(thread_checker_.CalledOnValidThread());
378   device_task_runner_->PostTask(FROM_HERE,
379       base::Bind(&SuspendObserverDelegate::OnDeviceChanged,
380                  suspend_observer_delegate_));
383 }  // namespace
385 @implementation CrAVFoundationDeviceObserver
387 - (id)initWithChangeReceiver:(SuspendObserverDelegate*)receiver {
388   if ((self = [super init])) {
389     DCHECK(receiver != NULL);
390     receiver_ = receiver;
391   }
392   return self;
395 - (void)dealloc {
396   std::set<CrAVCaptureDevice*>::iterator it = monitoredDevices_.begin();
397   while (it != monitoredDevices_.end())
398     [self stopObserving:*it++];
399   [super dealloc];
402 - (void)startObserving:(CrAVCaptureDevice*)device {
403   DCHECK(device != nil);
404   // Skip this device if there are already observers connected to it.
405   if (std::find(monitoredDevices_.begin(), monitoredDevices_.end(), device) !=
406           monitoredDevices_.end()) {
407     return;
408   }
409   [device addObserver:self
410            forKeyPath:@"suspended"
411               options:0
412               context:device];
413   [device addObserver:self
414            forKeyPath:@"connected"
415               options:0
416               context:device];
417   monitoredDevices_.insert(device);
420 - (void)stopObserving:(CrAVCaptureDevice*)device {
421   DCHECK(device != nil);
422   std::set<CrAVCaptureDevice*>::iterator found =
423       std::find(monitoredDevices_.begin(), monitoredDevices_.end(), device);
424   DCHECK(found != monitoredDevices_.end());
425   // Every so seldom, |device| might be gone when getting here, in that case
426   // removing the observer causes a crash. Try to avoid it by checking sanity of
427   // the |device| via its -observationInfo. http://crbug.com/371271.
428   if ([device observationInfo]) {
429     [device removeObserver:self
430                 forKeyPath:@"suspended"];
431     [device removeObserver:self
432                 forKeyPath:@"connected"];
433   }
434   monitoredDevices_.erase(found);
437 - (void)observeValueForKeyPath:(NSString*)keyPath
438                       ofObject:(id)object
439                         change:(NSDictionary*)change
440                        context:(void*)context {
441   if ([keyPath isEqual:@"suspended"])
442     receiver_->OnDeviceChanged();
443   if ([keyPath isEqual:@"connected"])
444     [self stopObserving:static_cast<CrAVCaptureDevice*>(context)];
447 @end  // @implementation CrAVFoundationDeviceObserver
449 namespace content {
451 DeviceMonitorMac::DeviceMonitorMac() {
452   // Both QTKit and AVFoundation do not need to be fired up until the user
453   // exercises a GetUserMedia. Bringing up either library and enumerating the
454   // devices in the system is an operation taking in the range of hundred of ms,
455   // so it is triggered explicitly from MediaStreamManager::StartMonitoring().
458 DeviceMonitorMac::~DeviceMonitorMac() {}
460 void DeviceMonitorMac::StartMonitoring(
461     const scoped_refptr<base::SingleThreadTaskRunner>& device_task_runner) {
462   DCHECK(thread_checker_.CalledOnValidThread());
463   if (AVFoundationGlue::IsAVFoundationSupported()) {
464     DVLOG(1) << "Monitoring via AVFoundation";
465     device_monitor_impl_.reset(new AVFoundationMonitorImpl(this,
466                                                            device_task_runner));
467   } else {
468     DVLOG(1) << "Monitoring via QTKit";
469     device_monitor_impl_.reset(new QTKitMonitorImpl(this));
470   }
473 void DeviceMonitorMac::NotifyDeviceChanged(
474     base::SystemMonitor::DeviceType type) {
475   DCHECK(thread_checker_.CalledOnValidThread());
476   // TODO(xians): Remove the global variable for SystemMonitor.
477   base::SystemMonitor::Get()->ProcessDevicesChanged(type);
480 }  // namespace content