Home | History | Annotate | Download | only in BluetoothChat
      1 /*
      2  * Copyright (C) 2009 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.example.android.BluetoothChat;
     18 
     19 import java.io.IOException;
     20 import java.io.InputStream;
     21 import java.io.OutputStream;
     22 import java.util.UUID;
     23 
     24 import android.bluetooth.BluetoothAdapter;
     25 import android.bluetooth.BluetoothDevice;
     26 import android.bluetooth.BluetoothServerSocket;
     27 import android.bluetooth.BluetoothSocket;
     28 import android.content.Context;
     29 import android.os.Bundle;
     30 import android.os.Handler;
     31 import android.os.Message;
     32 import android.util.Log;
     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     // Debugging
     42     private static final String TAG = "BluetoothChatService";
     43     private static final boolean D = true;
     44 
     45     // Name for the SDP record when creating server socket
     46     private static final String NAME_SECURE = "BluetoothChatSecure";
     47     private static final String NAME_INSECURE = "BluetoothChatInsecure";
     48 
     49     // Unique UUID for this application
     50     private static final UUID MY_UUID_SECURE =
     51         UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66");
     52     private static final UUID MY_UUID_INSECURE =
     53         UUID.fromString("8ce255c0-200a-11e0-ac64-0800200c9a66");
     54 
     55     // Member fields
     56     private final BluetoothAdapter mAdapter;
     57     private final Handler mHandler;
     58     private AcceptThread mSecureAcceptThread;
     59     private AcceptThread mInsecureAcceptThread;
     60     private ConnectThread mConnectThread;
     61     private ConnectedThread mConnectedThread;
     62     private int mState;
     63 
     64     // Constants that indicate the current connection state
     65     public static final int STATE_NONE = 0;       // we're doing nothing
     66     public static final int STATE_LISTEN = 1;     // now listening for incoming connections
     67     public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection
     68     public static final int STATE_CONNECTED = 3;  // now connected to a remote device
     69 
     70     /**
     71      * Constructor. Prepares a new BluetoothChat session.
     72      * @param context  The UI Activity Context
     73      * @param handler  A Handler to send messages back to the UI Activity
     74      */
     75     public BluetoothChatService(Context context, Handler handler) {
     76         mAdapter = BluetoothAdapter.getDefaultAdapter();
     77         mState = STATE_NONE;
     78         mHandler = handler;
     79     }
     80 
     81     /**
     82      * Set the current state of the chat connection
     83      * @param state  An integer defining the current connection state
     84      */
     85     private synchronized void setState(int state) {
     86         if (D) Log.d(TAG, "setState() " + mState + " -> " + state);
     87         mState = state;
     88 
     89         // Give the new state to the Handler so the UI Activity can update
     90         mHandler.obtainMessage(BluetoothChat.MESSAGE_STATE_CHANGE, state, -1).sendToTarget();
     91     }
     92 
     93     /**
     94      * Return the current connection state. */
     95     public synchronized int getState() {
     96         return mState;
     97     }
     98 
     99     /**
    100      * Start the chat service. Specifically start AcceptThread to begin a
    101      * session in listening (server) mode. Called by the Activity onResume() */
    102     public synchronized void start() {
    103         if (D) Log.d(TAG, "start");
    104 
    105         // Cancel any thread attempting to make a connection
    106         if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
    107 
    108         // Cancel any thread currently running a connection
    109         if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
    110 
    111         setState(STATE_LISTEN);
    112 
    113         // Start the thread to listen on a BluetoothServerSocket
    114         if (mSecureAcceptThread == null) {
    115             mSecureAcceptThread = new AcceptThread(true);
    116             mSecureAcceptThread.start();
    117         }
    118         if (mInsecureAcceptThread == null) {
    119             mInsecureAcceptThread = new AcceptThread(false);
    120             mInsecureAcceptThread.start();
    121         }
    122     }
    123 
    124     /**
    125      * Start the ConnectThread to initiate a connection to a remote device.
    126      * @param device  The BluetoothDevice to connect
    127      * @param secure Socket Security type - Secure (true) , Insecure (false)
    128      */
    129     public synchronized void connect(BluetoothDevice device, boolean secure) {
    130         if (D) Log.d(TAG, "connect to: " + device);
    131 
    132         // Cancel any thread attempting to make a connection
    133         if (mState == STATE_CONNECTING) {
    134             if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
    135         }
    136 
    137         // Cancel any thread currently running a connection
    138         if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
    139 
    140         // Start the thread to connect with the given device
    141         mConnectThread = new ConnectThread(device, secure);
    142         mConnectThread.start();
    143         setState(STATE_CONNECTING);
    144     }
    145 
    146     /**
    147      * Start the ConnectedThread to begin managing a Bluetooth connection
    148      * @param socket  The BluetoothSocket on which the connection was made
    149      * @param device  The BluetoothDevice that has been connected
    150      */
    151     public synchronized void connected(BluetoothSocket socket, BluetoothDevice
    152             device, final String socketType) {
    153         if (D) Log.d(TAG, "connected, Socket Type:" + socketType);
    154 
    155         // Cancel the thread that completed the connection
    156         if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
    157 
    158         // Cancel any thread currently running a connection
    159         if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
    160 
    161         // Cancel the accept thread because we only want to connect to one device
    162         if (mSecureAcceptThread != null) {
    163             mSecureAcceptThread.cancel();
    164             mSecureAcceptThread = null;
    165         }
    166         if (mInsecureAcceptThread != null) {
    167             mInsecureAcceptThread.cancel();
    168             mInsecureAcceptThread = null;
    169         }
    170 
    171         // Start the thread to manage the connection and perform transmissions
    172         mConnectedThread = new ConnectedThread(socket, socketType);
    173         mConnectedThread.start();
    174 
    175         // Send the name of the connected device back to the UI Activity
    176         Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_DEVICE_NAME);
    177         Bundle bundle = new Bundle();
    178         bundle.putString(BluetoothChat.DEVICE_NAME, device.getName());
    179         msg.setData(bundle);
    180         mHandler.sendMessage(msg);
    181 
    182         setState(STATE_CONNECTED);
    183     }
    184 
    185     /**
    186      * Stop all threads
    187      */
    188     public synchronized void stop() {
    189         if (D) Log.d(TAG, "stop");
    190 
    191         if (mConnectThread != null) {
    192             mConnectThread.cancel();
    193             mConnectThread = null;
    194         }
    195 
    196         if (mConnectedThread != null) {
    197             mConnectedThread.cancel();
    198             mConnectedThread = null;
    199         }
    200 
    201         if (mSecureAcceptThread != null) {
    202             mSecureAcceptThread.cancel();
    203             mSecureAcceptThread = null;
    204         }
    205 
    206         if (mInsecureAcceptThread != null) {
    207             mInsecureAcceptThread.cancel();
    208             mInsecureAcceptThread = null;
    209         }
    210         setState(STATE_NONE);
    211     }
    212 
    213     /**
    214      * Write to the ConnectedThread in an unsynchronized manner
    215      * @param out The bytes to write
    216      * @see ConnectedThread#write(byte[])
    217      */
    218     public void write(byte[] out) {
    219         // Create temporary object
    220         ConnectedThread r;
    221         // Synchronize a copy of the ConnectedThread
    222         synchronized (this) {
    223             if (mState != STATE_CONNECTED) return;
    224             r = mConnectedThread;
    225         }
    226         // Perform the write unsynchronized
    227         r.write(out);
    228     }
    229 
    230     /**
    231      * Indicate that the connection attempt failed and notify the UI Activity.
    232      */
    233     private void connectionFailed() {
    234         // Send a failure message back to the Activity
    235         Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_TOAST);
    236         Bundle bundle = new Bundle();
    237         bundle.putString(BluetoothChat.TOAST, "Unable to connect device");
    238         msg.setData(bundle);
    239         mHandler.sendMessage(msg);
    240 
    241         // Start the service over to restart listening mode
    242         BluetoothChatService.this.start();
    243     }
    244 
    245     /**
    246      * Indicate that the connection was lost and notify the UI Activity.
    247      */
    248     private void connectionLost() {
    249         // Send a failure message back to the Activity
    250         Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_TOAST);
    251         Bundle bundle = new Bundle();
    252         bundle.putString(BluetoothChat.TOAST, "Device connection was lost");
    253         msg.setData(bundle);
    254         mHandler.sendMessage(msg);
    255 
    256         // Start the service over to restart listening mode
    257         BluetoothChatService.this.start();
    258     }
    259 
    260     /**
    261      * This thread runs while listening for incoming connections. It behaves
    262      * like a server-side client. It runs until a connection is accepted
    263      * (or until cancelled).
    264      */
    265     private class AcceptThread extends Thread {
    266         // The local server socket
    267         private final BluetoothServerSocket mmServerSocket;
    268         private String mSocketType;
    269 
    270         public AcceptThread(boolean secure) {
    271             BluetoothServerSocket tmp = null;
    272             mSocketType = secure ? "Secure":"Insecure";
    273 
    274             // Create a new listening server socket
    275             try {
    276                 if (secure) {
    277                     tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME_SECURE,
    278                         MY_UUID_SECURE);
    279                 } else {
    280                     tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord(
    281                             NAME_INSECURE, MY_UUID_INSECURE);
    282                 }
    283             } catch (IOException e) {
    284                 Log.e(TAG, "Socket Type: " + mSocketType + "listen() failed", e);
    285             }
    286             mmServerSocket = tmp;
    287         }
    288 
    289         public void run() {
    290             if (D) Log.d(TAG, "Socket Type: " + mSocketType +
    291                     "BEGIN mAcceptThread" + this);
    292             setName("AcceptThread" + mSocketType);
    293 
    294             BluetoothSocket socket = null;
    295 
    296             // Listen to the server socket if we're not connected
    297             while (mState != STATE_CONNECTED) {
    298                 try {
    299                     // This is a blocking call and will only return on a
    300                     // successful connection or an exception
    301                     socket = mmServerSocket.accept();
    302                 } catch (IOException e) {
    303                     Log.e(TAG, "Socket Type: " + mSocketType + "accept() failed", e);
    304                     break;
    305                 }
    306 
    307                 // If a connection was accepted
    308                 if (socket != null) {
    309                     synchronized (BluetoothChatService.this) {
    310                         switch (mState) {
    311                         case STATE_LISTEN:
    312                         case STATE_CONNECTING:
    313                             // Situation normal. Start the connected thread.
    314                             connected(socket, socket.getRemoteDevice(),
    315                                     mSocketType);
    316                             break;
    317                         case STATE_NONE:
    318                         case STATE_CONNECTED:
    319                             // Either not ready or already connected. Terminate new socket.
    320                             try {
    321                                 socket.close();
    322                             } catch (IOException e) {
    323                                 Log.e(TAG, "Could not close unwanted socket", e);
    324                             }
    325                             break;
    326                         }
    327                     }
    328                 }
    329             }
    330             if (D) Log.i(TAG, "END mAcceptThread, socket Type: " + mSocketType);
    331 
    332         }
    333 
    334         public void cancel() {
    335             if (D) Log.d(TAG, "Socket Type" + mSocketType + "cancel " + this);
    336             try {
    337                 mmServerSocket.close();
    338             } catch (IOException e) {
    339                 Log.e(TAG, "Socket Type" + mSocketType + "close() of server failed", e);
    340             }
    341         }
    342     }
    343 
    344 
    345     /**
    346      * This thread runs while attempting to make an outgoing connection
    347      * with a device. It runs straight through; the connection either
    348      * succeeds or fails.
    349      */
    350     private class ConnectThread extends Thread {
    351         private final BluetoothSocket mmSocket;
    352         private final BluetoothDevice mmDevice;
    353         private String mSocketType;
    354 
    355         public ConnectThread(BluetoothDevice device, boolean secure) {
    356             mmDevice = device;
    357             BluetoothSocket tmp = null;
    358             mSocketType = secure ? "Secure" : "Insecure";
    359 
    360             // Get a BluetoothSocket for a connection with the
    361             // given BluetoothDevice
    362             try {
    363                 if (secure) {
    364                     tmp = device.createRfcommSocketToServiceRecord(
    365                             MY_UUID_SECURE);
    366                 } else {
    367                     tmp = device.createInsecureRfcommSocketToServiceRecord(
    368                             MY_UUID_INSECURE);
    369                 }
    370             } catch (IOException e) {
    371                 Log.e(TAG, "Socket Type: " + mSocketType + "create() failed", e);
    372             }
    373             mmSocket = tmp;
    374         }
    375 
    376         public void run() {
    377             Log.i(TAG, "BEGIN mConnectThread SocketType:" + mSocketType);
    378             setName("ConnectThread" + mSocketType);
    379 
    380             // Always cancel discovery because it will slow down a connection
    381             mAdapter.cancelDiscovery();
    382 
    383             // Make a connection to the BluetoothSocket
    384             try {
    385                 // This is a blocking call and will only return on a
    386                 // successful connection or an exception
    387                 mmSocket.connect();
    388             } catch (IOException e) {
    389                 // Close the socket
    390                 try {
    391                     mmSocket.close();
    392                 } catch (IOException e2) {
    393                     Log.e(TAG, "unable to close() " + mSocketType +
    394                             " socket during connection failure", e2);
    395                 }
    396                 connectionFailed();
    397                 return;
    398             }
    399 
    400             // Reset the ConnectThread because we're done
    401             synchronized (BluetoothChatService.this) {
    402                 mConnectThread = null;
    403             }
    404 
    405             // Start the connected thread
    406             connected(mmSocket, mmDevice, mSocketType);
    407         }
    408 
    409         public void cancel() {
    410             try {
    411                 mmSocket.close();
    412             } catch (IOException e) {
    413                 Log.e(TAG, "close() of connect " + mSocketType + " socket failed", e);
    414             }
    415         }
    416     }
    417 
    418     /**
    419      * This thread runs during a connection with a remote device.
    420      * It handles all incoming and outgoing transmissions.
    421      */
    422     private class ConnectedThread extends Thread {
    423         private final BluetoothSocket mmSocket;
    424         private final InputStream mmInStream;
    425         private final OutputStream mmOutStream;
    426 
    427         public ConnectedThread(BluetoothSocket socket, String socketType) {
    428             Log.d(TAG, "create ConnectedThread: " + socketType);
    429             mmSocket = socket;
    430             InputStream tmpIn = null;
    431             OutputStream tmpOut = null;
    432 
    433             // Get the BluetoothSocket input and output streams
    434             try {
    435                 tmpIn = socket.getInputStream();
    436                 tmpOut = socket.getOutputStream();
    437             } catch (IOException e) {
    438                 Log.e(TAG, "temp sockets not created", e);
    439             }
    440 
    441             mmInStream = tmpIn;
    442             mmOutStream = tmpOut;
    443         }
    444 
    445         public void run() {
    446             Log.i(TAG, "BEGIN mConnectedThread");
    447             byte[] buffer = new byte[1024];
    448             int bytes;
    449 
    450             // Keep listening to the InputStream while connected
    451             while (true) {
    452                 try {
    453                     // Read from the InputStream
    454                     bytes = mmInStream.read(buffer);
    455 
    456                     // Send the obtained bytes to the UI Activity
    457                     mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes, -1, buffer)
    458                             .sendToTarget();
    459                 } catch (IOException e) {
    460                     Log.e(TAG, "disconnected", e);
    461                     connectionLost();
    462                     // Start the service over to restart listening mode
    463                     BluetoothChatService.this.start();
    464                     break;
    465                 }
    466             }
    467         }
    468 
    469         /**
    470          * Write to the connected OutStream.
    471          * @param buffer  The bytes to write
    472          */
    473         public void write(byte[] buffer) {
    474             try {
    475                 mmOutStream.write(buffer);
    476 
    477                 // Share the sent message back to the UI Activity
    478                 mHandler.obtainMessage(BluetoothChat.MESSAGE_WRITE, -1, -1, buffer)
    479                         .sendToTarget();
    480             } catch (IOException e) {
    481                 Log.e(TAG, "Exception during write", e);
    482             }
    483         }
    484 
    485         public void cancel() {
    486             try {
    487                 mmSocket.close();
    488             } catch (IOException e) {
    489                 Log.e(TAG, "close() of connect socket failed", e);
    490             }
    491         }
    492     }
    493 }
    494