// 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 #import #include #include "base/bind.h" #include "base/compiler_specific.h" #include "base/containers/hash_tables.h" #include "base/location.h" #include "base/mac/mac_util.h" #include "base/mac/sdk_forward_declarations.h" #include "base/memory/scoped_ptr.h" #include "base/profiler/scoped_tracker.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_classic_device_mac.h" #include "device/bluetooth/bluetooth_discovery_session.h" #include "device/bluetooth/bluetooth_discovery_session_outcome.h" #include "device/bluetooth/bluetooth_low_energy_central_manager_delegate.h" #include "device/bluetooth/bluetooth_socket_mac.h" #include "device/bluetooth/bluetooth_uuid.h" namespace { // The frequency with which to poll the adapter for updates. const int kPollIntervalMs = 500; } // namespace namespace device { // static const NSTimeInterval BluetoothAdapterMac::kDiscoveryTimeoutSec = 180; // 3 minutes // static base::WeakPtr BluetoothAdapter::CreateAdapter( const InitCallback& init_callback) { return BluetoothAdapterMac::CreateAdapter(); } // static base::WeakPtr BluetoothAdapterMac::CreateAdapter() { BluetoothAdapterMac* adapter = new BluetoothAdapterMac(); adapter->Init(); return adapter->weak_ptr_factory_.GetWeakPtr(); } // static base::WeakPtr BluetoothAdapterMac::CreateAdapterForTest( std::string name, std::string address, scoped_refptr ui_task_runner) { BluetoothAdapterMac* adapter = new BluetoothAdapterMac(); adapter->InitForTest(ui_task_runner); adapter->name_ = name; adapter->address_ = address; return adapter->weak_ptr_factory_.GetWeakPtr(); } BluetoothAdapterMac::BluetoothAdapterMac() : BluetoothAdapter(), classic_powered_(false), num_discovery_sessions_(0), classic_discovery_manager_( BluetoothDiscoveryManagerMac::CreateClassic(this)), weak_ptr_factory_(this) { if (IsLowEnergyAvailable()) { low_energy_discovery_manager_.reset( BluetoothLowEnergyDiscoveryManagerMac::Create(this)); low_energy_central_manager_delegate_.reset( [[BluetoothLowEnergyCentralManagerDelegate alloc] initWithDiscoveryManager:low_energy_discovery_manager_.get() andAdapter:this]); Class aClass = NSClassFromString(@"CBCentralManager"); low_energy_central_manager_.reset([[aClass alloc] initWithDelegate:low_energy_central_manager_delegate_.get() queue:dispatch_get_main_queue()]); low_energy_discovery_manager_->SetCentralManager( low_energy_central_manager_.get()); } DCHECK(classic_discovery_manager_.get()); } BluetoothAdapterMac::~BluetoothAdapterMac() { } 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 { bool is_present = !address_.empty(); if (IsLowEnergyAvailable()) { is_present = is_present || ([low_energy_central_manager_ state] == CBCentralManagerStatePoweredOn); } return is_present; } bool BluetoothAdapterMac::IsPowered() const { bool is_powered = classic_powered_; if (IsLowEnergyAvailable()) { is_powered = is_powered || ([low_energy_central_manager_ state] == CBCentralManagerStatePoweredOn); } return is_powered; } void BluetoothAdapterMac::SetPowered(bool powered, const base::Closure& callback, const ErrorCallback& error_callback) { NOTIMPLEMENTED(); } // TODO(krstnmnlsn): If this information is retrievable form IOBluetooth we // should return the discoverable status. bool BluetoothAdapterMac::IsDiscoverable() const { return false; } void BluetoothAdapterMac::SetDiscoverable( bool discoverable, const base::Closure& callback, const ErrorCallback& error_callback) { NOTIMPLEMENTED(); } bool BluetoothAdapterMac::IsDiscovering() const { bool is_discovering = classic_discovery_manager_->IsDiscovering(); if (IsLowEnergyAvailable()) is_discovering = is_discovering || low_energy_discovery_manager_->IsDiscovering(); return is_discovering; } void BluetoothAdapterMac::CreateRfcommService( const BluetoothUUID& uuid, const ServiceOptions& options, const CreateServiceCallback& callback, const CreateServiceErrorCallback& error_callback) { scoped_refptr socket = BluetoothSocketMac::CreateSocket(); socket->ListenUsingRfcomm( this, uuid, options, base::Bind(callback, socket), error_callback); } void BluetoothAdapterMac::CreateL2capService( const BluetoothUUID& uuid, const ServiceOptions& options, const CreateServiceCallback& callback, const CreateServiceErrorCallback& error_callback) { scoped_refptr socket = BluetoothSocketMac::CreateSocket(); socket->ListenUsingL2cap( this, uuid, options, base::Bind(callback, socket), error_callback); } void BluetoothAdapterMac::RegisterAudioSink( const BluetoothAudioSink::Options& options, const AcquiredCallback& callback, const BluetoothAudioSink::ErrorCallback& error_callback) { NOTIMPLEMENTED(); error_callback.Run(BluetoothAudioSink::ERROR_UNSUPPORTED_PLATFORM); } void BluetoothAdapterMac::RegisterAdvertisement( scoped_ptr advertisement_data, const CreateAdvertisementCallback& callback, const CreateAdvertisementErrorCallback& error_callback) { NOTIMPLEMENTED(); error_callback.Run(BluetoothAdvertisement::ERROR_UNSUPPORTED_PLATFORM); } void BluetoothAdapterMac::ClassicDeviceFound(IOBluetoothDevice* device) { ClassicDeviceAdded(device); } void BluetoothAdapterMac::ClassicDiscoveryStopped(bool unexpected) { 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): Investigate whether this method can be replaced with a call // to +registerForConnectNotifications:selector:. DVLOG(1) << "Adapter registered a new connection from device with address: " << BluetoothClassicDeviceMac::GetDeviceAddress(device); ClassicDeviceAdded(device); } // static bool BluetoothAdapterMac::IsLowEnergyAvailable() { return base::mac::IsOSYosemiteOrLater(); } void BluetoothAdapterMac::SetCentralManagerForTesting( CBCentralManager* central_manager) { CHECK(BluetoothAdapterMac::IsLowEnergyAvailable()); [central_manager performSelector:@selector(setDelegate:) withObject:low_energy_central_manager_delegate_]; low_energy_central_manager_.reset(central_manager); low_energy_discovery_manager_->SetCentralManager( low_energy_central_manager_.get()); } void BluetoothAdapterMac::RemovePairingDelegateInternal( BluetoothDevice::PairingDelegate* pairing_delegate) { } void BluetoothAdapterMac::AddDiscoverySession( BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, const DiscoverySessionErrorCallback& error_callback) { DVLOG(1) << __func__; if (num_discovery_sessions_ > 0) { DCHECK(IsDiscovering()); num_discovery_sessions_++; // We are already running a discovery session, notify the system if the // filter has changed. if (!StartDiscovery(discovery_filter)) { // TODO: Provide a more precise error here. error_callback.Run(UMABluetoothDiscoverySessionOutcome::UNKNOWN); return; } callback.Run(); return; } DCHECK_EQ(0, num_discovery_sessions_); if (!StartDiscovery(discovery_filter)) { // TODO: Provide a more precise error here. error_callback.Run(UMABluetoothDiscoverySessionOutcome::UNKNOWN); return; } DVLOG(1) << "Added a discovery session"; num_discovery_sessions_++; FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, AdapterDiscoveringChanged(this, true)); callback.Run(); } void BluetoothAdapterMac::RemoveDiscoverySession( BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, const DiscoverySessionErrorCallback& 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(UMABluetoothDiscoverySessionOutcome::NOT_ACTIVE); return; } // Default to dual discovery if |discovery_filter| is NULL. BluetoothDiscoveryFilter::TransportMask transport = BluetoothDiscoveryFilter::Transport::TRANSPORT_DUAL; if (discovery_filter) transport = discovery_filter->GetTransport(); if (transport & BluetoothDiscoveryFilter::Transport::TRANSPORT_CLASSIC) { if (!classic_discovery_manager_->StopDiscovery()) { DVLOG(1) << "Failed to stop classic discovery"; // TODO: Provide a more precise error here. error_callback.Run(UMABluetoothDiscoverySessionOutcome::UNKNOWN); return; } } if (transport & BluetoothDiscoveryFilter::Transport::TRANSPORT_LE) { if (IsLowEnergyAvailable()) low_energy_discovery_manager_->StopDiscovery(); } DVLOG(1) << "Discovery stopped"; num_discovery_sessions_--; callback.Run(); } void BluetoothAdapterMac::SetDiscoveryFilter( scoped_ptr discovery_filter, const base::Closure& callback, const DiscoverySessionErrorCallback& error_callback) { NOTIMPLEMENTED(); error_callback.Run(UMABluetoothDiscoverySessionOutcome::NOT_IMPLEMENTED); } bool BluetoothAdapterMac::StartDiscovery( BluetoothDiscoveryFilter* discovery_filter) { // Default to dual discovery if |discovery_filter| is NULL. IOBluetooth seems // allow starting low energy and classic discovery at once. BluetoothDiscoveryFilter::TransportMask transport = BluetoothDiscoveryFilter::Transport::TRANSPORT_DUAL; if (discovery_filter) transport = discovery_filter->GetTransport(); if ((transport & BluetoothDiscoveryFilter::Transport::TRANSPORT_CLASSIC) && !classic_discovery_manager_->IsDiscovering()) { // TODO(krstnmnlsn): If a classic discovery session is already running then // we should update its filter. crbug.com/498056 if (!classic_discovery_manager_->StartDiscovery()) { DVLOG(1) << "Failed to add a classic discovery session"; return false; } } if (transport & BluetoothDiscoveryFilter::Transport::TRANSPORT_LE) { // Begin a low energy discovery session or update it if one is already // running. if (IsLowEnergyAvailable()) low_energy_discovery_manager_->StartDiscovery( BluetoothDevice::UUIDList()); } return true; } void BluetoothAdapterMac::Init() { ui_task_runner_ = base::ThreadTaskRunnerHandle::Get(); PollAdapter(); } void BluetoothAdapterMac::InitForTest( scoped_refptr ui_task_runner) { ui_task_runner_ = ui_task_runner; } void BluetoothAdapterMac::PollAdapter() { // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181 // is fixed. tracked_objects::ScopedTracker tracking_profile1( FROM_HERE_WITH_EXPLICIT_FUNCTION( "461181 BluetoothAdapterMac::PollAdapter::Start")); bool was_present = IsPresent(); std::string address; bool classic_powered = false; IOBluetoothHostController* controller = [IOBluetoothHostController defaultController]; // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181 // is fixed. tracked_objects::ScopedTracker tracking_profile2( FROM_HERE_WITH_EXPLICIT_FUNCTION( "461181 BluetoothAdapterMac::PollAdapter::GetControllerStats")); if (controller != nil) { address = BluetoothDevice::CanonicalizeAddress( base::SysNSStringToUTF8([controller addressAsString])); classic_powered = ([controller powerState] == kBluetoothHCIPowerStateON); // For performance reasons, cache the adapter's name. It's not uncommon for // a call to [controller nameAsString] to take tens of milliseconds. Note // that this caching strategy might result in clients receiving a stale // name. If this is a significant issue, then some more sophisticated // workaround for the performance bottleneck will be needed. For additional // context, see http://crbug.com/461181 and http://crbug.com/467316 if (address != address_ || (!address.empty() && name_.empty())) name_ = base::SysNSStringToUTF8([controller nameAsString]); } bool is_present = !address.empty(); address_ = address; // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181 // is fixed. tracked_objects::ScopedTracker tracking_profile3( FROM_HERE_WITH_EXPLICIT_FUNCTION( "461181 BluetoothAdapterMac::PollAdapter::AdapterPresentChanged")); if (was_present != is_present) { FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, AdapterPresentChanged(this, is_present)); } // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181 // is fixed. tracked_objects::ScopedTracker tracking_profile4( FROM_HERE_WITH_EXPLICIT_FUNCTION( "461181 BluetoothAdapterMac::PollAdapter::AdapterPowerChanged")); if (classic_powered_ != classic_powered) { classic_powered_ = classic_powered; FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, AdapterPoweredChanged(this, classic_powered_)); } // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181 // is fixed. tracked_objects::ScopedTracker tracking_profile5( FROM_HERE_WITH_EXPLICIT_FUNCTION( "461181 BluetoothAdapterMac::PollAdapter::RemoveTimedOutDevices")); RemoveTimedOutDevices(); // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181 // is fixed. tracked_objects::ScopedTracker tracking_profile6( FROM_HERE_WITH_EXPLICIT_FUNCTION( "461181 BluetoothAdapterMac::PollAdapter::AddPairedDevices")); AddPairedDevices(); ui_task_runner_->PostDelayedTask( FROM_HERE, base::Bind(&BluetoothAdapterMac::PollAdapter, weak_ptr_factory_.GetWeakPtr()), base::TimeDelta::FromMilliseconds(kPollIntervalMs)); } void BluetoothAdapterMac::ClassicDeviceAdded(IOBluetoothDevice* device) { std::string device_address = BluetoothClassicDeviceMac::GetDeviceAddress(device); // Only notify observers once per device. if (devices_.count(device_address)) return; BluetoothDevice* device_classic = new BluetoothClassicDeviceMac(this, device); devices_.set(device_address, make_scoped_ptr(device_classic)); FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, DeviceAdded(this, device_classic)); } void BluetoothAdapterMac::LowEnergyDeviceUpdated( CBPeripheral* peripheral, NSDictionary* advertisement_data, int rssi) { std::string device_address = BluetoothLowEnergyDeviceMac::GetPeripheralHashAddress(peripheral); // Try to find device from |devices_| with key |device_address|, // if has no entry in the map, create new device and insert into |devices_|, // otherwise update the existing device. DevicesMap::const_iterator iter = devices_.find(device_address); if (iter == devices_.end()) { VLOG(1) << "LowEnergyDeviceUpdated new device"; // A new device has been found. BluetoothLowEnergyDeviceMac* device_mac = new BluetoothLowEnergyDeviceMac( this, peripheral, advertisement_data, rssi); devices_.add(device_address, scoped_ptr(device_mac)); FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, DeviceAdded(this, device_mac)); return; } BluetoothLowEnergyDeviceMac* device_mac = static_cast(iter->second); std::string stored_device_id = device_mac->GetIdentifier(); std::string updated_device_id = BluetoothLowEnergyDeviceMac::GetPeripheralIdentifier(peripheral); if (stored_device_id != updated_device_id) { VLOG(1) << "LowEnergyDeviceUpdated stored_device_id != updated_device_id: " << std::endl << " " << stored_device_id << std::endl << " " << updated_device_id; // Collision, two identifiers map to the same hash address. With a 48 bit // hash the probability of this occuring with 10,000 devices // simultaneously present is 1e-6 (see // https://en.wikipedia.org/wiki/Birthday_problem#Probability_table). We // ignore the second device by returning. return; } // A device has an update. VLOG(2) << "LowEnergyDeviceUpdated"; device_mac->Update(peripheral, advertisement_data, rssi); // TODO(scheib): Call DeviceChanged only if UUIDs change. crbug.com/547106 FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, DeviceChanged(this, device_mac)); } // TODO(krstnmnlsn): Implement. crbug.com/511025 void BluetoothAdapterMac::LowEnergyCentralManagerUpdatedState() {} void BluetoothAdapterMac::RemoveTimedOutDevices() { // Notify observers if any previously seen devices are no longer available, // i.e. if they are no longer paired, connected, nor recently discovered via // an inquiry. std::set removed_devices; for (DevicesMap::const_iterator it = devices_.begin(); it != devices_.end(); ++it) { BluetoothDevice* device = it->second; if (device->IsPaired() || device->IsConnected()) continue; NSDate* last_update_time = static_cast(device)->GetLastUpdateTime(); if (last_update_time && -[last_update_time timeIntervalSinceNow] < kDiscoveryTimeoutSec) continue; FOR_EACH_OBSERVER( BluetoothAdapter::Observer, observers_, DeviceRemoved(this, device)); removed_devices.insert(it->first); // The device will be erased from the map in the loop immediately below. } for (const std::string& device_address : removed_devices) { size_t num_removed = devices_.erase(device_address); DCHECK_EQ(num_removed, 1U); } } void BluetoothAdapterMac::AddPairedDevices() { // Add any new paired devices. for (IOBluetoothDevice* device in [IOBluetoothDevice pairedDevices]) { ClassicDeviceAdded(device); } } } // namespace device