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