Home | History | Annotate | Download | only in bluetooth
      1 /*
      2  * Copyright (C) 2011 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.android.cts.verifier.bluetooth;
     18 
     19 import android.bluetooth.BluetoothAdapter;
     20 import android.bluetooth.BluetoothDevice;
     21 import android.bluetooth.BluetoothServerSocket;
     22 import android.bluetooth.BluetoothSocket;
     23 import android.content.Context;
     24 import android.os.Bundle;
     25 import android.os.Handler;
     26 import android.os.Message;
     27 import android.util.Log;
     28 
     29 import java.io.IOException;
     30 import java.io.InputStream;
     31 import java.io.OutputStream;
     32 import java.util.UUID;
     33 
     34 /**
     35  * This class does all the work for setting up and managing Bluetooth
     36  * connections with other devices. It has a thread that listens for
     37  * incoming connections, a thread for connecting with a device, and a
     38  * thread for performing data transmissions when connected.
     39  */
     40 public class BluetoothChatService {
     41     // Message types sent from the BluetoothChatService Handler
     42     public static final int MESSAGE_STATE_CHANGE = 1;
     43     public static final int MESSAGE_READ = 2;
     44     public static final int MESSAGE_WRITE = 3;
     45     public static final int MESSAGE_DEVICE_NAME = 4;
     46     public static final int MESSAGE_TOAST = 5;
     47 
     48     // Key names received from the BluetoothChatService Handler
     49     public static final String DEVICE_NAME = "device_name";
     50     public static final String TOAST = "toast";
     51 
     52     static final UUID SECURE_UUID =
     53             UUID.fromString("8591d757-18ee-45e1-9b12-92875d06ba23");
     54     static final UUID INSECURE_UUID =
     55             UUID.fromString("301c214f-91a2-43bf-a795-09d1198a81a7");
     56     static final UUID HANDSFREE_INSECURE_UUID =
     57             UUID.fromString("0000111F-0000-1000-8000-00805F9B34FB");
     58 
     59     // Debugging
     60     private static final String TAG = "CtsBluetoothChatService";
     61     private static final boolean D = true;
     62 
     63     // Name for the SDP record when creating server socket
     64     private static final String NAME_SECURE = "CtsBluetoothChatSecure";
     65     private static final String NAME_INSECURE = "CtsBluetoothChatInsecure";
     66 
     67     // Member fields
     68     private final BluetoothAdapter mAdapter;
     69     private final Handler mHandler;
     70     private final UUID mUuid;
     71     private AcceptThread mSecureAcceptThread;
     72     private AcceptThread mInsecureAcceptThread;
     73     private ConnectThread mConnectThread;
     74     private ConnectedThread mConnectedThread;
     75     private int mState;
     76     private boolean mBleTransport;
     77     private int mLePsm;
     78     private int mSocketConnectionType = -1;
     79 
     80     // Constants that indicate the current connection state
     81     public static final int STATE_NONE = 0;       // we're doing nothing
     82     public static final int STATE_LISTEN = 1;     // now listening for incoming connections
     83     public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection
     84     public static final int STATE_CONNECTED = 3;  // now connected to a remote device
     85 
     86     /**
     87      * Constructor. Prepares a new BluetoothChat session.
     88      * @param context  The UI Activity Context
     89      * @param handler  A Handler to send messages back to the UI Activity
     90      */
     91     public BluetoothChatService(Context context, Handler handler, UUID uuid) {
     92         mAdapter = BluetoothAdapter.getDefaultAdapter();
     93         mState = STATE_NONE;
     94         mHandler = handler;
     95         mUuid = uuid;
     96         mBleTransport = false;
     97     }
     98 
     99     /**
    100      * Constructor. Prepares a new BluetoothChat session.
    101      * @param context  The UI Activity Context
    102      * @param handler  A Handler to send messages back to the UI Activity
    103      * @param useBle   A flag to use the BLE transport
    104      */
    105     public BluetoothChatService(Context context, Handler handler, boolean useBle) {
    106         mAdapter = BluetoothAdapter.getDefaultAdapter();
    107         mState = STATE_NONE;
    108         mHandler = handler;
    109         mUuid = null;
    110         mBleTransport = useBle;
    111         if (D) Log.d(TAG, "Construct BluetoothChatService: useBle=" + useBle);
    112     }
    113 
    114     /**
    115      * Set the current state of the chat connection
    116      * @param state  An integer defining the current connection state
    117      */
    118     private synchronized void setState(int state) {
    119         if (D) Log.d(TAG, "setState() " + mState + " -> " + state);
    120         mState = state;
    121 
    122         // Give the new state to the Handler so the UI Activity can update
    123         mHandler.obtainMessage(MESSAGE_STATE_CHANGE, state, -1).sendToTarget();
    124     }
    125 
    126     /**
    127      * Return the current connection state. */
    128     public synchronized int getState() {
    129         return mState;
    130     }
    131 
    132     /**
    133      * Start the chat service. Specifically start AcceptThread to begin a
    134      * session in listening (server) mode. Called by the Activity onResume() */
    135     public synchronized void start(boolean secure) {
    136         if (D) Log.d(TAG, "start secure: " + secure + UUID.randomUUID() + " - " + UUID.randomUUID());
    137 
    138         // Cancel any thread attempting to make a connection
    139         if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
    140 
    141         // Cancel any thread currently running a connection
    142         if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
    143 
    144         setState(STATE_LISTEN);
    145 
    146         // Start the thread to listen on a BluetoothServerSocket
    147         if (secure && mSecureAcceptThread == null) {
    148             mSecureAcceptThread = new AcceptThread(true);
    149             mSecureAcceptThread.start();
    150         }
    151         if (!secure && mInsecureAcceptThread == null) {
    152             mInsecureAcceptThread = new AcceptThread(false);
    153             mInsecureAcceptThread.start();
    154         }
    155     }
    156 
    157     /**
    158      * Return the assigned PSM value.
    159      */
    160     public synchronized int getPsm(boolean secure) {
    161         if (secure && mSecureAcceptThread != null) {
    162             return mSecureAcceptThread.getPsm();
    163         }
    164         else if (!secure && mInsecureAcceptThread != null) {
    165             return mInsecureAcceptThread.getPsm();
    166         }
    167         Log.e(TAG, "getPsm: Invalid PSM value");
    168         return 0;
    169     }
    170 
    171     /**
    172      * Return the socket Connection Type.
    173      */
    174     public synchronized int getSocketConnectionType() {
    175         return mSocketConnectionType;
    176     }
    177 
    178     /**
    179      * Start the ConnectThread to initiate a connection to a remote device.
    180      * @param device  The BluetoothDevice to connect to
    181      * @param secure Socket Security type - Secure (true) , Insecure (false)
    182      */
    183     public synchronized void connect(BluetoothDevice device, boolean secure) {
    184         if (!mBleTransport) {
    185             connect(device, secure, 0);
    186         } else {
    187             Log.e(TAG, "connect: Error: LE cannot call this method!");
    188         }
    189     }
    190 
    191     /**
    192      * Start the ConnectThread to initiate a connection to a remote device.
    193      * @param device  The BluetoothDevice to connect to
    194      * @param secure Socket Security type - Secure (true) , Insecure (false)
    195      * @param psm Assigned PSM value
    196      */
    197     public synchronized void connect(BluetoothDevice device, boolean secure, int psm) {
    198         if (D) Log.d(TAG, "connect to: " + device + ", psm: " + psm + ", ble: " + mBleTransport);
    199 
    200         // Cancel any thread attempting to make a connection
    201         if (mState == STATE_CONNECTING) {
    202             if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
    203         }
    204 
    205         // Cancel any thread currently running a connection
    206         if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
    207 
    208         // Start the thread to connect with the given device
    209         mConnectThread = new ConnectThread(device, secure, psm);
    210         mConnectThread.start();
    211         setState(STATE_CONNECTING);
    212     }
    213 
    214     /**
    215      * Start the ConnectedThread to begin managing a Bluetooth connection
    216      * @param socket  The BluetoothSocket on which the connection was made
    217      * @param device  The BluetoothDevice that has been connected
    218      */
    219     public synchronized void connected(BluetoothSocket socket, BluetoothDevice
    220             device, final String socketType) {
    221         if (D) Log.d(TAG, "connected, Socket Type: " + socketType);
    222 
    223         // Cancel the thread that completed the connection
    224         if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
    225 
    226         // Cancel any thread currently running a connection
    227         if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
    228 
    229         // Cancel the accept thread because we only want to connect to one device
    230         if (mSecureAcceptThread != null) {
    231             mSecureAcceptThread.cancel();
    232             mSecureAcceptThread = null;
    233         }
    234         if (mInsecureAcceptThread != null) {
    235             mInsecureAcceptThread.cancel();
    236             mInsecureAcceptThread = null;
    237         }
    238 
    239         // Start the thread to manage the connection and perform transmissions
    240         mConnectedThread = new ConnectedThread(socket, socketType);
    241         mConnectedThread.start();
    242 
    243         // Send the name of the connected device back to the UI Activity
    244         Message msg = mHandler.obtainMessage(MESSAGE_DEVICE_NAME);
    245         Bundle bundle = new Bundle();
    246         bundle.putString(DEVICE_NAME, device.getName());
    247         msg.setData(bundle);
    248         mHandler.sendMessage(msg);
    249 
    250         setState(STATE_CONNECTED);
    251     }
    252 
    253     /**
    254      * Stop all threads
    255      */
    256     public synchronized void stop() {
    257         if (D) Log.d(TAG, "stop");
    258 
    259         if (mConnectThread != null) {
    260             mConnectThread.cancel();
    261             mConnectThread = null;
    262         }
    263 
    264         if (mConnectedThread != null) {
    265             mConnectedThread.cancel();
    266             mConnectedThread = null;
    267         }
    268 
    269         if (mSecureAcceptThread != null) {
    270             mSecureAcceptThread.cancel();
    271             mSecureAcceptThread = null;
    272         }
    273 
    274         if (mInsecureAcceptThread != null) {
    275             mInsecureAcceptThread.cancel();
    276             mInsecureAcceptThread = null;
    277         }
    278         setState(STATE_NONE);
    279     }
    280 
    281     /**
    282      * Write to the ConnectedThread in an unsynchronized manner
    283      * @param out The bytes to write
    284      * @see ConnectedThread#write(byte[])
    285      */
    286     public void write(byte[] out) {
    287         // Create temporary object
    288         ConnectedThread r;
    289         // Synchronize a copy of the ConnectedThread
    290         synchronized (this) {
    291             if (mState != STATE_CONNECTED) return;
    292             r = mConnectedThread;
    293         }
    294         // Perform the write unsynchronized
    295         r.write(out);
    296     }
    297 
    298     /**
    299      * Indicate that the connection attempt failed and notify the UI Activity.
    300      */
    301     private void connectionFailed() {
    302         // Send a failure message back to the Activity
    303         Message msg = mHandler.obtainMessage(MESSAGE_TOAST);
    304         Bundle bundle = new Bundle();
    305         bundle.putString(TOAST, "Unable to connect device");
    306         msg.setData(bundle);
    307         mHandler.sendMessage(msg);
    308     }
    309 
    310     /**
    311      * Indicate that the connection was lost and notify the UI Activity.
    312      */
    313     private void connectionLost() {
    314         // Send a failure message back to the Activity
    315         Message msg = mHandler.obtainMessage(MESSAGE_TOAST);
    316         Bundle bundle = new Bundle();
    317         bundle.putString(TOAST, "Device connection was lost");
    318         msg.setData(bundle);
    319         mHandler.sendMessage(msg);
    320     }
    321 
    322     /**
    323      * This thread runs while listening for incoming connections. It behaves
    324      * like a server-side client. It runs until a connection is accepted
    325      * (or until cancelled).
    326      */
    327     private class AcceptThread extends Thread {
    328         // The local server socket
    329         private final BluetoothServerSocket mmServerSocket;
    330         private String mSocketType;
    331 
    332         public AcceptThread(boolean secure) {
    333             BluetoothServerSocket tmp = null;
    334             mSocketType = secure ? "Secure" : "Insecure";
    335 
    336             // Create a new listening server socket
    337             try {
    338                 if (mBleTransport) {
    339                     if (secure) {
    340                         tmp = mAdapter.listenUsingL2capChannel();
    341                     } else {
    342                         tmp = mAdapter.listenUsingInsecureL2capChannel();
    343                     }
    344                 } else {
    345                     if (secure) {
    346                         tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME_SECURE, mUuid);
    347                     } else {
    348                         tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord(NAME_INSECURE, mUuid);
    349                     }
    350                 }
    351             } catch (IOException e) {
    352                 Log.e(TAG, "Socket Type: " + mSocketType + ", le: " + mBleTransport + " listen() failed", e);
    353             }
    354             mmServerSocket = tmp;
    355             if (mBleTransport) {
    356                 // Get the assigned PSM value
    357                 mLePsm = mmServerSocket.getPsm();
    358             }
    359         }
    360 
    361         public int getPsm() {
    362             return mLePsm;
    363         }
    364 
    365         public void run() {
    366             if (D) Log.d(TAG, "Socket Type: " + mSocketType +
    367                     " BEGIN mAcceptThread" + this);
    368             setName("AcceptThread" + mSocketType);
    369 
    370             BluetoothSocket socket = null;
    371 
    372             // Listen to the server socket if we're not connected
    373             while (mState != STATE_CONNECTED) {
    374                 try {
    375                     // This is a blocking call and will only return on a
    376                     // successful connection or an exception
    377                     socket = mmServerSocket.accept();
    378                 } catch (IOException e) {
    379                     Log.e(TAG, "Socket Type: " + mSocketType + " accept() failed", e);
    380                     break;
    381                 }
    382 
    383                 // If a connection was accepted
    384                 if (socket != null) {
    385                     synchronized (BluetoothChatService.this) {
    386                         switch (mState) {
    387                         case STATE_LISTEN:
    388                         case STATE_CONNECTING:
    389                             // Situation normal. Start the connected thread.
    390                             mSocketConnectionType = socket.getConnectionType();
    391                             connected(socket, socket.getRemoteDevice(),
    392                                     mSocketType);
    393                             break;
    394                         case STATE_NONE:
    395                         case STATE_CONNECTED:
    396                             // Either not ready or already connected. Terminate new socket.
    397                             try {
    398                                 socket.close();
    399                             } catch (IOException e) {
    400                                 Log.e(TAG, "Could not close unwanted socket", e);
    401                             }
    402                             break;
    403                         }
    404                     }
    405                 } else {
    406                     Log.i(TAG, "Got null socket");
    407                 }
    408             }
    409             if (D) {
    410                 Log.i(TAG, "END mAcceptThread, socket Type: " + mSocketType
    411                          + ", SocketConnectionType: " + mSocketConnectionType);
    412             }
    413         }
    414 
    415         public void cancel() {
    416             if (D) Log.d(TAG, "Socket Type" + mSocketType + "cancel " + this);
    417             try {
    418                 mmServerSocket.close();
    419             } catch (IOException e) {
    420                 Log.e(TAG, "Socket Type" + mSocketType + "close() of server failed", e);
    421             }
    422         }
    423     }
    424 
    425 
    426     /**
    427      * This thread runs while attempting to make an outgoing connection
    428      * with a device. It runs straight through; the connection either
    429      * succeeds or fails.
    430      */
    431     private class ConnectThread extends Thread {
    432         private final BluetoothSocket mmSocket;
    433         private final BluetoothDevice mmDevice;
    434         private String mSocketType;
    435 
    436         public ConnectThread(BluetoothDevice device, boolean secure) {
    437             if (mBleTransport) {
    438                 Log.e(TAG, "ConnectThread: Error: LE should not call this constructor");
    439             }
    440             mmDevice = device;
    441             mmSocket = connectThreadCommon(device, secure, 0);
    442         }
    443 
    444         public ConnectThread(BluetoothDevice device, boolean secure, int psm) {
    445             mmDevice = device;
    446             mmSocket = connectThreadCommon(device, secure, psm);
    447         }
    448 
    449         private BluetoothSocket connectThreadCommon(BluetoothDevice device, boolean secure, int psm) {
    450             BluetoothSocket tmp = null;
    451             mSocketType = secure ? "Secure" : "Insecure";
    452 
    453             // Get a BluetoothSocket for a connection with the
    454             // given BluetoothDevice
    455             try {
    456                 if (mBleTransport) {
    457                     if (secure) {
    458                         tmp = device.createL2capChannel(psm);
    459                     } else {
    460                         tmp = device.createInsecureL2capChannel(psm);
    461                     }
    462                 } else {
    463                     if (secure) {
    464                         tmp = device.createRfcommSocketToServiceRecord(mUuid);
    465                     } else {
    466                         tmp = device.createInsecureRfcommSocketToServiceRecord(mUuid);
    467                     }
    468                 }
    469             } catch (IOException e) {
    470                 Log.e(TAG, "Socket Type: " + mSocketType + "create() failed", e);
    471             }
    472 
    473             mSocketConnectionType = tmp.getConnectionType();
    474 
    475             return tmp;
    476         }
    477 
    478         public void run() {
    479             Log.i(TAG, "BEGIN mConnectThread SocketType:" + mSocketType
    480                   + ", mSocketConnectionType: " + mSocketConnectionType);
    481             setName("ConnectThread" + mSocketType);
    482 
    483             // Always cancel discovery because it will slow down a connection
    484             mAdapter.cancelDiscovery();
    485 
    486             // Make a connection to the BluetoothSocket
    487             try {
    488                 // This is a blocking call and will only return on a
    489                 // successful connection or an exception
    490                 mmSocket.connect();
    491             } catch (IOException e) {
    492                 Log.e(TAG, "connect() failed ", e);
    493                 // Close the socket
    494                 try {
    495                     mmSocket.close();
    496                 } catch (IOException e2) {
    497                     Log.e(TAG, "unable to close() " + mSocketType +
    498                             " socket during connection failure", e2);
    499                 }
    500                 connectionFailed();
    501                 return;
    502             }
    503 
    504             // Reset the ConnectThread because we're done
    505             synchronized (BluetoothChatService.this) {
    506                 mConnectThread = null;
    507             }
    508 
    509             // Start the connected thread
    510             connected(mmSocket, mmDevice, mSocketType);
    511         }
    512 
    513         public void cancel() {
    514             try {
    515                 mmSocket.close();
    516             } catch (IOException e) {
    517                 Log.e(TAG, "close() of connect " + mSocketType + " socket failed", e);
    518             }
    519         }
    520     }
    521 
    522     /**
    523      * This thread runs during a connection with a remote device.
    524      * It handles all incoming and outgoing transmissions.
    525      */
    526     private class ConnectedThread extends Thread {
    527         private final BluetoothSocket mmSocket;
    528         private final InputStream mmInStream;
    529         private final OutputStream mmOutStream;
    530 
    531         public ConnectedThread(BluetoothSocket socket, String socketType) {
    532             Log.d(TAG, "create ConnectedThread: " + socketType);
    533             mmSocket = socket;
    534             InputStream tmpIn = null;
    535             OutputStream tmpOut = null;
    536 
    537             // Get the BluetoothSocket input and output streams
    538             try {
    539                 tmpIn = socket.getInputStream();
    540                 tmpOut = socket.getOutputStream();
    541             } catch (IOException e) {
    542                 Log.e(TAG, "temp sockets not created", e);
    543             }
    544 
    545             mmInStream = tmpIn;
    546             mmOutStream = tmpOut;
    547         }
    548 
    549         public void run() {
    550             Log.i(TAG, "BEGIN mConnectedThread");
    551             byte[] buffer = new byte[1024];
    552             int bytes;
    553 
    554             // Keep listening to the InputStream while connected
    555             while (true) {
    556                 try {
    557                     // Read from the InputStream
    558                     bytes = mmInStream.read(buffer);
    559 
    560                     // Send the obtained bytes to the UI Activity
    561                     mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
    562                             .sendToTarget();
    563                 } catch (IOException e) {
    564                     Log.e(TAG, "disconnected", e);
    565                     connectionLost();
    566                     break;
    567                 }
    568             }
    569         }
    570 
    571         /**
    572          * Write to the connected OutStream.
    573          * @param buffer  The bytes to write
    574          */
    575         public void write(byte[] buffer) {
    576             try {
    577                 mmOutStream.write(buffer);
    578                 mmOutStream.flush();
    579 
    580                 // Share the sent message back to the UI Activity
    581                 mHandler.obtainMessage(MESSAGE_WRITE, -1, -1, buffer)
    582                         .sendToTarget();
    583             } catch (IOException e) {
    584                 Log.e(TAG, "Exception during write", e);
    585             }
    586         }
    587 
    588         public void cancel() {
    589             try {
    590                 mmSocket.close();
    591             } catch (IOException e) {
    592                 Log.e(TAG, "close() of connect socket failed", e);
    593             }
    594         }
    595     }
    596 }
    597