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