Home | History | Annotate | Download | only in bluetooth
      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 
     65     public BluetoothSocketConnFacade(FacadeManager manager) {
     66         super(manager);
     67         mEventFacade = manager.getReceiver(EventFacade.class);
     68         mService = manager.getService();
     69         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
     70     }
     71 
     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 
     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")
    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. ")
    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. ")
    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.")
    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")
    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")
    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")
    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.")
    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.")
    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. ")
    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. ")
    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")
    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")
    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.")
    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.")
    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.")
    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.")
    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.")
    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.")
    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 
    470     private static byte getNextOutputChar(byte in) {
    471         in++;
    472         if (in >= 'z') {
    473             in = 'a';
    474         }
    475         return in;
    476     }
    477 
    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")
    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")
    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.")
    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
    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 
    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 
    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 
    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 
    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 
    737         public BluetoothSocket getSocket() {
    738             return mSocket;
    739         }
    740 
    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 
    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 
    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 
    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 
    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 
    827         public BluetoothSocket getSocket() {
    828             return mSocket;
    829         }
    830 
    831         public int getPsm() {
    832             return mServerSocket.getPsm();
    833         }
    834 
    835         public String getConnUuid() {
    836             Log.d("ConnectThread::getConnUuid(): mConnUuid=" + mConnUuid);
    837             return mConnUuid;
    838         }
    839     }
    840 }
    841