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