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 byte[] buffer = new byte[1024]; 552 int bytes; 553 554 // Keep listening to the InputStream while connected 555 while (true) { 556 try { 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