• 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.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