• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
BluetoothChatService(Context context, Handler handler)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      */
setState(int state)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. */
getState()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() */
start()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      */
connect(BluetoothDevice device, boolean secure)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      */
connected(BluetoothSocket socket, BluetoothDevice device, final String socketType)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      */
stop()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      */
write(byte[] out)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      */
connectionFailed()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      */
connectionLost()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 
AcceptThread(boolean secure)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 
run()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 
cancel()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 
ConnectThread(BluetoothDevice device, boolean secure)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 
run()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 
cancel()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 
ConnectedThread(BluetoothSocket socket, String socketType)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 
run()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          */
write(byte[] buffer)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 
cancel()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