blob: d44a0f37773e5a4eb56d7a49a0c6153cca72f61f [file] [log] [blame]
// 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