| // 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/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/mac/sdk_forward_declarations.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" |
| #include "device/bluetooth/bluetooth_socket_mac.h" |
| #include "device/bluetooth/bluetooth_uuid.h" |
| |
| 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), |
| num_discovery_sessions_(0), |
| classic_discovery_manager_( |
| BluetoothDiscoveryManagerMac::CreateClassic(this)), |
| weak_ptr_factory_(this) { |
| DCHECK(classic_discovery_manager_.get()); |
| } |
| |
| 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 classic_discovery_manager_->IsDiscovering(); |
| } |
| |
| void BluetoothAdapterMac::CreateRfcommService( |
| const BluetoothUUID& uuid, |
| int channel, |
| const CreateServiceCallback& callback, |
| const CreateServiceErrorCallback& error_callback) { |
| scoped_refptr<BluetoothSocketMac> socket = BluetoothSocketMac::CreateSocket(); |
| socket->ListenUsingRfcomm( |
| this, uuid, channel, base::Bind(callback, socket), error_callback); |
| } |
| |
| void BluetoothAdapterMac::CreateL2capService( |
| const BluetoothUUID& uuid, |
| int psm, |
| const CreateServiceCallback& callback, |
| const CreateServiceErrorCallback& error_callback) { |
| scoped_refptr<BluetoothSocketMac> socket = BluetoothSocketMac::CreateSocket(); |
| socket->ListenUsingL2cap( |
| this, uuid, psm, base::Bind(callback, socket), error_callback); |
| } |
| |
| void BluetoothAdapterMac::DeviceFound(BluetoothDiscoveryManagerMac* manager, |
| IOBluetoothDevice* device) { |
| // TODO(isherman): The list of discovered devices is never reset. This should |
| // probably key off of |devices_| instead. Currently, if a device is paired, |
| // then unpaired, then paired again, the app would never hear about the second |
| // pairing. |
| 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::DiscoveryStopped( |
| BluetoothDiscoveryManagerMac* manager, |
| bool unexpected) { |
| DCHECK_EQ(manager, classic_discovery_manager_.get()); |
| if (unexpected) { |
| DVLOG(1) << "Discovery stopped unexpectedly"; |
| num_discovery_sessions_ = 0; |
| MarkDiscoverySessionsAsInactive(); |
| } |
| FOR_EACH_OBSERVER(BluetoothAdapter::Observer, |
| observers_, |
| AdapterDiscoveringChanged(this, false)); |
| } |
| |
| void BluetoothAdapterMac::DeviceConnected(IOBluetoothDevice* device) { |
| // TODO(isherman): Call -registerForDisconnectNotification:selector:, and |
| // investigate whether this method can be replaced with a call to |
| // +registerForConnectNotifications:selector:. |
| std::string device_address = BluetoothDeviceMac::GetDeviceAddress(device); |
| DVLOG(1) << "Adapter registered a new connection from device with address: " |
| << device_address; |
| |
| // Only notify once per device. |
| if (devices_.count(device_address)) |
| return; |
| |
| scoped_ptr<BluetoothDeviceMac> device_mac(new BluetoothDeviceMac(device)); |
| FOR_EACH_OBSERVER(BluetoothAdapter::Observer, |
| observers_, |
| DeviceAdded(this, device_mac.get())); |
| devices_[device_address] = device_mac.release(); |
| } |
| |
| void BluetoothAdapterMac::AddDiscoverySession( |
| const base::Closure& callback, |
| const ErrorCallback& error_callback) { |
| DVLOG(1) << __func__; |
| if (num_discovery_sessions_ > 0) { |
| DCHECK(IsDiscovering()); |
| num_discovery_sessions_++; |
| callback.Run(); |
| return; |
| } |
| |
| DCHECK_EQ(0, num_discovery_sessions_); |
| |
| if (!classic_discovery_manager_->StartDiscovery()) { |
| DVLOG(1) << "Failed to add a discovery session"; |
| error_callback.Run(); |
| return; |
| } |
| |
| DVLOG(1) << "Added a discovery session"; |
| num_discovery_sessions_++; |
| FOR_EACH_OBSERVER(BluetoothAdapter::Observer, |
| observers_, |
| AdapterDiscoveringChanged(this, true)); |
| callback.Run(); |
| } |
| |
| void BluetoothAdapterMac::RemoveDiscoverySession( |
| const base::Closure& callback, |
| const ErrorCallback& error_callback) { |
| DVLOG(1) << __func__; |
| |
| if (num_discovery_sessions_ > 1) { |
| // There are active sessions other than the one currently being removed. |
| DCHECK(IsDiscovering()); |
| num_discovery_sessions_--; |
| callback.Run(); |
| return; |
| } |
| |
| if (num_discovery_sessions_ == 0) { |
| DVLOG(1) << "No active discovery sessions. Returning error."; |
| error_callback.Run(); |
| return; |
| } |
| |
| if (!classic_discovery_manager_->StopDiscovery()) { |
| DVLOG(1) << "Failed to stop discovery"; |
| error_callback.Run(); |
| return; |
| } |
| |
| DVLOG(1) << "Discovery stopped"; |
| num_discovery_sessions_--; |
| callback.Run(); |
| } |
| |
| 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 = BluetoothDevice::CanonicalizeAddress( |
| 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_)); |
| } |
| |
| // TODO(isherman): This doesn't detect when a device is unpaired. |
| 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(); |
| 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() { |
| // Snapshot the devices observers were previously notified of. |
| // Note that the code below is careful to take ownership of any values that |
| // are erased from the map, since the map owns the memory for all its mapped |
| // devices. |
| DevicesMap old_devices = devices_; |
| |
| // Add all the paired devices. |
| devices_.clear(); |
| for (IOBluetoothDevice* device in [IOBluetoothDevice pairedDevices]) { |
| std::string device_address = BluetoothDeviceMac::GetDeviceAddress(device); |
| scoped_ptr<BluetoothDevice> device_mac(old_devices[device_address]); |
| if (!device_mac) |
| device_mac.reset(new BluetoothDeviceMac(device)); |
| devices_[device_address] = device_mac.release(); |
| old_devices.erase(device_address); |
| } |
| |
| // Add any unpaired connected devices. |
| for (const auto& old_device : old_devices) { |
| if (!old_device.second->IsConnected()) |
| continue; |
| |
| const std::string& device_address = old_device.first; |
| DCHECK(!devices_.count(device_address)); |
| devices_[device_address] = old_device.second; |
| old_devices.erase(device_address); |
| } |
| |
| // TODO(isherman): Notify observers of any devices that are no longer in |
| // range. Note that it's possible for a device to be neither paired nor |
| // connected, but to still be in range. |
| STLDeleteValues(&old_devices); |
| } |
| |
| } // namespace device |