Chromium Code Reviews| Index: device/bluetooth/bluetooth_profile_mac.mm |
| diff --git a/device/bluetooth/bluetooth_profile_mac.mm b/device/bluetooth/bluetooth_profile_mac.mm |
| index 8d676ecc71df16f561df052010079ae9eb90a05e..8b4a73c982a2d5f8fd4f34a80a1308b4959b3e7c 100644 |
| --- a/device/bluetooth/bluetooth_profile_mac.mm |
| +++ b/device/bluetooth/bluetooth_profile_mac.mm |
| @@ -4,16 +4,13 @@ |
| #include "device/bluetooth/bluetooth_profile_mac.h" |
| -#import <IOBluetooth/objc/IOBluetoothDevice.h> |
| -#import <IOBluetooth/objc/IOBluetoothSDPServiceRecord.h> |
| -#import <IOBluetooth/objc/IOBluetoothSDPUUID.h> |
| - |
| #include <vector> |
| #include "base/basictypes.h" |
| #include "base/bind.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| +#include "base/mac/scoped_cftyperef.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/sys_string_conversions.h" |
| @@ -31,11 +28,17 @@ |
| #endif // MAC_OS_X_VERSION_10_7 |
| +namespace device { |
| namespace { |
| const char kNoConnectionCallback[] = "Connection callback not set"; |
| const char kProfileNotFound[] = "Profile not found"; |
| +// It's safe to use 0 to represent an unregistered service, as implied by the |
| +// documentation at |
| +// https://developer.apple.com/library/mac/documentation/devicedrivers/conceptual/bluetooth/BT_Develop_BT_Apps/BT_Develop_BT_Apps.html |
| +const BluetoothSDPServiceRecordHandle kInvalidServiceRecordHandle = 0; |
| + |
| // Converts |uuid| to a IOBluetoothSDPUUID instance. |
| // |
| // |uuid| must be in the format of XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX. |
| @@ -54,29 +57,30 @@ IOBluetoothSDPUUID* GetIOBluetoothSDPUUID(const std::string& uuid) { |
| base::HexStringToBytes(numbers_only, &uuid_bytes_vector); |
| DCHECK(uuid_bytes_vector.size() == 16); |
| - return [IOBluetoothSDPUUID uuidWithBytes:&uuid_bytes_vector[0] |
| + return [IOBluetoothSDPUUID uuidWithBytes:&uuid_bytes_vector.front() |
| length:uuid_bytes_vector.size()]; |
| } |
| void OnConnectSuccessWithAdapter( |
| const base::Closure& callback, |
| - const device::BluetoothProfileMac::ConnectionCallback& connection_callback, |
| + const BluetoothProfileMac::ConnectionCallback& connection_callback, |
| const std::string& device_address, |
| - scoped_refptr<device::BluetoothSocketMac> socket, |
| - scoped_refptr<device::BluetoothAdapter> adapter) { |
| - const device::BluetoothDevice* device = adapter->GetDevice(device_address); |
| + scoped_refptr<BluetoothSocket> socket, |
| + scoped_refptr<BluetoothAdapter> adapter) { |
| + const BluetoothDevice* device = adapter->GetDevice(device_address); |
| if (device) { |
| connection_callback.Run(device, socket); |
| - callback.Run(); |
| + if (!callback.is_null()) |
| + callback.Run(); |
| } |
| } |
| void OnConnectSuccess( |
| const base::Closure& callback, |
| - const device::BluetoothProfileMac::ConnectionCallback& connection_callback, |
| + const BluetoothProfileMac::ConnectionCallback& connection_callback, |
| const std::string& device_address, |
| - scoped_refptr<device::BluetoothSocketMac> socket) { |
| - device::BluetoothAdapterFactory::GetAdapter( |
| + scoped_refptr<BluetoothSocket> socket) { |
| + BluetoothAdapterFactory::GetAdapter( |
| base::Bind(&OnConnectSuccessWithAdapter, |
| callback, |
| connection_callback, |
| @@ -84,16 +88,172 @@ void OnConnectSuccess( |
| socket)); |
| } |
| +// Converts the given |integer| to a string. |
| +NSString* IntToNSString(int integer) { |
| + return [[NSNumber numberWithInt:integer] stringValue]; |
| +} |
| + |
| +// Returns a dictionary containing the Bluetooth service definition |
| +// corresponding to the provided |uuid| and |options|. |
| +// TODO(isherman): Support more of the fields defined in |options|. |
| +NSDictionary* BuildServiceDefinition(const BluetoothUUID& uuid, |
| + const BluetoothProfile::Options& options) { |
| + NSMutableDictionary* service_definition = [NSMutableDictionary dictionary]; |
| + |
| + // TODO(isherman): The service's language is currently hardcoded to English. |
| + // The language should ideally be specified in the chrome.bluetooth API |
| + // instead. |
| + const int kEnglishLanguageBase = 100; |
| + const int kServiceNameKey = |
| + kEnglishLanguageBase + kBluetoothSDPAttributeIdentifierServiceName; |
| + NSString* service_name = base::SysUTF8ToNSString(options.name); |
| + [service_definition setObject:service_name |
| + forKey:IntToNSString(kServiceNameKey)]; |
| + |
| + const int kUUIDsKey = kBluetoothSDPAttributeIdentifierServiceClassIDList; |
| + NSArray* uuids = @[GetIOBluetoothSDPUUID(uuid.canonical_value())]; |
| + [service_definition setObject:uuids forKey:IntToNSString(kUUIDsKey)]; |
| + |
| + const int kProtocolDefinitionsKey = |
| + kBluetoothSDPAttributeIdentifierProtocolDescriptorList; |
| + NSArray* rfcomm_protocol_definition = |
| + @[[IOBluetoothSDPUUID uuid16:kBluetoothSDPUUID16RFCOMM], |
| + [NSNumber numberWithInt:options.channel]]; |
| + [service_definition setObject:@[rfcomm_protocol_definition] |
| + forKey:IntToNSString(kProtocolDefinitionsKey)]; |
| + |
| + return service_definition; |
| +} |
| + |
| +// Registers a Bluetooth service with the specifieid |uuid| and |options| in the |
| +// system SDP server. Returns a handle to the registered service, or |
| +// |kInvalidServcieRecordHandle| if the service could not be registered. |
| +BluetoothSDPServiceRecordHandle RegisterService( |
| + const BluetoothUUID& uuid, |
| + const BluetoothProfile::Options& options) { |
| + // Attempt to register the service. |
| + NSDictionary* service_definition = BuildServiceDefinition(uuid, options); |
| + IOBluetoothSDPServiceRecordRef service_record_ref; |
| + IOReturn result = |
| + IOBluetoothAddServiceDict((CFDictionaryRef)service_definition, |
| + &service_record_ref); |
| + if (result != kIOReturnSuccess) |
| + return kInvalidServiceRecordHandle; |
| + // Transfer ownership to a scoped object, to simplify memory management. |
| + base::ScopedCFTypeRef<IOBluetoothSDPServiceRecordRef> |
| + scoped_service_record_ref(service_record_ref); |
| + |
| + // Extract the service record handle. |
| + BluetoothSDPServiceRecordHandle service_record_handle; |
| + IOBluetoothSDPServiceRecord* service_record = |
| + [IOBluetoothSDPServiceRecord withSDPServiceRecordRef:service_record_ref]; |
| + result = [service_record getServiceRecordHandle:&service_record_handle]; |
| + if (result != kIOReturnSuccess) |
| + return kInvalidServiceRecordHandle; |
| + |
| + // Verify that the requested channel id was available. If not, withdraw the |
| + // service. |
| + // TODO(isherman): Once the chrome.bluetooth API is updated, it should be |
| + // possible to register an RFCOMM service without explicitly specifying a |
| + // channel. In that case, we should be willing to bind to any channel, and not |
| + // try to require any specific channel as we currently do here. |
| + BluetoothRFCOMMChannelID rfcomm_channel_id; |
| + result = [service_record getRFCOMMChannelID:&rfcomm_channel_id]; |
| + if (result != kIOReturnSuccess || rfcomm_channel_id != options.channel) { |
| + IOBluetoothRemoveServiceWithRecordHandle(service_record_handle); |
| + return kInvalidServiceRecordHandle; |
| + } |
| + |
| + return service_record_handle; |
| +} |
| + |
| } // namespace |
| +} // namespace device |
| + |
| +// A simple helper class that forwards system notifications to its wrapped |
| +// |profile_|. |
| +@interface BluetoothProfileMacHelper : NSObject { |
| + @private |
| + // The profile that owns |self|. |
| + device::BluetoothProfileMac* profile_; // weak |
| + |
| + // The OS mechanism used to subscribe to and unsubscribe from RFCOMM channel |
| + // creation notifications. |
| + IOBluetoothUserNotification* rfcommNewChannelNotification_; // weak |
| +} |
| + |
| +- (id)initWithProfile:(device::BluetoothProfileMac*)profile |
| + channelID:(BluetoothRFCOMMChannelID)channelID; |
| +- (void)rfcommChannelOpened:(IOBluetoothUserNotification*)notification |
| + channel:(IOBluetoothRFCOMMChannel*)rfcommChannel; |
| + |
| +@end |
| + |
| +@implementation BluetoothProfileMacHelper |
| + |
| +- (id)initWithProfile:(device::BluetoothProfileMac*)profile |
| + channelID:(BluetoothRFCOMMChannelID)channelID { |
| + if ((self = [super init])) { |
| + profile_ = profile; |
| + |
| + SEL selector = @selector(rfcommChannelOpened:channel:); |
| + const auto kIncomingDirection = |
| + kIOBluetoothUserNotificationChannelDirectionIncoming; |
| + rfcommNewChannelNotification_ = |
| + [IOBluetoothRFCOMMChannel |
| + registerForChannelOpenNotifications:self |
| + selector:selector |
| + withChannelID:channelID |
| + direction:kIncomingDirection]; |
| + } |
| + |
| + return self; |
| +} |
| + |
| +- (void)dealloc { |
| + [rfcommNewChannelNotification_ unregister]; |
| + [super dealloc]; |
| +} |
| + |
| +- (void)rfcommChannelOpened:(IOBluetoothUserNotification*)notification |
| + channel:(IOBluetoothRFCOMMChannel*)rfcommChannel { |
| + if (notification != rfcommNewChannelNotification_) { |
| + // This case is reachable if there are pre-existing RFCOMM channels open at |
| + // the time that the helper is created. In that case, each existing channel |
| + // calls into this method with a different notification than the one this |
| + // class registered with. Ignore those; this class is only interested in |
| + // channels that have opened since it registered for notifications. |
| + return; |
| + } |
| + |
| + profile_->OnRFCOMMChannelOpened(rfcommChannel); |
| +} |
| + |
| +@end |
| namespace device { |
| +BluetoothProfile* CreateBluetoothProfileMac( |
| + const BluetoothUUID& uuid, |
| + const BluetoothProfile::Options& options) { |
| + return new BluetoothProfileMac(uuid, options); |
| +} |
| + |
| BluetoothProfileMac::BluetoothProfileMac(const BluetoothUUID& uuid, |
| - const std::string& name) |
| - : BluetoothProfile(), uuid_(uuid), name_(name) { |
| + const Options& options) |
| + : uuid_(uuid), |
| + rfcomm_channel_id_(options.channel), |
| + helper_([[BluetoothProfileMacHelper alloc] |
| + initWithProfile:this |
| + channelID:rfcomm_channel_id_]), |
| + service_record_handle_(RegisterService(uuid, options)) { |
| + // TODO(isherman): What should happen if there was an error registering the |
| + // service, i.e. if |service_record_handle_| is |kInvalidServiceRecordHandle|? |
|
rpaquay
2014/04/23 22:15:55
Ideally, we would have a way to report the error t
Ilya Sherman
2014/04/25 22:44:44
Filed a bug and referenced it here.
|
| } |
| BluetoothProfileMac::~BluetoothProfileMac() { |
| + if (service_record_handle_ != kInvalidServiceRecordHandle) |
| + IOBluetoothRemoveServiceWithRecordHandle(service_record_handle_); |
| } |
| void BluetoothProfileMac::Unregister() { |
| @@ -122,15 +282,36 @@ void BluetoothProfileMac::Connect( |
| } |
| std::string device_address = base::SysNSStringToUTF8([device addressString]); |
| - scoped_refptr<BluetoothSocketMac> socket( |
| - BluetoothSocketMac::CreateBluetoothSocket(record)); |
| - socket->Connect( |
| + BluetoothSocketMac::Connect( |
| + record, |
| base::Bind(OnConnectSuccess, |
| success_callback, |
| connection_callback_, |
| - device_address, |
| - socket), |
| + device_address), |
| error_callback); |
| } |
| +void BluetoothProfileMac::OnRFCOMMChannelOpened( |
| + IOBluetoothRFCOMMChannel* rfcomm_channel) { |
| + DCHECK_EQ([rfcomm_channel getChannelID], rfcomm_channel_id_); |
| + std::string device_address = |
| + base::SysNSStringToUTF8([[rfcomm_channel getDevice] addressString]); |
| + BluetoothSocketMac::AcceptConnection( |
| + rfcomm_channel, |
| + base::Bind(OnConnectSuccess, |
| + base::Closure(), |
| + connection_callback_, |
| + device_address), |
| + BluetoothSocket::ErrorCompletionCallback()); |
| + |
| + // TODO(isherman): Currently, both the profile and the socket remain alive |
| + // even after the app that requested them is closed. That's not great, as a |
| + // misbehaving app could saturate all of the system's RFCOMM channels, and |
| + // then they would not be freed until the user restarts Chrome. |
| + // TODO(isherman): Likewise, the socket currently remains alive even if the |
| + // underlying rfcomm_channel is closed, e.g. via the client disconnecting, or |
| + // the user closing the Bluetooth connection via the system menu. This |
| + // functions essentially as a minor memory leak. |
|
rpaquay
2014/04/23 22:15:55
These issues are serious imho. Do we have a bug tr
Ilya Sherman
2014/04/25 22:44:44
Filed bugs and referenced them here.
|
| +} |
| + |
| } // namespace device |