• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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