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      */
BluetoothChatService(Context context, Handler handler, UUID uuid)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      */
BluetoothChatService(Context context, Handler handler, boolean useBle)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      */
setState(int state)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. */
getState()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() */
start(boolean secure)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      */
getPsm(boolean secure)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      */
getSocketConnectionType()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      */
connect(BluetoothDevice device, boolean secure)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      */
connect(BluetoothDevice device, boolean secure, int psm)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      */
connected(BluetoothSocket socket, BluetoothDevice device, final String socketType)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      */
stop()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      */
write(byte[] out)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      */
connectionFailed()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      */
connectionLost()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 
AcceptThread(boolean secure)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 
getPsm()361         public int getPsm() {
362             return mLePsm;
363         }
364 
run()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 
cancel()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 
ConnectThread(BluetoothDevice device, boolean secure)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 
ConnectThread(BluetoothDevice device, boolean secure, int psm)444         public ConnectThread(BluetoothDevice device, boolean secure, int psm) {
445             mmDevice = device;
446             mmSocket = connectThreadCommon(device, secure, psm);
447         }
448 
connectThreadCommon(BluetoothDevice device, boolean secure, int psm)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 
run()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 
cancel()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 
ConnectedThread(BluetoothSocket socket, String socketType)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 
run()549         public void run() {
550             Log.i(TAG, "BEGIN mConnectedThread");
551             int bytes;
552 
553             // Keep listening to the InputStream while connected
554             while (true) {
555                 try {
556                     byte[] buffer = new byte[1024];
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          */
write(byte[] buffer)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 
cancel()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