1 /* 2 * Copyright 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.googlecode.android_scripting.facade.bluetooth; 18 19 import android.app.Service; 20 import android.bluetooth.BluetoothAdapter; 21 import android.bluetooth.BluetoothDevice; 22 import android.bluetooth.BluetoothServerSocket; 23 import android.bluetooth.BluetoothSocket; 24 25 import com.googlecode.android_scripting.Log; 26 import com.googlecode.android_scripting.facade.EventFacade; 27 import com.googlecode.android_scripting.facade.FacadeManager; 28 import com.googlecode.android_scripting.jsonrpc.RpcReceiver; 29 import com.googlecode.android_scripting.rpc.Rpc; 30 import com.googlecode.android_scripting.rpc.RpcDefault; 31 import com.googlecode.android_scripting.rpc.RpcOptional; 32 import com.googlecode.android_scripting.rpc.RpcParameter; 33 34 import org.apache.commons.codec.binary.Base64Codec; 35 36 import java.io.ByteArrayOutputStream; 37 import java.io.IOException; 38 import java.lang.reflect.Method; 39 import java.util.HashMap; 40 import java.util.Map; 41 import java.util.UUID; 42 43 /** 44 * Bluetooth functions. 45 * 46 */ 47 48 public class BluetoothSocketConnFacade extends RpcReceiver { 49 private final Service mService; 50 private final BluetoothAdapter mBluetoothAdapter; 51 private Map<String, BluetoothConnection> mConnections = 52 new HashMap<String, BluetoothConnection>(); 53 private final EventFacade mEventFacade; 54 private ConnectThread mConnectThread; 55 private AcceptThread mAcceptThread; 56 private byte mTxPktIndex = 0; 57 58 private static final String DEFAULT_PSM = "161"; //=0x00A1 59 60 // UUID for SL4A. 61 protected static final String DEFAULT_UUID = "457807c0-4897-11df-9879-0800200c9a66"; 62 protected static final String SDP_NAME = "SL4A"; 63 64 protected static final String DEFAULT_LE_DATA_LENGTH = "23"; 65 BluetoothSocketConnFacade(FacadeManager manager)66 public BluetoothSocketConnFacade(FacadeManager manager) { 67 super(manager); 68 mEventFacade = manager.getReceiver(EventFacade.class); 69 mService = manager.getService(); 70 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 71 } 72 getConnection(String connID)73 private BluetoothConnection getConnection(String connID) throws IOException { 74 if (connID == null) { 75 throw new IOException("Connection ID is null"); 76 } 77 Log.d("BluetoothConnection:getConnection: connID=" + connID); 78 BluetoothConnection conn = null; 79 if (connID.trim().length() > 0) { 80 conn = mConnections.get(connID); 81 } else if (mConnections.size() == 1) { 82 conn = (BluetoothConnection) mConnections.values().toArray()[0]; 83 } else { 84 Log.e("More than one available connections. Num=" + mConnections.size()); 85 throw new IOException("More than 1 available connections. Num=" + mConnections.size()); 86 } 87 if (conn == null) { 88 throw new IOException("Bluetooth connection not established. connID=" + connID); 89 } 90 return conn; 91 } 92 addConnection(BluetoothConnection conn)93 private String addConnection(BluetoothConnection conn) { 94 String uuid = UUID.randomUUID().toString(); 95 mConnections.put(uuid, conn); 96 conn.setUUID(uuid); 97 return uuid; 98 } 99 100 /** 101 * Create L2CAP socket to Bluetooth device 102 * 103 * @param address the bluetooth address to open the socket on 104 * @param channel the channel to open the socket on 105 * @throws Exception 106 */ 107 @Rpc(description = "Create L2CAP socket to Bluetooth deice") bluetoothSocketConnCreateL2capSocket(@pcParametername = "address") String address, @RpcParameter(name = "channel") Integer channel)108 public void bluetoothSocketConnCreateL2capSocket(@RpcParameter(name = "address") String address, 109 @RpcParameter(name = "channel") Integer channel) throws Exception { 110 BluetoothDevice mDevice; 111 mDevice = mBluetoothAdapter.getRemoteDevice(address); 112 Class bluetoothDeviceClass = Class.forName("android.bluetooth.BluetoothDevice"); 113 Method createL2capSocketMethod = 114 bluetoothDeviceClass.getMethod("createL2capSocket", int.class); 115 createL2capSocketMethod.invoke(mDevice, channel); 116 } 117 118 /** 119 * Begin Connect Thread using UUID 120 * 121 * @param address the mac address of the device to connect to 122 * @param uuid the UUID that is used by the server device 123 * @throws Exception 124 */ 125 @Rpc(description = "Begins a thread initiate an L2CAP socket connection over Bluetooth. ") bluetoothSocketConnBeginConnectThreadUuid( @pcParametername = "address", description = "The mac address of the device to connect to.") String address, @RpcParameter(name = "uuid", description = "The UUID passed here must match the UUID used by the server device.") @RpcDefault(DEFAULT_UUID) String uuid)126 public void bluetoothSocketConnBeginConnectThreadUuid( 127 @RpcParameter(name = "address", 128 description = "The mac address of the device to connect to.") String address, 129 @RpcParameter(name = "uuid", 130 description = "The UUID passed here must match the UUID used by the server device.") 131 @RpcDefault(DEFAULT_UUID) String uuid) 132 throws IOException { 133 BluetoothDevice mDevice; 134 mDevice = mBluetoothAdapter.getRemoteDevice(address); 135 ConnectThread connectThread = new ConnectThread(mDevice, uuid); 136 connectThread.start(); 137 mConnectThread = connectThread; 138 } 139 140 /** 141 * Begin Connect Thread using PSM value 142 * 143 * @param address the mac address of the device to connect to 144 * @param isBle the transport is LE 145 * @param psmValue the assigned PSM value to use for this socket connection 146 * @throws Exception 147 */ 148 @Rpc(description = "Begins a thread initiate an L2CAP CoC connection over Bluetooth. ") bluetoothSocketConnBeginConnectThreadPsm( @pcParametername = "address", description = "The mac address of the device to connect to.") String address, @RpcParameter(name = "isBle", description = "Is transport BLE?") @RpcDefault("false") Boolean isBle, @RpcParameter(name = "psmValue") @RpcDefault(DEFAULT_PSM) Integer psmValue, @RpcParameter(name = "securedConn") @RpcDefault("false") Boolean securedConn)149 public void bluetoothSocketConnBeginConnectThreadPsm( 150 @RpcParameter(name = "address", 151 description = "The mac address of the device to connect to.") String address, 152 @RpcParameter(name = "isBle", description = "Is transport BLE?") @RpcDefault("false") 153 Boolean isBle, 154 @RpcParameter(name = "psmValue") @RpcDefault(DEFAULT_PSM) Integer psmValue, 155 @RpcParameter(name = "securedConn") @RpcDefault("false") Boolean securedConn) 156 throws IOException { 157 BluetoothDevice mDevice; 158 mDevice = mBluetoothAdapter.getRemoteDevice(address); 159 Log.d("bluetoothSocketConnBeginConnectThreadPsm: Coc connecting to " + address + ", isBle=" 160 + isBle + ", psmValue=" + psmValue + ", securedConn=" + securedConn); 161 ConnectThread connectThread = new ConnectThread(mDevice, psmValue, isBle, securedConn); 162 connectThread.start(); 163 mConnectThread = connectThread; 164 } 165 166 /** 167 * Get last connection ID 168 * 169 * @return String the last connection ID 170 * @throws Exception 171 */ 172 @Rpc(description = "Returns the connection ID of the last connection.") bluetoothGetLastConnId()173 public String bluetoothGetLastConnId() 174 throws IOException { 175 if (mAcceptThread != null) { 176 String connUuid = mAcceptThread.getConnUuid(); 177 Log.d("bluetoothGetLastConnId from Accept Thread: connUuid=" + connUuid); 178 return connUuid; 179 } 180 if (mConnectThread != null) { 181 String connUuid = mConnectThread.getConnUuid(); 182 Log.d("bluetoothGetLastConnId from Connect Thread: connUuid=" + connUuid); 183 return connUuid; 184 } 185 Log.e("bluetoothGetLastConnId: No active threads"); 186 return null; 187 } 188 189 /** 190 * Kill the connect thread 191 */ 192 @Rpc(description = "Kill thread") bluetoothSocketConnKillConnThread()193 public void bluetoothSocketConnKillConnThread() { 194 try { 195 mConnectThread.cancel(); 196 mConnectThread.join(5000); 197 } catch (InterruptedException e) { 198 Log.e("Interrupted Exception: " + e.toString()); 199 } 200 } 201 202 /** 203 * Closes an active Client socket 204 * 205 * @throws Exception 206 */ 207 @Rpc(description = "Close an active Client socket") bluetoothSocketConnEndConnectThread()208 public void bluetoothSocketConnEndConnectThread() throws IOException { 209 mConnectThread.cancel(); 210 } 211 212 /** 213 * Closes an active Server socket 214 * 215 * @throws Exception 216 */ 217 @Rpc(description = "Close an active Server socket") bluetoothSocketConnEndAcceptThread()218 public void bluetoothSocketConnEndAcceptThread() throws IOException { 219 mAcceptThread.cancel(); 220 } 221 222 /** 223 * Returns active Bluetooth mConnections 224 * 225 * @return map of active connections and its remote addresses 226 */ 227 @Rpc(description = "Returns active Bluetooth mConnections.") bluetoothSocketConnActiveConnections()228 public Map<String, String> bluetoothSocketConnActiveConnections() { 229 Map<String, String> out = new HashMap<String, String>(); 230 for (Map.Entry<String, BluetoothConnection> entry : mConnections.entrySet()) { 231 if (entry.getValue().isConnected()) { 232 out.put(entry.getKey(), entry.getValue().getRemoteBluetoothAddress()); 233 } 234 } 235 return out; 236 } 237 238 /** 239 * Returns the name of the connected device 240 * 241 * @return string name of connected device 242 * @throws Exception 243 */ 244 @Rpc(description = "Returns the name of the connected device.") bluetoothSocketConnGetConnectedDeviceName( @pcParametername = "connID", description = "Connection id") @pcOptional @pcDefault"") String connID)245 public String bluetoothSocketConnGetConnectedDeviceName( 246 @RpcParameter(name = "connID", description = "Connection id") @RpcOptional 247 @RpcDefault("") String connID) 248 throws IOException { 249 BluetoothConnection conn = getConnection(connID); 250 return conn.getConnectedDeviceName(); 251 } 252 253 /** 254 * Begins a thread to accept an L2CAP connection over Bluetooth with UUID 255 * 256 * @param uuid the UUID to identify this L2CAP connection 257 * @param timeout the time to wait for new connection 258 * @throws Exception 259 */ 260 @Rpc(description = "Begins a thread to accept an L2CAP connection over Bluetooth. ") bluetoothSocketConnBeginAcceptThreadUuid( @pcParametername = "uuid") @pcDefaultDEFAULT_UUID) String uuid, @RpcParameter(name = "timeout", description = "How long to wait for a new connection, 0 is wait for ever") @RpcDefault("0") Integer timeout)261 public void bluetoothSocketConnBeginAcceptThreadUuid( 262 @RpcParameter(name = "uuid") @RpcDefault(DEFAULT_UUID) String uuid, 263 @RpcParameter(name = "timeout", 264 description = "How long to wait for a new connection, 0 is wait for ever") 265 @RpcDefault("0") Integer timeout) 266 throws IOException { 267 Log.d("bluetoothSocketConnBeginAcceptThreadUuid: uuid=" + uuid); 268 AcceptThread acceptThread = new AcceptThread(uuid, timeout.intValue()); 269 acceptThread.start(); 270 mAcceptThread = acceptThread; 271 } 272 273 /** 274 * Begins a thread to accept an L2CAP connection over Bluetooth with PSM value 275 * 276 * @param psmValue the PSM value to identify this L2CAP connection 277 * @param timeout the time to wait for new connection 278 * @param isBle whether this connection uses LE transport 279 * @throws Exception 280 */ 281 @Rpc(description = "Begins a thread to accept an Coc connection over Bluetooth. ") bluetoothSocketConnBeginAcceptThreadPsm( @pcParametername = "timeout", description = "How long to wait for a new connection, 0 is wait for ever") @pcDefault"0") Integer timeout, @RpcParameter(name = "isBle", description = "Is transport BLE?") @RpcDefault("false") Boolean isBle, @RpcParameter(name = "securedConn", description = "Using secured connection?") @RpcDefault("false") Boolean securedConn, @RpcParameter(name = "psmValue") @RpcDefault(DEFAULT_PSM) Integer psmValue)282 public void bluetoothSocketConnBeginAcceptThreadPsm( 283 @RpcParameter(name = "timeout", 284 description = "How long to wait for a new connection, 0 is wait for ever") 285 @RpcDefault("0") Integer timeout, 286 @RpcParameter(name = "isBle", 287 description = "Is transport BLE?") 288 @RpcDefault("false") Boolean isBle, 289 @RpcParameter(name = "securedConn", 290 description = "Using secured connection?") 291 @RpcDefault("false") Boolean securedConn, 292 @RpcParameter(name = "psmValue") @RpcDefault(DEFAULT_PSM) Integer psmValue) 293 throws IOException { 294 Log.d("bluetoothSocketConnBeginAcceptThreadPsm: PSM value=" + psmValue); 295 AcceptThread acceptThread = new AcceptThread(psmValue.intValue(), timeout.intValue(), 296 isBle, securedConn); 297 acceptThread.start(); 298 mAcceptThread = acceptThread; 299 } 300 301 /** 302 * Get the current BluetoothServerSocket PSM value 303 * @return Integer the assigned PSM value 304 * @throws Exception 305 */ 306 @Rpc(description = "Returns the PSM value") bluetoothSocketConnGetPsm()307 public Integer bluetoothSocketConnGetPsm() throws IOException { 308 Integer psm = new Integer(mAcceptThread.getPsm()); 309 Log.d("bluetoothSocketConnGetPsm: PSM value=" + psm); 310 return psm; 311 } 312 313 /** 314 * Set the current BluetoothSocket LE Data Length value to the maximum supported by this BT 315 * controller. This command suggests to the BT controller to set its maximum transmission packet 316 * size. 317 * @throws Exception 318 */ 319 @Rpc(description = "Request Maximum Tx Data Length") bluetoothSocketRequestMaximumTxDataLength()320 public void bluetoothSocketRequestMaximumTxDataLength() 321 throws IOException { 322 Log.d("bluetoothSocketRequestMaximumTxDataLength"); 323 324 if (mConnectThread == null) { 325 String connUuid = mConnectThread.getConnUuid(); 326 throw new IOException("bluetoothSocketRequestMaximumTxDataLength: no active connect" 327 + " thread"); 328 } 329 330 BluetoothSocket socket = mConnectThread.getSocket(); 331 if (socket == null) { 332 throw new IOException("bluetoothSocketRequestMaximumTxDataLength: no active connect" 333 + " socket"); 334 } 335 socket.requestMaximumTxDataLength(); 336 } 337 338 /** 339 * Sends ASCII characters over the currently open Bluetooth connection 340 * 341 * @param ascii the string to write 342 * @param connID the connection ID 343 * @throws Exception 344 */ 345 @Rpc(description = "Sends ASCII characters over the currently open Bluetooth connection.") bluetoothSocketConnWrite(@pcParametername = "ascii") String ascii, @RpcParameter(name = "connID", description = "Connection id") @RpcDefault("") String connID)346 public void bluetoothSocketConnWrite(@RpcParameter(name = "ascii") String ascii, 347 @RpcParameter(name = "connID", description = "Connection id") 348 @RpcDefault("") String connID) 349 throws IOException { 350 BluetoothConnection conn = getConnection(connID); 351 try { 352 conn.write(ascii); 353 } catch (IOException e) { 354 mConnections.remove(conn.getUUID()); 355 throw e; 356 } 357 } 358 359 /** 360 * Read up to bufferSize ASCII characters 361 * 362 * @param bufferSize the size of buffer to read 363 * @param connID the connection ID 364 * @return the string buffer containing the read ASCII characters 365 * @throws Exception 366 */ 367 @Rpc(description = "Read up to bufferSize ASCII characters.") bluetoothSocketConnRead( @pcParametername = "bufferSize") @pcDefault"4096") Integer bufferSize, @RpcParameter(name = "connID", description = "Connection id") @RpcOptional @RpcDefault("") String connID)368 public String bluetoothSocketConnRead( 369 @RpcParameter(name = "bufferSize") @RpcDefault("4096") Integer bufferSize, 370 @RpcParameter(name = "connID", description = "Connection id") @RpcOptional 371 @RpcDefault("") String connID) 372 throws IOException { 373 BluetoothConnection conn = getConnection(connID); 374 try { 375 return conn.read(bufferSize); 376 } catch (IOException e) { 377 mConnections.remove(conn.getUUID()); 378 throw e; 379 } 380 } 381 382 /** 383 * Send bytes over the currently open Bluetooth connection 384 * 385 * @param base64 the based64-encoded string to write 386 * @param connID the connection ID 387 * @throws Exception 388 */ 389 @Rpc(description = "Send bytes over the currently open Bluetooth connection.") bluetoothSocketConnWriteBinary( @pcParametername = "base64", description = "A base64 encoded String of the bytes to be sent.") String base64, @RpcParameter(name = "connID", description = "Connection id") @RpcDefault("") @RpcOptional String connID)390 public void bluetoothSocketConnWriteBinary( 391 @RpcParameter(name = "base64", 392 description = "A base64 encoded String of the bytes to be sent.") String base64, 393 @RpcParameter(name = "connID", 394 description = "Connection id") @RpcDefault("") @RpcOptional String connID) 395 throws IOException { 396 BluetoothConnection conn = getConnection(connID); 397 try { 398 conn.write(Base64Codec.decodeBase64(base64)); 399 } catch (IOException e) { 400 mConnections.remove(conn.getUUID()); 401 throw e; 402 } 403 } 404 405 /** 406 * Read up to bufferSize bytes and return a chunked, base64 encoded string 407 * 408 * @param bufferSize the size of buffer to read 409 * @param connID the connection ID 410 * @return the string buffer containing the read base64-encoded characters 411 * @throws Exception 412 */ 413 @Rpc(description = "Read up to bufferSize bytes and return a chunked, base64 encoded string.") bluetoothSocketConnReadBinary( @pcParametername = "bufferSize") @pcDefault"4096") Integer bufferSize, @RpcParameter(name = "connID", description = "Connection id") @RpcDefault("") @RpcOptional String connID)414 public String bluetoothSocketConnReadBinary( 415 @RpcParameter(name = "bufferSize") @RpcDefault("4096") Integer bufferSize, 416 @RpcParameter(name = "connID", description = "Connection id") @RpcDefault("") 417 @RpcOptional String connID) 418 throws IOException { 419 420 BluetoothConnection conn = getConnection(connID); 421 try { 422 return Base64Codec.encodeBase64String(conn.readBinary(bufferSize)); 423 } catch (IOException e) { 424 mConnections.remove(conn.getUUID()); 425 throw e; 426 } 427 } 428 429 /** 430 * Returns true if the next read is guaranteed not to block 431 * 432 * @param connID the connection ID 433 * @return true if the the next read is guaranteed not to block 434 * @throws Exception 435 */ 436 @Rpc(description = "Returns True if the next read is guaranteed not to block.") bluetoothSocketConnReadReady( @pcParametername = "connID", description = "Connection id") @pcDefault"") @pcOptional String connID)437 public Boolean bluetoothSocketConnReadReady( 438 @RpcParameter(name = "connID", description = "Connection id") @RpcDefault("") 439 @RpcOptional String connID) 440 throws IOException { 441 BluetoothConnection conn = getConnection(connID); 442 try { 443 return conn.readReady(); 444 } catch (IOException e) { 445 mConnections.remove(conn.getUUID()); 446 throw e; 447 } 448 } 449 450 /** 451 * Read the next line 452 * 453 * @param connID the connection ID 454 * @return the string buffer containing the read line 455 * @throws Exception 456 */ 457 @Rpc(description = "Read the next line.") bluetoothSocketConnReadLine( @pcParametername = "connID", description = "Connection id") @pcOptional @pcDefault"") String connID)458 public String bluetoothSocketConnReadLine( 459 @RpcParameter(name = "connID", description = "Connection id") @RpcOptional 460 @RpcDefault("") String connID) 461 throws IOException { 462 BluetoothConnection conn = getConnection(connID); 463 try { 464 return conn.readLine(); 465 } catch (IOException e) { 466 mConnections.remove(conn.getUUID()); 467 throw e; 468 } 469 } 470 getNextOutputChar(byte in)471 private static byte getNextOutputChar(byte in) { 472 in++; 473 if (in >= 'z') { 474 in = 'a'; 475 } 476 return in; 477 } 478 getNextOutputChar(int in)479 private static int getNextOutputChar(int in) { 480 in++; 481 if (in >= 'z') { 482 in = 'a'; 483 } 484 return in; 485 } 486 487 /** 488 * Send a data buffer with auto-generated data 489 * 490 * @param numBuffers the number of buffers to send 491 * @param bufferSize the buffer size in bytes 492 * @param connID the connection ID 493 * @throws Exception 494 */ 495 @Rpc(description = "Send a large buffer of bytes for throughput test") bluetoothConnectionThroughputSend( @pcParametername = "numBuffers", description = "number of buffers") Integer numBuffers, @RpcParameter(name = "bufferSize", description = "buffer size") Integer bufferSize, @RpcParameter(name = "connID", description = "Connection id") @RpcDefault("") @RpcOptional String connID)496 public void bluetoothConnectionThroughputSend( 497 @RpcParameter(name = "numBuffers", description = "number of buffers") 498 Integer numBuffers, 499 @RpcParameter(name = "bufferSize", description = "buffer size") Integer bufferSize, 500 @RpcParameter(name = "connID", description = "Connection id") 501 @RpcDefault("") @RpcOptional String connID) 502 throws IOException { 503 504 Log.d("bluetoothConnectionThroughputSend: numBuffers=" + numBuffers + ", bufferSize=" 505 + bufferSize + ", connID=" + connID + ", mTxPktIndex=" + mTxPktIndex); 506 507 // Generate a buffer of given size 508 byte[] outBuf = new byte[bufferSize]; 509 byte outChar = 'a'; 510 // The first byte is the buffer index 511 int i = 0; 512 outBuf[i++] = mTxPktIndex; 513 for (; i < bufferSize; i++) { 514 outBuf[i] = outChar; 515 outChar = getNextOutputChar(outChar); 516 } 517 518 BluetoothConnection conn = getConnection(connID); 519 try { 520 for (i = 0; i < numBuffers; i++) { 521 Log.d("bluetoothConnectionThroughputSend: sending " + i + " buffer."); 522 outBuf[0] = mTxPktIndex++; 523 conn.write(outBuf); 524 } 525 } catch (IOException e) { 526 mConnections.remove(conn.getUUID()); 527 throw e; 528 } 529 } 530 531 /** 532 * Read a number of data buffers and make sure the data is correct 533 * 534 * @param numBuffers the number of buffers to send 535 * @param bufferSize the buffer size in bytes 536 * @param connID the connection ID 537 * @return the data rate read in terms of bytes per second 538 * @throws Exception 539 */ 540 @Rpc(description = "Returns the throughput in bytes-per-sec, or Returns 0 if unsuccessful") bluetoothConnectionThroughputRead( @pcParametername = "numBuffers", description = "number of buffers") Integer numBuffers, @RpcParameter(name = "bufferSize", description = "buffer size") Integer bufferSize, @RpcParameter(name = "connID", description = "Connection id") @RpcDefault("") @RpcOptional String connID)541 public Integer bluetoothConnectionThroughputRead( 542 @RpcParameter(name = "numBuffers", description = "number of buffers") 543 Integer numBuffers, 544 @RpcParameter(name = "bufferSize", description = "buffer size") Integer bufferSize, 545 @RpcParameter(name = "connID", description = "Connection id") 546 @RpcDefault("") @RpcOptional String connID) 547 throws IOException { 548 549 Log.d("bluetoothConnectionThroughputRead: numBuffers=" + numBuffers + ", bufferSize=" 550 + bufferSize); 551 552 BluetoothConnection conn = getConnection(connID); 553 554 long startTesttime = System.currentTimeMillis(); 555 556 byte bufIndex = (byte) 0x00FF; 557 558 try { 559 560 ByteArrayOutputStream tmp_array = new ByteArrayOutputStream(); 561 for (int i = 0; i < numBuffers; i++) { 562 // Read one buffer 563 while (tmp_array.size() < bufferSize) { 564 byte[] tmp_read = conn.readBinary(bufferSize); 565 if (tmp_read.length == 0) { 566 break; 567 } 568 tmp_array.write(tmp_read); 569 } 570 byte[] readBuf = tmp_array.toByteArray(); 571 tmp_array.reset(); 572 573 // Make sure the contents are valid 574 int nextInChar = 'a'; 575 int j = 0; 576 // The first byte is the buffer index 577 if (i == 0) { 578 bufIndex = readBuf[j]; 579 } else { 580 bufIndex++; 581 if (bufIndex != readBuf[j]) { 582 Log.e("bluetoothConnectionThroughputRead: Wrong Buffer index (First byte). " 583 + "Expected=" + bufIndex + ", read=" + readBuf[j]); 584 throw new IOException("bluetoothConnectionThroughputRead: Wrong Buffer(" 585 + (i + 1) + ") index (First byte). Expected=" 586 + bufIndex + ", read=" + readBuf[j]); 587 } 588 } 589 Log.d("bluetoothConnectionThroughputRead: First byte=" + bufIndex); 590 j++; 591 592 for (; j < bufferSize; j++) { 593 if (readBuf[j] != nextInChar) { 594 Log.e("Last Read Char Read wrong value. Read=" + String.valueOf(readBuf[j]) 595 + ", Expected=" + String.valueOf(nextInChar)); 596 throw new IOException("Read mismatched at buf=" + i + ", idx=" + j); 597 } 598 nextInChar = getNextOutputChar(nextInChar); 599 } 600 Log.d("bluetoothConnectionThroughputRead: Buffer Read index=" + i); 601 if (bufferSize < readBuf.length) { 602 tmp_array.write(readBuf, bufferSize, readBuf.length - bufferSize); 603 } 604 } 605 606 long endTesttime = System.currentTimeMillis(); 607 608 long diffTime = endTesttime - startTesttime; // time delta in milliseconds 609 Log.d("bluetoothConnectionThroughputRead: Completed! numBuffers=" + numBuffers 610 + ",delta time=" + diffTime + " millisec"); 611 long numBytes = numBuffers * bufferSize; 612 long dataRatePerMsec; 613 if (diffTime > 0) { 614 dataRatePerMsec = (1000L * numBytes) / diffTime; 615 } else { 616 dataRatePerMsec = 9999; 617 } 618 Integer dataRate = new Integer((int) dataRatePerMsec); 619 620 Log.d("bluetoothConnectionThroughputRead: Completed! numBytes=" + numBytes 621 + ", data rate=" + dataRate + " bytes per sec"); 622 623 return dataRate; 624 } catch (IOException e) { 625 mConnections.remove(conn.getUUID()); 626 throw e; 627 } 628 } 629 630 /** 631 * Stops Bluetooth connection 632 * 633 * @param connID the connection ID 634 */ 635 @Rpc(description = "Stops Bluetooth connection.") bluetoothSocketConnStop( @pcParametername = "connID", description = "Connection id") @pcOptional @pcDefault"") String connID)636 public void bluetoothSocketConnStop( 637 @RpcParameter(name = "connID", description = "Connection id") @RpcOptional 638 @RpcDefault("") String connID) { 639 BluetoothConnection conn; 640 try { 641 conn = getConnection(connID); 642 } catch (IOException e) { 643 e.printStackTrace(); 644 return; 645 } 646 if (conn == null) { 647 Log.d("bluetoothSocketConnStop: conn is NULL. connID=%s" + connID); 648 return; 649 } 650 Log.d("bluetoothSocketConnStop: connID=" + connID + ", UUID=" + conn.getUUID()); 651 652 conn.stop(); 653 mConnections.remove(conn.getUUID()); 654 655 if (mAcceptThread != null) { 656 mAcceptThread.cancel(); 657 } 658 if (mConnectThread != null) { 659 mConnectThread.cancel(); 660 } 661 } 662 663 @Override shutdown()664 public void shutdown() { 665 for (Map.Entry<String, BluetoothConnection> entry : mConnections.entrySet()) { 666 entry.getValue().stop(); 667 } 668 mConnections.clear(); 669 if (mAcceptThread != null) { 670 mAcceptThread.cancel(); 671 } 672 if (mConnectThread != null) { 673 mConnectThread.cancel(); 674 } 675 } 676 677 private class ConnectThread extends Thread { 678 private final BluetoothSocket mSocket; 679 private final Boolean mIsBle; 680 String mConnUuid; 681 ConnectThread(BluetoothDevice device, String uuid)682 ConnectThread(BluetoothDevice device, String uuid) { 683 BluetoothSocket tmp = null; 684 try { 685 tmp = device.createRfcommSocketToServiceRecord(UUID.fromString(uuid)); 686 } catch (IOException createSocketException) { 687 Log.e("Failed to create socket: " + createSocketException.toString()); 688 } 689 mIsBle = false; 690 mSocket = tmp; 691 } 692 ConnectThread(BluetoothDevice device, @RpcParameter(name = "psmValue") @RpcDefault(DEFAULT_PSM) Integer psmValue, @RpcParameter(name = "isBle") @RpcDefault("false") boolean isBle, @RpcParameter(name = "securedConn") @RpcDefault("false") boolean securedConn)693 ConnectThread(BluetoothDevice device, 694 @RpcParameter(name = "psmValue") 695 @RpcDefault(DEFAULT_PSM) Integer psmValue, 696 @RpcParameter(name = "isBle") @RpcDefault("false") boolean isBle, 697 @RpcParameter(name = "securedConn") 698 @RpcDefault("false") boolean securedConn) { 699 BluetoothSocket tmp = null; 700 Log.d("ConnectThread: psmValue=" + psmValue + ", isBle=" + isBle 701 + ", securedConn=" + securedConn); 702 try { 703 if (isBle) { 704 if (securedConn) { 705 tmp = device.createL2capChannel(psmValue); 706 } else { 707 tmp = device.createInsecureL2capChannel(psmValue); 708 } 709 } else { 710 if (securedConn) { 711 tmp = device.createL2capSocket(psmValue); 712 } else { 713 tmp = device.createInsecureL2capSocket(psmValue); 714 } 715 } 716 // Secured version: tmp = device.createL2capSocket(0x1011); 717 // tmp = device.createRfcommSocketToServiceRecord(UUID.fromString(uuid)); 718 } catch (IOException createSocketException) { 719 Log.e("Failed to create socket: " + createSocketException.toString()); 720 } 721 mIsBle = isBle; 722 mSocket = tmp; 723 } 724 run()725 public void run() { 726 mBluetoothAdapter.cancelDiscovery(); 727 try { 728 BluetoothConnection conn; 729 mSocket.connect(); 730 conn = new BluetoothConnection(mSocket); 731 mConnUuid = addConnection(conn); 732 Log.d("ConnectThread:run: isConnected=" + mSocket.isConnected() + ", address=" 733 + mSocket.getRemoteDevice().getAddress() + ", uuid=" + mConnUuid); 734 } catch (IOException connectException) { 735 Log.e("ConnectThread::run(): Error: Connection Unsuccessful"); 736 cancel(); 737 return; 738 } 739 } 740 cancel()741 public void cancel() { 742 if (mSocket != null) { 743 try { 744 mSocket.close(); 745 } catch (IOException closeException) { 746 Log.e("Failed to close socket: " + closeException.toString()); 747 } 748 } 749 } 750 getSocket()751 public BluetoothSocket getSocket() { 752 return mSocket; 753 } 754 getConnUuid()755 public String getConnUuid() { 756 Log.d("ConnectThread::getConnUuid(): mConnUuid=" + mConnUuid); 757 return mConnUuid; 758 } 759 } 760 761 private class AcceptThread extends Thread { 762 private final BluetoothServerSocket mServerSocket; 763 private final int mTimeout; 764 private BluetoothSocket mSocket; 765 String mConnUuid; 766 AcceptThread(String uuid, int timeout)767 AcceptThread(String uuid, int timeout) { 768 BluetoothServerSocket tmp = null; 769 mTimeout = timeout; 770 try { 771 tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(SDP_NAME, 772 UUID.fromString(uuid)); 773 } catch (IOException createSocketException) { 774 Log.e("Failed to create socket: " + createSocketException.toString()); 775 } 776 mServerSocket = tmp; 777 Log.d("AcceptThread: uuid=" + uuid); 778 } 779 AcceptThread(int psmValue, int timeout, boolean isBle, boolean securedConn)780 AcceptThread(int psmValue, int timeout, boolean isBle, boolean securedConn) { 781 BluetoothServerSocket tmp = null; 782 mTimeout = timeout; 783 try { 784 // Secured version: mBluetoothAdapter.listenUsingL2capOn(0x1011, false, false); 785 if (isBle) { 786 /* Assigned a dynamic LE_PSM Value */ 787 if (securedConn) { 788 tmp = mBluetoothAdapter.listenUsingL2capChannel(); 789 } else { 790 tmp = mBluetoothAdapter.listenUsingInsecureL2capChannel(); 791 } 792 } else { 793 if (securedConn) { 794 tmp = mBluetoothAdapter.listenUsingL2capOn(psmValue); 795 } else { 796 tmp = mBluetoothAdapter.listenUsingInsecureL2capOn(psmValue); 797 } 798 } 799 } catch (IOException createSocketException) { 800 Log.e("Failed to create Coc socket: " + createSocketException.toString()); 801 } 802 mServerSocket = tmp; 803 Log.d("AcceptThread: securedConn=" + securedConn + ", Old PSM value=" + psmValue 804 + ", new PSM=" + getPsm()); 805 } 806 run()807 public void run() { 808 try { 809 mSocket = mServerSocket.accept(mTimeout); 810 BluetoothConnection conn = new BluetoothConnection(mSocket, mServerSocket); 811 mConnUuid = addConnection(conn); 812 Log.d("AcceptThread:run: isConnected=" + mSocket.isConnected() + ", address=" 813 + mSocket.getRemoteDevice().getAddress() + ", uuid=" + mConnUuid); 814 } catch (IOException connectException) { 815 Log.e("AcceptThread:run: Failed to connect socket: " + connectException.toString()); 816 if (mSocket != null) { 817 cancel(); 818 } 819 return; 820 } 821 } 822 cancel()823 public void cancel() { 824 Log.d("AcceptThread:cancel: mmSocket=" + mSocket + ", mmServerSocket=" + mServerSocket); 825 if (mSocket != null) { 826 try { 827 mSocket.close(); 828 } catch (IOException closeException) { 829 Log.e("Failed to close socket: " + closeException.toString()); 830 } 831 } 832 if (mServerSocket != null) { 833 try { 834 mServerSocket.close(); 835 } catch (IOException closeException) { 836 Log.e("Failed to close socket: " + closeException.toString()); 837 } 838 } 839 } 840 getSocket()841 public BluetoothSocket getSocket() { 842 return mSocket; 843 } 844 getPsm()845 public int getPsm() { 846 return mServerSocket.getPsm(); 847 } 848 getConnUuid()849 public String getConnUuid() { 850 Log.d("ConnectThread::getConnUuid(): mConnUuid=" + mConnUuid); 851 return mConnUuid; 852 } 853 } 854 } 855