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