Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(523)

Side by Side Diff: device/bluetooth/bluetooth_socket_mac.mm

Issue 229463003: MacOS implementation of BluetoothSocket. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fix build error on OSX SDK < 10.7. Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « device/bluetooth/bluetooth_socket_mac.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "device/bluetooth/bluetooth_socket_mac.h" 5 #include "device/bluetooth/bluetooth_socket_mac.h"
6 6
7 #import <IOBluetooth/objc/IOBluetoothDevice.h> 7 #import <IOBluetooth/objc/IOBluetoothDevice.h>
8 #import <IOBluetooth/objc/IOBluetoothRFCOMMChannel.h> 8 #import <IOBluetooth/objc/IOBluetoothRFCOMMChannel.h>
9 #import <IOBluetooth/objc/IOBluetoothSDPServiceRecord.h> 9 #import <IOBluetooth/objc/IOBluetoothSDPServiceRecord.h>
10 10
11 #include <limits> 11 #include <limits>
12 #include <sstream>
12 #include <string> 13 #include <string>
13 14
14 #include "base/basictypes.h" 15 #include "base/basictypes.h"
16 #include "base/callback_helpers.h"
15 #include "base/memory/ref_counted.h" 17 #include "base/memory/ref_counted.h"
16 #include "base/strings/stringprintf.h" 18 #include "base/strings/stringprintf.h"
17 #include "base/strings/sys_string_conversions.h" 19 #include "base/strings/sys_string_conversions.h"
20 #include "base/threading/thread_restrictions.h"
18 #include "device/bluetooth/bluetooth_service_record.h" 21 #include "device/bluetooth/bluetooth_service_record.h"
19 #include "device/bluetooth/bluetooth_service_record_mac.h" 22 #include "device/bluetooth/bluetooth_service_record_mac.h"
20 #include "net/base/io_buffer.h" 23 #include "net/base/io_buffer.h"
21 24
22 // Replicate specific 10.7 SDK declarations for building with prior SDKs. 25 // Replicate specific 10.7 SDK declarations for building with prior SDKs.
23 #if !defined(MAC_OS_X_VERSION_10_7) || \ 26 #if !defined(MAC_OS_X_VERSION_10_7) || \
24 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 27 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
25 28
26 @interface IOBluetoothDevice (LionSDKDeclarations) 29 @interface IOBluetoothDevice (LionSDKDeclarations)
27 - (NSString*)addressString; 30 - (NSString*)addressString;
(...skipping 13 matching lines...) Expand all
41 44
42 @implementation BluetoothRFCOMMChannelDelegate 45 @implementation BluetoothRFCOMMChannelDelegate
43 46
44 - (id)initWithSocket:(device::BluetoothSocketMac*)socket { 47 - (id)initWithSocket:(device::BluetoothSocketMac*)socket {
45 if ((self = [super init])) 48 if ((self = [super init]))
46 socket_ = socket; 49 socket_ = socket;
47 50
48 return self; 51 return self;
49 } 52 }
50 53
54 - (void)rfcommChannelOpenComplete:(IOBluetoothRFCOMMChannel*)rfcommChannel
55 status:(IOReturn)error {
56 socket_->OnChannelOpened(rfcommChannel, error);
57 }
58
59 - (void)rfcommChannelWriteComplete:(IOBluetoothRFCOMMChannel*)rfcommChannel
60 refcon:(void*)refcon
61 status:(IOReturn)error {
62 socket_->OnChannelWriteComplete(rfcommChannel, refcon, error);
63 }
64
51 - (void)rfcommChannelData:(IOBluetoothRFCOMMChannel*)rfcommChannel 65 - (void)rfcommChannelData:(IOBluetoothRFCOMMChannel*)rfcommChannel
52 data:(void*)dataPointer 66 data:(void*)dataPointer
53 length:(size_t)dataLength { 67 length:(size_t)dataLength {
54 socket_->OnDataReceived(rfcommChannel, dataPointer, dataLength); 68 socket_->OnChannelDataReceived(rfcommChannel, dataPointer, dataLength);
69 }
70
71 - (void)rfcommChannelClosed:(IOBluetoothRFCOMMChannel*)rfcommChannel {
72 socket_->OnChannelClosed(rfcommChannel);
55 } 73 }
56 74
57 @end 75 @end
58 76
77 namespace {
78
79 const char kL2CAPNotSupported[] = "Bluetooth L2CAP protocol is not supported";
80 const char kSocketConnecting[] = "The socket is currently connecting";
81 const char kSocketAlreadyConnected[] = "The socket is already connected";
82 const char kSocketNotConnected[] = "The socket is not connected";
83 const char kReceivePending[] = "A Receive operation is pending";
84
85 template <class T>
86 void empty_queue(std::queue<T>& queue) {
87 std::queue<T> empty;
88 std::swap(queue, empty);
89 }
90
91 } // namespace
92
59 namespace device { 93 namespace device {
60 94
61 BluetoothSocketMac::BluetoothSocketMac(IOBluetoothRFCOMMChannel* rfcomm_channel) 95 BluetoothSocketMac::SendRequest::SendRequest()
62 : rfcomm_channel_(rfcomm_channel), 96 : status(kIOReturnSuccess), active_async_writes(0), error_signaled(false) {}
63 delegate_([[BluetoothRFCOMMChannelDelegate alloc] initWithSocket:this]) { 97
98 BluetoothSocketMac::SendRequest::~SendRequest() {}
99
100 BluetoothSocketMac::ReceiveCallbacks::ReceiveCallbacks() {}
101
102 BluetoothSocketMac::ReceiveCallbacks::~ReceiveCallbacks() {}
103
104 BluetoothSocketMac::ConnectCallbacks::ConnectCallbacks() {}
105
106 BluetoothSocketMac::ConnectCallbacks::~ConnectCallbacks() {}
107
108 // static
109 scoped_refptr<BluetoothSocketMac> BluetoothSocketMac::CreateBluetoothSocket(
110 const scoped_refptr<base::SequencedTaskRunner>& ui_task_runner,
111 IOBluetoothSDPServiceRecord* record) {
112 return new BluetoothSocketMac(ui_task_runner, record);
113 }
114
115 BluetoothSocketMac::BluetoothSocketMac(
116 const scoped_refptr<base::SequencedTaskRunner>& ui_task_runner,
117 IOBluetoothSDPServiceRecord* record)
118 : ui_task_runner_(ui_task_runner),
119 record_(record),
120 delegate_([[BluetoothRFCOMMChannelDelegate alloc] initWithSocket:this]),
121 rfcomm_channel_(nil) {
122 DCHECK(thread_checker_.CalledOnValidThread());
123 DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
124 [record_ retain];
125 }
126
127 BluetoothSocketMac::~BluetoothSocketMac() {
128 DCHECK(thread_checker_.CalledOnValidThread());
129 DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
130 ReleaseChannel();
131 [delegate_ release];
132 [record_ release];
133 }
134
135 void BluetoothSocketMac::ReleaseChannel() {
136 DCHECK(thread_checker_.CalledOnValidThread());
137 DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
138 if (rfcomm_channel_ != nil) {
139 [rfcomm_channel_ setDelegate:nil];
140 [rfcomm_channel_ closeChannel];
141 [rfcomm_channel_ release];
142 rfcomm_channel_ = nil;
143 }
144
145 // Closing the channel above prevents the callback delegate from being called
146 // so it is now safe to release all callback state.
147 connect_callbacks_.reset(NULL);
148 receive_callbacks_.reset(NULL);
149 empty_queue(receive_queue_);
150 empty_queue(send_queue_);
151 }
152
153 void BluetoothSocketMac::Connect(
154 const base::Closure& success_callback,
155 const ErrorCompletionCallback& error_callback) {
156 DCHECK(thread_checker_.CalledOnValidThread());
157 DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
158
159 if (connecting()) {
160 error_callback.Run(kSocketConnecting);
161 return;
162 }
163
164 if (rfcomm_channel_ != nil) {
165 error_callback.Run(kSocketAlreadyConnected);
166 return;
167 }
168
169 uint8 rfcomm_channel_id;
170 IOReturn status = [record_ getRFCOMMChannelID:&rfcomm_channel_id];
171 if (status != kIOReturnSuccess) {
172 // TODO(youngki) add support for L2CAP sockets as well.
173 error_callback.Run(kL2CAPNotSupported);
174 return;
175 }
176
177 IOBluetoothDevice* device = [record_ device];
178 IOBluetoothRFCOMMChannel* rfcomm_channel;
179 status = [device openRFCOMMChannelAsync:&rfcomm_channel
180 withChannelID:rfcomm_channel_id
181 delegate:delegate_];
182 if (status != kIOReturnSuccess) {
183 std::stringstream error;
184 error << std::string("Failed to connect bluetooth socket (")
185 << base::SysNSStringToUTF8([device addressString]) << "): (" << status
186 << std::string(")");
187 error_callback.Run(error.str());
188 return;
189 }
190
191 connect_callbacks_.reset(new ConnectCallbacks());
192 connect_callbacks_->success_callback = success_callback;
193 connect_callbacks_->error_callback = error_callback;
194 rfcomm_channel_ = rfcomm_channel;
64 [rfcomm_channel_ setDelegate:delegate_]; 195 [rfcomm_channel_ setDelegate:delegate_];
65 ResetIncomingDataBuffer(); 196 }
66 } 197
67 198 void BluetoothSocketMac::OnChannelOpened(
68 BluetoothSocketMac::~BluetoothSocketMac() { 199 IOBluetoothRFCOMMChannel* rfcomm_channel,
69 [rfcomm_channel_ setDelegate:nil]; 200 IOReturn status) {
70 [rfcomm_channel_ closeChannel]; 201 DCHECK(thread_checker_.CalledOnValidThread());
71 [rfcomm_channel_ release]; 202 DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
72 [delegate_ release]; 203 DCHECK(rfcomm_channel_ == rfcomm_channel);
73 } 204 DCHECK(connecting());
74 205
75 // static 206 scoped_ptr<ConnectCallbacks> temp = connect_callbacks_.Pass();
76 scoped_refptr<BluetoothSocket> BluetoothSocketMac::CreateBluetoothSocket( 207 if (status != kIOReturnSuccess) {
77 const BluetoothServiceRecord& service_record) { 208 ReleaseChannel();
78 BluetoothSocketMac* bluetooth_socket = NULL; 209 std::stringstream error;
79 if (service_record.SupportsRfcomm()) { 210 error << "Failed to connect bluetooth socket ("
80 const BluetoothServiceRecordMac* service_record_mac = 211 << base::SysNSStringToUTF8([[record_ device] addressString]) << "): ("
81 static_cast<const BluetoothServiceRecordMac*>(&service_record); 212 << status << ")";
82 IOBluetoothDevice* device = service_record_mac->GetIOBluetoothDevice(); 213 temp->error_callback.Run(error.str());
83 IOBluetoothRFCOMMChannel* rfcomm_channel; 214 return;
84 IOReturn status = 215 }
85 [device openRFCOMMChannelAsync:&rfcomm_channel 216
86 withChannelID:service_record.rfcomm_channel() 217 temp->success_callback.Run();
87 delegate:nil]; 218 }
88 if (status == kIOReturnSuccess) { 219
89 bluetooth_socket = new BluetoothSocketMac(rfcomm_channel); 220 void BluetoothSocketMac::Close() {
90 } else { 221 DCHECK(thread_checker_.CalledOnValidThread());
91 LOG(ERROR) << "Failed to connect bluetooth socket (" 222 DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
92 << service_record.address() << "): (" << status << ")"; 223
93 } 224 ReleaseChannel();
94 } 225 }
95 // TODO(youngki): add support for L2CAP sockets as well.
96
97 return scoped_refptr<BluetoothSocketMac>(bluetooth_socket);
98 }
99
100 // static
101 scoped_refptr<BluetoothSocket> BluetoothSocketMac::CreateBluetoothSocket(
102 IOBluetoothSDPServiceRecord* record) {
103 BluetoothSocketMac* bluetooth_socket = NULL;
104 uint8 rfcomm_channel_id;
105 if ([record getRFCOMMChannelID:&rfcomm_channel_id] == kIOReturnSuccess) {
106 IOBluetoothDevice* device = [record device];
107 IOBluetoothRFCOMMChannel* rfcomm_channel;
108 IOReturn status =
109 [device openRFCOMMChannelAsync:&rfcomm_channel
110 withChannelID:rfcomm_channel_id
111 delegate:nil];
112 if (status == kIOReturnSuccess) {
113 bluetooth_socket = new BluetoothSocketMac(rfcomm_channel);
114 } else {
115 LOG(ERROR) << "Failed to connect bluetooth socket ("
116 << base::SysNSStringToUTF8([device addressString]) << "): (" << status
117 << ")";
118 }
119 }
120
121 // TODO(youngki): Add support for L2CAP sockets as well.
122
123 return scoped_refptr<BluetoothSocketMac>(bluetooth_socket);
124 }
125
126 void BluetoothSocketMac::Close() { NOTIMPLEMENTED(); }
127 226
128 void BluetoothSocketMac::Disconnect(const base::Closure& callback) { 227 void BluetoothSocketMac::Disconnect(const base::Closure& callback) {
129 NOTIMPLEMENTED(); 228 DCHECK(thread_checker_.CalledOnValidThread());
229 DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
230
231 ReleaseChannel();
232 callback.Run();
130 } 233 }
131 234
132 void BluetoothSocketMac::Receive( 235 void BluetoothSocketMac::Receive(
133 int count, 236 int count,
134 const ReceiveCompletionCallback& success_callback, 237 const ReceiveCompletionCallback& success_callback,
135 const ReceiveErrorCompletionCallback& error_callback) { 238 const ReceiveErrorCompletionCallback& error_callback) {
136 NOTIMPLEMENTED(); 239 DCHECK(thread_checker_.CalledOnValidThread());
240 DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
241
242 if (connecting()) {
243 error_callback.Run(BluetoothSocket::kSystemError, kSocketConnecting);
244 return;
245 }
246
247 if (rfcomm_channel_ == nil) {
248 error_callback.Run(BluetoothSocket::kDisconnected, kSocketNotConnected);
249 return;
250 }
251
252 // Only one pending read at a time
253 if (receive_callbacks_) {
254 error_callback.Run(BluetoothSocket::kIOPending, kReceivePending);
255 return;
256 }
257
258 // If there is at least one packet, consume it and succeed right away.
259 if (!receive_queue_.empty()) {
260 scoped_refptr<net::IOBufferWithSize> buffer = receive_queue_.front();
261 receive_queue_.pop();
262 success_callback.Run(buffer->size(), buffer);
263 return;
264 }
265
266 // Set the receive callback to use when data is received.
267 receive_callbacks_.reset(new ReceiveCallbacks());
268 receive_callbacks_->success_callback = success_callback;
269 receive_callbacks_->error_callback = error_callback;
270 }
271
272 void BluetoothSocketMac::OnChannelDataReceived(
273 IOBluetoothRFCOMMChannel* rfcomm_channel,
274 void* data,
275 size_t length) {
276 DCHECK(thread_checker_.CalledOnValidThread());
277 DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
278 DCHECK(rfcomm_channel_ == rfcomm_channel);
279 DCHECK(!connecting());
280 CHECK_LT(length, static_cast<size_t>(std::numeric_limits<int>::max()));
281
282 int data_size = static_cast<int>(length);
283 scoped_refptr<net::IOBufferWithSize> buffer(
284 new net::IOBufferWithSize(data_size));
285 memcpy(buffer->data(), data, buffer->size());
286
287 // If there is a pending read callback, call it now.
288 if (receive_callbacks_) {
289 scoped_ptr<ReceiveCallbacks> temp = receive_callbacks_.Pass();
290 temp->success_callback.Run(buffer->size(), buffer);
291 return;
292 }
293
294 // Otherwise, enqueue the buffer for later use
295 receive_queue_.push(buffer);
137 } 296 }
138 297
139 void BluetoothSocketMac::Send(scoped_refptr<net::IOBuffer> buffer, 298 void BluetoothSocketMac::Send(scoped_refptr<net::IOBuffer> buffer,
140 int buffer_size, 299 int buffer_size,
141 const SendCompletionCallback& success_callback, 300 const SendCompletionCallback& success_callback,
142 const ErrorCompletionCallback& error_callback) { 301 const ErrorCompletionCallback& error_callback) {
143 NOTIMPLEMENTED(); 302 DCHECK(thread_checker_.CalledOnValidThread());
144 } 303 DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
145 304
146 #if 0 305 if (connecting()) {
147 bool BluetoothSocketMac::Receive(net::GrowableIOBuffer* buffer) { 306 error_callback.Run(kSocketConnecting);
148 CHECK(buffer->offset() == 0); 307 return;
149 int length = incoming_data_buffer_->offset(); 308 }
150 if (length > 0) { 309
151 buffer->SetCapacity(length); 310 if (rfcomm_channel_ == nil) {
152 memcpy(buffer->data(), incoming_data_buffer_->StartOfBuffer(), length); 311 error_callback.Run(kSocketNotConnected);
153 buffer->set_offset(length); 312 return;
154 313 }
155 ResetIncomingDataBuffer(); 314
156 } 315 // Create and enqueue request in preparation of async writes.
157 return true; 316 linked_ptr<SendRequest> request(new SendRequest());
158 } 317 request->buffer_size = buffer_size;
159 318 request->success_callback = success_callback;
160 bool BluetoothSocketMac::Send(net::DrainableIOBuffer* buffer) { 319 request->error_callback = error_callback;
161 int bytes_written = buffer->BytesRemaining(); 320 send_queue_.push(request);
162 IOReturn status = [rfcomm_channel_ writeAsync:buffer->data() 321
163 length:bytes_written 322 // |writeAsync| accepts buffers of max. mtu bytes per call, so we need to emit
164 refcon:nil]; 323 // multiple write operations if buffer_size > mtu.
324 BluetoothRFCOMMMTU mtu = [rfcomm_channel_ getMTU];
325 scoped_refptr<net::DrainableIOBuffer> send_buffer(
326 new net::DrainableIOBuffer(buffer, buffer_size));
327 while (send_buffer->BytesRemaining() > 0) {
328 int byte_count = send_buffer->BytesRemaining();
329 if (byte_count > mtu)
330 byte_count = mtu;
331 IOReturn status = [rfcomm_channel_ writeAsync:send_buffer->data()
332 length:byte_count
333 refcon:request.get()];
334 if (status != kIOReturnSuccess) {
335 std::stringstream error;
336 error << "Failed to connect bluetooth socket ("
337 << base::SysNSStringToUTF8([[record_ device] addressString])
338 << "): (" << status << ")";
339 // Remember the first error only
340 if (request->status == kIOReturnSuccess)
341 request->status = status;
342 request->error_signaled = true;
343 request->error_callback.Run(error.str());
344 // We may have failed to issue any write operation. In that case, there
345 // will be no corresponding completion callback for this particular
346 // request, so we must forget about it now.
347 if (request->active_async_writes == 0) {
348 send_queue_.pop();
349 }
350 return;
351 }
352
353 request->active_async_writes++;
354 send_buffer->DidConsume(byte_count);
355 }
356 }
357
358 void BluetoothSocketMac::OnChannelWriteComplete(
359 IOBluetoothRFCOMMChannel* rfcomm_channel,
360 void* refcon,
361 IOReturn status) {
362 DCHECK(thread_checker_.CalledOnValidThread());
363 DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
364
365 // Note: We use "CHECK" below to ensure we never run into unforeseen
366 // occurrences of asynchronous callbacks, which could lead to data
367 // corruption.
368 CHECK(rfcomm_channel_ == rfcomm_channel);
369 CHECK(static_cast<SendRequest*>(refcon) == send_queue_.front().get());
370
371 // Keep a local linked_ptr to avoid releasing the request too early if we end
372 // up removing it from the queue.
373 linked_ptr<SendRequest> request = send_queue_.front();
374
375 // Remember the first error only
165 if (status != kIOReturnSuccess) { 376 if (status != kIOReturnSuccess) {
166 error_message_ = base::StringPrintf( 377 if (request->status == kIOReturnSuccess)
167 "Failed to send data. IOReturn code: %u", status); 378 request->status = status;
168 return false; 379 }
169 } 380
170 381 // Figure out if we are done with this async request
171 buffer->DidConsume(bytes_written); 382 request->active_async_writes--;
172 return true; 383 if (request->active_async_writes > 0)
173 } 384 return;
174 385
175 std::string BluetoothSocketMac::GetLastErrorMessage() const { 386 // If this was the last active async write for this request, remove it from
176 return error_message_; 387 // the queue and call the appropriate callback associated to the request.
177 } 388 send_queue_.pop();
178 #endif 389 if (request->status != kIOReturnSuccess) {
179 void BluetoothSocketMac::OnDataReceived( 390 if (!request->error_signaled) {
180 IOBluetoothRFCOMMChannel* rfcomm_channel, void* data, size_t length) { 391 std::stringstream error;
392 error << "Failed to connect bluetooth socket ("
393 << base::SysNSStringToUTF8([[record_ device] addressString])
394 << "): (" << status << ")";
395 request->error_signaled = true;
396 request->error_callback.Run(error.str());
397 }
398 } else {
399 request->success_callback.Run(request->buffer_size);
400 }
401 }
402
403 void BluetoothSocketMac::OnChannelClosed(
404 IOBluetoothRFCOMMChannel* rfcomm_channel) {
405 DCHECK(thread_checker_.CalledOnValidThread());
406 DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
181 DCHECK(rfcomm_channel_ == rfcomm_channel); 407 DCHECK(rfcomm_channel_ == rfcomm_channel);
182 CHECK_LT(length, static_cast<size_t>(std::numeric_limits<int>::max())); 408
183 int data_size = static_cast<int>(length); 409 if (receive_callbacks_) {
184 if (incoming_data_buffer_->RemainingCapacity() < data_size) { 410 scoped_ptr<ReceiveCallbacks> temp = receive_callbacks_.Pass();
185 int additional_capacity = 411 temp->error_callback.Run(BluetoothSocket::kDisconnected,
186 std::max(data_size, incoming_data_buffer_->capacity()); 412 kSocketNotConnected);
187 CHECK_LT( 413 }
188 additional_capacity, 414
189 std::numeric_limits<int>::max() - incoming_data_buffer_->capacity()); 415 ReleaseChannel();
190 incoming_data_buffer_->SetCapacity(
191 incoming_data_buffer_->capacity() + additional_capacity);
192 }
193 memcpy(incoming_data_buffer_->data(), data, data_size);
194 incoming_data_buffer_->set_offset(
195 incoming_data_buffer_->offset() + data_size);
196 }
197
198 void BluetoothSocketMac::ResetIncomingDataBuffer() {
199 incoming_data_buffer_ = new net::GrowableIOBuffer();
200 incoming_data_buffer_->SetCapacity(1024);
201 } 416 }
202 417
203 } // namespace device 418 } // namespace device
OLDNEW
« no previous file with comments | « device/bluetooth/bluetooth_socket_mac.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698