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