| // Copyright 2013 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 "device/bluetooth/bluetooth_adapter_mac.h" |
| |
| #import <IOBluetooth/objc/IOBluetoothDevice.h> |
| #import <IOBluetooth/objc/IOBluetoothDeviceInquiry.h> |
| #import <IOBluetooth/objc/IOBluetoothHostController.h> |
| |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "base/compiler_specific.h" |
| #include "base/containers/hash_tables.h" |
| #include "base/location.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "device/bluetooth/bluetooth_device_mac.h" |
| |
| // Replicate specific 10.7 SDK declarations for building with prior SDKs. |
| #if !defined(MAC_OS_X_VERSION_10_7) || \ |
| MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 |
| |
| @interface IOBluetoothHostController (LionSDKDeclarations) |
| - (NSString*)nameAsString; |
| - (BluetoothHCIPowerState)powerState; |
| @end |
| |
| @protocol IOBluetoothDeviceInquiryDelegate |
| - (void)deviceInquiryStarted:(IOBluetoothDeviceInquiry*)sender; |
| - (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry*)sender |
| device:(IOBluetoothDevice*)device; |
| - (void)deviceInquiryComplete:(IOBluetoothDeviceInquiry*)sender |
| error:(IOReturn)error |
| aborted:(BOOL)aborted; |
| @end |
| |
| #endif // MAC_OS_X_VERSION_10_7 |
| |
| @interface BluetoothAdapterMacDelegate |
| : NSObject <IOBluetoothDeviceInquiryDelegate> { |
| @private |
| device::BluetoothAdapterMac* adapter_; // weak |
| } |
| |
| - (id)initWithAdapter:(device::BluetoothAdapterMac*)adapter; |
| |
| @end |
| |
| @implementation BluetoothAdapterMacDelegate |
| |
| - (id)initWithAdapter:(device::BluetoothAdapterMac*)adapter { |
| if ((self = [super init])) |
| adapter_ = adapter; |
| |
| return self; |
| } |
| |
| - (void)deviceInquiryStarted:(IOBluetoothDeviceInquiry*)sender { |
| adapter_->DeviceInquiryStarted(sender); |
| } |
| |
| - (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry*)sender |
| device:(IOBluetoothDevice*)device { |
| adapter_->DeviceFound(sender, device); |
| } |
| |
| - (void)deviceInquiryComplete:(IOBluetoothDeviceInquiry*)sender |
| error:(IOReturn)error |
| aborted:(BOOL)aborted { |
| adapter_->DeviceInquiryComplete(sender, error, aborted); |
| } |
| |
| @end |
| |
| namespace { |
| |
| const int kPollIntervalMs = 500; |
| |
| } // namespace |
| |
| namespace device { |
| |
| // static |
| base::WeakPtr<BluetoothAdapter> BluetoothAdapter::CreateAdapter( |
| const InitCallback& init_callback) { |
| return BluetoothAdapterMac::CreateAdapter(); |
| } |
| |
| // static |
| base::WeakPtr<BluetoothAdapter> BluetoothAdapterMac::CreateAdapter() { |
| BluetoothAdapterMac* adapter = new BluetoothAdapterMac(); |
| adapter->Init(); |
| return adapter->weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| BluetoothAdapterMac::BluetoothAdapterMac() |
| : BluetoothAdapter(), |
| powered_(false), |
| discovery_status_(NOT_DISCOVERING), |
| adapter_delegate_( |
| [[BluetoothAdapterMacDelegate alloc] initWithAdapter:this]), |
| device_inquiry_( |
| [[IOBluetoothDeviceInquiry |
| inquiryWithDelegate:adapter_delegate_] retain]), |
| weak_ptr_factory_(this) { |
| } |
| |
| BluetoothAdapterMac::~BluetoothAdapterMac() { |
| } |
| |
| void BluetoothAdapterMac::AddObserver(BluetoothAdapter::Observer* observer) { |
| DCHECK(observer); |
| observers_.AddObserver(observer); |
| } |
| |
| void BluetoothAdapterMac::RemoveObserver(BluetoothAdapter::Observer* observer) { |
| DCHECK(observer); |
| observers_.RemoveObserver(observer); |
| } |
| |
| std::string BluetoothAdapterMac::GetAddress() const { |
| return address_; |
| } |
| |
| std::string BluetoothAdapterMac::GetName() const { |
| return name_; |
| } |
| |
| void BluetoothAdapterMac::SetName(const std::string& name, |
| const base::Closure& callback, |
| const ErrorCallback& error_callback) { |
| NOTIMPLEMENTED(); |
| } |
| |
| bool BluetoothAdapterMac::IsInitialized() const { |
| return true; |
| } |
| |
| bool BluetoothAdapterMac::IsPresent() const { |
| return !address_.empty(); |
| } |
| |
| bool BluetoothAdapterMac::IsPowered() const { |
| return powered_; |
| } |
| |
| void BluetoothAdapterMac::SetPowered(bool powered, |
| const base::Closure& callback, |
| const ErrorCallback& error_callback) { |
| NOTIMPLEMENTED(); |
| } |
| |
| bool BluetoothAdapterMac::IsDiscoverable() const { |
| NOTIMPLEMENTED(); |
| return false; |
| } |
| |
| void BluetoothAdapterMac::SetDiscoverable( |
| bool discoverable, |
| const base::Closure& callback, |
| const ErrorCallback& error_callback) { |
| NOTIMPLEMENTED(); |
| } |
| |
| bool BluetoothAdapterMac::IsDiscovering() const { |
| return discovery_status_ == DISCOVERING || |
| discovery_status_ == DISCOVERY_STOPPING; |
| } |
| |
| void BluetoothAdapterMac::ReadLocalOutOfBandPairingData( |
| const BluetoothOutOfBandPairingDataCallback& callback, |
| const ErrorCallback& error_callback) { |
| } |
| |
| void BluetoothAdapterMac::AddDiscoverySession( |
| const base::Closure& callback, |
| const ErrorCallback& error_callback) { |
| if (discovery_status_ == DISCOVERING) { |
| num_discovery_listeners_++; |
| callback.Run(); |
| return; |
| } |
| on_start_discovery_callbacks_.push_back( |
| std::make_pair(callback, error_callback)); |
| MaybeStartDeviceInquiry(); |
| } |
| |
| void BluetoothAdapterMac::RemoveDiscoverySession( |
| const base::Closure& callback, |
| const ErrorCallback& error_callback) { |
| if (discovery_status_ == NOT_DISCOVERING) { |
| error_callback.Run(); |
| return; |
| } |
| on_stop_discovery_callbacks_.push_back( |
| std::make_pair(callback, error_callback)); |
| MaybeStopDeviceInquiry(); |
| } |
| |
| void BluetoothAdapterMac::RemovePairingDelegateInternal( |
| BluetoothDevice::PairingDelegate* pairing_delegate) { |
| } |
| |
| void BluetoothAdapterMac::Init() { |
| ui_task_runner_ = base::ThreadTaskRunnerHandle::Get(); |
| PollAdapter(); |
| } |
| |
| void BluetoothAdapterMac::InitForTest( |
| scoped_refptr<base::SequencedTaskRunner> ui_task_runner) { |
| ui_task_runner_ = ui_task_runner; |
| PollAdapter(); |
| } |
| |
| void BluetoothAdapterMac::PollAdapter() { |
| bool was_present = IsPresent(); |
| std::string name; |
| std::string address; |
| bool powered = false; |
| IOBluetoothHostController* controller = |
| [IOBluetoothHostController defaultController]; |
| |
| if (controller != nil) { |
| name = base::SysNSStringToUTF8([controller nameAsString]); |
| address = BluetoothDeviceMac::NormalizeAddress( |
| base::SysNSStringToUTF8([controller addressAsString])); |
| powered = ([controller powerState] == kBluetoothHCIPowerStateON); |
| } |
| |
| bool is_present = !address.empty(); |
| name_ = name; |
| address_ = address; |
| |
| if (was_present != is_present) { |
| FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, |
| AdapterPresentChanged(this, is_present)); |
| } |
| if (powered_ != powered) { |
| powered_ = powered; |
| FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, |
| AdapterPoweredChanged(this, powered_)); |
| } |
| |
| IOBluetoothDevice* recent_device = |
| [[IOBluetoothDevice recentDevices:1] lastObject]; |
| NSDate* access_timestamp = [recent_device recentAccessDate]; |
| if (recently_accessed_device_timestamp_ == nil || |
| access_timestamp == nil || |
| [recently_accessed_device_timestamp_ compare:access_timestamp] == |
| NSOrderedAscending) { |
| UpdateDevices([IOBluetoothDevice pairedDevices]); |
| recently_accessed_device_timestamp_.reset([access_timestamp copy]); |
| } |
| |
| ui_task_runner_->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&BluetoothAdapterMac::PollAdapter, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::TimeDelta::FromMilliseconds(kPollIntervalMs)); |
| } |
| |
| void BluetoothAdapterMac::UpdateDevices(NSArray* devices) { |
| STLDeleteValues(&devices_); |
| for (IOBluetoothDevice* device in devices) { |
| std::string device_address = BluetoothDeviceMac::GetDeviceAddress(device); |
| devices_[device_address] = new BluetoothDeviceMac(device); |
| } |
| } |
| |
| void BluetoothAdapterMac::DeviceInquiryStarted( |
| IOBluetoothDeviceInquiry* inquiry) { |
| DCHECK_EQ(device_inquiry_, inquiry); |
| if (discovery_status_ == DISCOVERING) |
| return; |
| |
| discovery_status_ = DISCOVERING; |
| RunCallbacks(on_start_discovery_callbacks_, true); |
| num_discovery_listeners_ = on_start_discovery_callbacks_.size(); |
| on_start_discovery_callbacks_.clear(); |
| |
| FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, |
| AdapterDiscoveringChanged(this, true)); |
| MaybeStopDeviceInquiry(); |
| } |
| |
| void BluetoothAdapterMac::DeviceFound(IOBluetoothDeviceInquiry* inquiry, |
| IOBluetoothDevice* device) { |
| DCHECK_EQ(device_inquiry_, inquiry); |
| std::string device_address = BluetoothDeviceMac::GetDeviceAddress(device); |
| if (discovered_devices_.find(device_address) == discovered_devices_.end()) { |
| BluetoothDeviceMac device_mac(device); |
| FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, |
| DeviceAdded(this, &device_mac)); |
| discovered_devices_.insert(device_address); |
| } |
| } |
| |
| void BluetoothAdapterMac::DeviceInquiryComplete( |
| IOBluetoothDeviceInquiry* inquiry, |
| IOReturn error, |
| bool aborted) { |
| DCHECK_EQ(device_inquiry_, inquiry); |
| if (discovery_status_ == DISCOVERING && |
| [device_inquiry_ start] == kIOReturnSuccess) { |
| return; |
| } |
| |
| // Device discovery is done. |
| discovered_devices_.clear(); |
| discovery_status_ = NOT_DISCOVERING; |
| RunCallbacks(on_stop_discovery_callbacks_, error == kIOReturnSuccess); |
| num_discovery_listeners_ = 0; |
| on_stop_discovery_callbacks_.clear(); |
| FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, |
| AdapterDiscoveringChanged(this, false)); |
| MaybeStartDeviceInquiry(); |
| } |
| |
| void BluetoothAdapterMac::MaybeStartDeviceInquiry() { |
| if (discovery_status_ == NOT_DISCOVERING && |
| !on_start_discovery_callbacks_.empty()) { |
| discovery_status_ = DISCOVERY_STARTING; |
| if ([device_inquiry_ start] != kIOReturnSuccess) { |
| discovery_status_ = NOT_DISCOVERING; |
| RunCallbacks(on_start_discovery_callbacks_, false); |
| on_start_discovery_callbacks_.clear(); |
| } |
| } |
| } |
| |
| void BluetoothAdapterMac::MaybeStopDeviceInquiry() { |
| if (discovery_status_ != DISCOVERING) |
| return; |
| |
| if (on_stop_discovery_callbacks_.size() < num_discovery_listeners_) { |
| RunCallbacks(on_stop_discovery_callbacks_, true); |
| num_discovery_listeners_ -= on_stop_discovery_callbacks_.size(); |
| on_stop_discovery_callbacks_.clear(); |
| return; |
| } |
| |
| discovery_status_ = DISCOVERY_STOPPING; |
| if ([device_inquiry_ stop] != kIOReturnSuccess) { |
| RunCallbacks(on_stop_discovery_callbacks_, false); |
| on_stop_discovery_callbacks_.clear(); |
| } |
| } |
| |
| void BluetoothAdapterMac::RunCallbacks( |
| const DiscoveryCallbackList& callback_list, bool success) const { |
| for (DiscoveryCallbackList::const_iterator iter = callback_list.begin(); |
| iter != callback_list.end(); |
| ++iter) { |
| if (success) |
| ui_task_runner_->PostTask(FROM_HERE, iter->first); |
| else |
| ui_task_runner_->PostTask(FROM_HERE, iter->second); |
| } |
| } |
| |
| } // namespace device |