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 static org.junit.Assert.assertTrue; 20 21 import android.annotation.NonNull; 22 import android.bluetooth.BluetoothAdapter; 23 import android.bluetooth.BluetoothDevice; 24 import android.bluetooth.BluetoothServerSocket; 25 import android.bluetooth.BluetoothSocket; 26 import android.content.Context; 27 import android.os.Bundle; 28 import android.os.Handler; 29 import android.os.Message; 30 import android.util.Log; 31 32 import java.io.IOException; 33 import java.io.InputStream; 34 import java.io.OutputStream; 35 import java.util.UUID; 36 37 /** 38 * This class does all the work for setting up and managing Bluetooth 39 * connections with other devices. It has a thread that listens for 40 * incoming connections, a thread for connecting with a device, and a 41 * thread for performing data transmissions when connected. 42 */ 43 public class BluetoothChatService { 44 // Message types sent from the BluetoothChatService Handler 45 public static final int MESSAGE_STATE_CHANGE = 1; 46 public static final int MESSAGE_READ = 2; 47 public static final int MESSAGE_WRITE = 3; 48 public static final int MESSAGE_DEVICE_NAME = 4; 49 public static final int MESSAGE_TOAST = 5; 50 51 // Key names received from the BluetoothChatService Handler 52 public static final String DEVICE_NAME = "device_name"; 53 public static final String TOAST = "toast"; 54 55 static final UUID SECURE_UUID = 56 UUID.fromString("8591d757-18ee-45e1-9b12-92875d06ba23"); 57 static final UUID INSECURE_UUID = 58 UUID.fromString("301c214f-91a2-43bf-a795-09d1198a81a7"); 59 static final UUID HANDSFREE_INSECURE_UUID = 60 UUID.fromString("0000111F-0000-1000-8000-00805F9B34FB"); 61 62 // Debugging 63 private static final String TAG = "CtsBluetoothChatService"; 64 private static final boolean D = true; 65 66 // Name for the SDP record when creating server socket 67 private static final String NAME_SECURE = "CtsBluetoothChatSecure"; 68 private static final String NAME_INSECURE = "CtsBluetoothChatInsecure"; 69 70 // Member fields 71 private final BluetoothAdapter mAdapter; 72 private final Handler mHandler; 73 private final UUID mUuid; 74 private AcceptThread mSecureAcceptThread; 75 private AcceptThread mInsecureAcceptThread; 76 private ConnectThread mConnectThread; 77 private ConnectedThread mConnectedThread; 78 private int mState; 79 private boolean mBleTransport; 80 private int mLePsm; 81 private int mSocketConnectionType = -1; 82 83 // Constants that indicate the current connection state 84 public static final int STATE_NONE = 0; // we're doing nothing 85 public static final int STATE_LISTEN = 1; // now listening for incoming connections 86 public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection 87 public static final int STATE_CONNECTED = 3; // now connected to a remote device 88 89 /** 90 * Constructor. Prepares a new BluetoothChat session. 91 * @param context The UI Activity Context 92 * @param handler A Handler to send messages back to the UI Activity 93 */ BluetoothChatService(Context context, Handler handler, UUID uuid)94 public BluetoothChatService(Context context, Handler handler, UUID uuid) { 95 mAdapter = BluetoothAdapter.getDefaultAdapter(); 96 mState = STATE_NONE; 97 mHandler = handler; 98 mUuid = uuid; 99 mBleTransport = false; 100 } 101 102 /** 103 * Constructor. Prepares a new BluetoothChat session. 104 * @param context The UI Activity Context 105 * @param handler A Handler to send messages back to the UI Activity 106 * @param useBle A flag to use the BLE transport 107 */ BluetoothChatService(Context context, Handler handler, boolean useBle)108 public BluetoothChatService(Context context, Handler handler, boolean useBle) { 109 mAdapter = BluetoothAdapter.getDefaultAdapter(); 110 mState = STATE_NONE; 111 mHandler = handler; 112 mUuid = null; 113 mBleTransport = useBle; 114 if (D) Log.d(TAG, "Construct BluetoothChatService: useBle=" + useBle); 115 } 116 117 /** 118 * Set the current state of the chat connection 119 * @param state An integer defining the current connection state 120 */ setState(int state)121 private synchronized void setState(int state) { 122 if (D) Log.d(TAG, "setState() " + mState + " -> " + state); 123 mState = state; 124 125 // Give the new state to the Handler so the UI Activity can update 126 mHandler.obtainMessage(MESSAGE_STATE_CHANGE, state, -1).sendToTarget(); 127 } 128 129 /** 130 * Return the current connection state. */ getState()131 public synchronized int getState() { 132 return mState; 133 } 134 135 /** 136 * Start the chat service. Specifically start AcceptThread to begin a 137 * session in listening (server) mode. Called by the Activity onResume() */ start(boolean secure)138 public synchronized void start(boolean secure) { 139 if (D) Log.d(TAG, "start secure: " + secure + UUID.randomUUID() + " - " + UUID.randomUUID()); 140 141 // Cancel any thread attempting to make a connection 142 if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;} 143 144 // Cancel any thread currently running a connection 145 if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;} 146 147 setState(STATE_LISTEN); 148 149 // Start the thread to listen on a BluetoothServerSocket 150 if (secure && mSecureAcceptThread == null) { 151 mSecureAcceptThread = new AcceptThread(true); 152 mSecureAcceptThread.start(); 153 } 154 if (!secure && mInsecureAcceptThread == null) { 155 mInsecureAcceptThread = new AcceptThread(false); 156 mInsecureAcceptThread.start(); 157 } 158 } 159 160 /** 161 * Return the assigned PSM value. 162 */ getPsm(boolean secure)163 public synchronized int getPsm(boolean secure) { 164 if (secure && mSecureAcceptThread != null) { 165 return mSecureAcceptThread.getPsm(); 166 } 167 else if (!secure && mInsecureAcceptThread != null) { 168 return mInsecureAcceptThread.getPsm(); 169 } 170 Log.e(TAG, "getPsm: Invalid PSM value"); 171 return 0; 172 } 173 174 /** 175 * Return the socket Connection Type. 176 */ getSocketConnectionType()177 public synchronized int getSocketConnectionType() { 178 return mSocketConnectionType; 179 } 180 181 /** 182 * Start the ConnectThread to initiate a connection to a remote device. 183 * @param device The BluetoothDevice to connect to 184 * @param secure Socket Security type - Secure (true) , Insecure (false) 185 */ connect(BluetoothDevice device, boolean secure)186 public synchronized void connect(BluetoothDevice device, boolean secure) { 187 if (!mBleTransport) { 188 connect(device, secure, 0); 189 } else { 190 Log.e(TAG, "connect: Error: LE cannot call this method!"); 191 } 192 } 193 194 /** 195 * Start the ConnectThread to initiate a connection to a remote device. 196 * @param device The BluetoothDevice to connect to 197 * @param secure Socket Security type - Secure (true) , Insecure (false) 198 * @param psm Assigned PSM value 199 */ connect(BluetoothDevice device, boolean secure, int psm)200 public synchronized void connect(BluetoothDevice device, boolean secure, int psm) { 201 if (D) Log.d(TAG, "connect to: " + device + ", psm: " + psm + ", ble: " + mBleTransport); 202 203 // Cancel any thread attempting to make a connection 204 if (mState == STATE_CONNECTING) { 205 if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;} 206 } 207 208 // Cancel any thread currently running a connection 209 if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;} 210 211 // Start the thread to connect with the given device 212 mConnectThread = new ConnectThread(device, secure, psm); 213 mConnectThread.start(); 214 setState(STATE_CONNECTING); 215 } 216 217 /** 218 * Start the ConnectedThread to begin managing a Bluetooth connection 219 * @param socket The BluetoothSocket on which the connection was made 220 * @param device The BluetoothDevice that has been connected 221 */ connected(@onNull BluetoothSocket socket, BluetoothDevice device, final String socketType)222 public synchronized void connected(@NonNull BluetoothSocket socket, BluetoothDevice 223 device, final String socketType) { 224 if (D) { 225 Log.d(TAG, "connected, Socket Type: " + socketType 226 + ", MaxReceivePacketSize: " + socket.getMaxReceivePacketSize() 227 + ", MaxTransmitPacketSize: " + socket.getMaxTransmitPacketSize()); 228 } 229 assertTrue("socket.getMaxReceivePacketSize() expected to be non negative value instead of " 230 + socket.getMaxReceivePacketSize(), socket.getMaxReceivePacketSize() >= 0); 231 assertTrue("socket.getMaxTransmitPacketSize() expected to be non negative value instead of " 232 + socket.getMaxTransmitPacketSize(), socket.getMaxTransmitPacketSize() >= 0); 233 234 // Cancel the thread that completed the connection 235 if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;} 236 237 // Cancel any thread currently running a connection 238 if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;} 239 240 // Cancel the accept thread because we only want to connect to one device 241 if (mSecureAcceptThread != null) { 242 mSecureAcceptThread.cancel(); 243 mSecureAcceptThread = null; 244 } 245 if (mInsecureAcceptThread != null) { 246 mInsecureAcceptThread.cancel(); 247 mInsecureAcceptThread = null; 248 } 249 250 // Start the thread to manage the connection and perform transmissions 251 mConnectedThread = new ConnectedThread(socket, socketType); 252 mConnectedThread.start(); 253 254 // Send the name of the connected device back to the UI Activity 255 Message msg = mHandler.obtainMessage(MESSAGE_DEVICE_NAME); 256 Bundle bundle = new Bundle(); 257 bundle.putString(DEVICE_NAME, device.getName()); 258 msg.setData(bundle); 259 mHandler.sendMessage(msg); 260 261 setState(STATE_CONNECTED); 262 } 263 264 /** 265 * Stop all threads 266 */ stop()267 public synchronized void stop() { 268 if (D) Log.d(TAG, "stop"); 269 270 if (mConnectThread != null) { 271 mConnectThread.cancel(); 272 mConnectThread = null; 273 } 274 275 if (mConnectedThread != null) { 276 mConnectedThread.cancel(); 277 mConnectedThread = null; 278 } 279 280 if (mSecureAcceptThread != null) { 281 mSecureAcceptThread.cancel(); 282 mSecureAcceptThread = null; 283 } 284 285 if (mInsecureAcceptThread != null) { 286 mInsecureAcceptThread.cancel(); 287 mInsecureAcceptThread = null; 288 } 289 setState(STATE_NONE); 290 } 291 292 /** 293 * Write to the ConnectedThread in an unsynchronized manner 294 * @param out The bytes to write 295 * @see ConnectedThread#write(byte[]) 296 */ write(byte[] out)297 public void write(byte[] out) { 298 // Create temporary object 299 ConnectedThread r; 300 // Synchronize a copy of the ConnectedThread 301 synchronized (this) { 302 if (mState != STATE_CONNECTED) return; 303 r = mConnectedThread; 304 } 305 // Perform the write unsynchronized 306 r.write(out); 307 } 308 309 /** 310 * Indicate that the connection attempt failed and notify the UI Activity. 311 */ connectionFailed()312 private void connectionFailed() { 313 // Send a failure message back to the Activity 314 Message msg = mHandler.obtainMessage(MESSAGE_TOAST); 315 Bundle bundle = new Bundle(); 316 bundle.putString(TOAST, "Unable to connect device"); 317 msg.setData(bundle); 318 mHandler.sendMessage(msg); 319 } 320 321 /** 322 * Indicate that the connection was lost and notify the UI Activity. 323 */ connectionLost()324 private void connectionLost() { 325 // Send a failure message back to the Activity 326 Message msg = mHandler.obtainMessage(MESSAGE_TOAST); 327 Bundle bundle = new Bundle(); 328 bundle.putString(TOAST, "Device connection was lost"); 329 msg.setData(bundle); 330 mHandler.sendMessage(msg); 331 } 332 333 /** 334 * This thread runs while listening for incoming connections. It behaves 335 * like a server-side client. It runs until a connection is accepted 336 * (or until cancelled). 337 */ 338 private class AcceptThread extends Thread { 339 // The local server socket 340 private final BluetoothServerSocket mmServerSocket; 341 private String mSocketType; 342 AcceptThread(boolean secure)343 public AcceptThread(boolean secure) { 344 BluetoothServerSocket tmp = null; 345 mSocketType = secure ? "Secure" : "Insecure"; 346 347 // Create a new listening server socket 348 try { 349 if (mBleTransport) { 350 if (secure) { 351 tmp = mAdapter.listenUsingL2capChannel(); 352 } else { 353 tmp = mAdapter.listenUsingInsecureL2capChannel(); 354 } 355 } else { 356 if (secure) { 357 tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME_SECURE, mUuid); 358 } else { 359 tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord(NAME_INSECURE, mUuid); 360 } 361 } 362 } catch (IOException e) { 363 Log.e(TAG, "Socket Type: " + mSocketType + ", le: " + mBleTransport + " listen() failed", e); 364 } 365 mmServerSocket = tmp; 366 if (mBleTransport) { 367 // Get the assigned PSM value 368 mLePsm = mmServerSocket.getPsm(); 369 } 370 } 371 getPsm()372 public int getPsm() { 373 return mLePsm; 374 } 375 run()376 public void run() { 377 if (D) Log.d(TAG, "Socket Type: " + mSocketType + 378 " BEGIN mAcceptThread" + this); 379 setName("AcceptThread" + mSocketType); 380 381 BluetoothSocket socket = null; 382 383 // Listen to the server socket if we're not connected 384 while (mState != STATE_CONNECTED) { 385 try { 386 // This is a blocking call and will only return on a 387 // successful connection or an exception 388 socket = mmServerSocket.accept(); 389 } catch (IOException e) { 390 Log.e(TAG, "Socket Type: " + mSocketType + " accept() failed", e); 391 break; 392 } 393 394 // If a connection was accepted 395 if (socket != null) { 396 synchronized (BluetoothChatService.this) { 397 switch (mState) { 398 case STATE_LISTEN: 399 case STATE_CONNECTING: 400 // Situation normal. Start the connected thread. 401 mSocketConnectionType = socket.getConnectionType(); 402 connected(socket, socket.getRemoteDevice(), 403 mSocketType); 404 break; 405 case STATE_NONE: 406 case STATE_CONNECTED: 407 // Either not ready or already connected. Terminate new socket. 408 try { 409 socket.close(); 410 } catch (IOException e) { 411 Log.e(TAG, "Could not close unwanted socket", e); 412 } 413 break; 414 } 415 } 416 } else { 417 Log.i(TAG, "Got null socket"); 418 } 419 } 420 if (D) { 421 Log.i(TAG, "END mAcceptThread, socket Type: " + mSocketType 422 + ", SocketConnectionType: " + mSocketConnectionType); 423 } 424 } 425 cancel()426 public void cancel() { 427 if (D) Log.d(TAG, "Socket Type" + mSocketType + "cancel " + this); 428 try { 429 mmServerSocket.close(); 430 } catch (IOException e) { 431 Log.e(TAG, "Socket Type" + mSocketType + "close() of server failed", e); 432 } 433 } 434 } 435 436 437 /** 438 * This thread runs while attempting to make an outgoing connection 439 * with a device. It runs straight through; the connection either 440 * succeeds or fails. 441 */ 442 private class ConnectThread extends Thread { 443 private final BluetoothSocket mmSocket; 444 private final BluetoothDevice mmDevice; 445 private String mSocketType; 446 ConnectThread(BluetoothDevice device, boolean secure)447 public ConnectThread(BluetoothDevice device, boolean secure) { 448 if (mBleTransport) { 449 Log.e(TAG, "ConnectThread: Error: LE should not call this constructor"); 450 } 451 mmDevice = device; 452 mmSocket = connectThreadCommon(device, secure, 0); 453 } 454 ConnectThread(BluetoothDevice device, boolean secure, int psm)455 public ConnectThread(BluetoothDevice device, boolean secure, int psm) { 456 mmDevice = device; 457 mmSocket = connectThreadCommon(device, secure, psm); 458 } 459 connectThreadCommon(BluetoothDevice device, boolean secure, int psm)460 private BluetoothSocket connectThreadCommon(BluetoothDevice device, boolean secure, int psm) { 461 BluetoothSocket tmp = null; 462 mSocketType = secure ? "Secure" : "Insecure"; 463 464 // Get a BluetoothSocket for a connection with the 465 // given BluetoothDevice 466 try { 467 if (mBleTransport) { 468 if (secure) { 469 tmp = device.createL2capChannel(psm); 470 } else { 471 tmp = device.createInsecureL2capChannel(psm); 472 } 473 } else { 474 if (secure) { 475 tmp = device.createRfcommSocketToServiceRecord(mUuid); 476 } else { 477 tmp = device.createInsecureRfcommSocketToServiceRecord(mUuid); 478 } 479 } 480 } catch (IOException e) { 481 Log.e(TAG, "Socket Type: " + mSocketType + "create() failed", e); 482 } 483 484 mSocketConnectionType = tmp.getConnectionType(); 485 486 return tmp; 487 } 488 run()489 public void run() { 490 Log.i(TAG, "BEGIN mConnectThread SocketType:" + mSocketType 491 + ", mSocketConnectionType: " + mSocketConnectionType); 492 setName("ConnectThread" + mSocketType); 493 494 // Always cancel discovery because it will slow down a connection 495 mAdapter.cancelDiscovery(); 496 497 // Make a connection to the BluetoothSocket 498 try { 499 // This is a blocking call and will only return on a 500 // successful connection or an exception 501 mmSocket.connect(); 502 } catch (IOException e) { 503 Log.e(TAG, "connect() failed ", e); 504 // Close the socket 505 try { 506 mmSocket.close(); 507 } catch (IOException e2) { 508 Log.e(TAG, "unable to close() " + mSocketType + 509 " socket during connection failure", e2); 510 } 511 connectionFailed(); 512 return; 513 } 514 515 // Reset the ConnectThread because we're done 516 synchronized (BluetoothChatService.this) { 517 mConnectThread = null; 518 } 519 520 // Start the connected thread 521 connected(mmSocket, mmDevice, mSocketType); 522 } 523 cancel()524 public void cancel() { 525 try { 526 mmSocket.close(); 527 } catch (IOException e) { 528 Log.e(TAG, "close() of connect " + mSocketType + " socket failed", e); 529 } 530 } 531 } 532 533 /** 534 * This thread runs during a connection with a remote device. 535 * It handles all incoming and outgoing transmissions. 536 */ 537 private class ConnectedThread extends Thread { 538 private final BluetoothSocket mmSocket; 539 private final InputStream mmInStream; 540 private final OutputStream mmOutStream; 541 ConnectedThread(BluetoothSocket socket, String socketType)542 public ConnectedThread(BluetoothSocket socket, String socketType) { 543 Log.d(TAG, "create ConnectedThread: " + socketType); 544 mmSocket = socket; 545 InputStream tmpIn = null; 546 OutputStream tmpOut = null; 547 548 // Get the BluetoothSocket input and output streams 549 try { 550 tmpIn = socket.getInputStream(); 551 tmpOut = socket.getOutputStream(); 552 } catch (IOException e) { 553 Log.e(TAG, "temp sockets not created", e); 554 } 555 556 mmInStream = tmpIn; 557 mmOutStream = tmpOut; 558 } 559 run()560 public void run() { 561 Log.i(TAG, "BEGIN mConnectedThread"); 562 int bytes; 563 564 // Keep listening to the InputStream while connected 565 while (true) { 566 try { 567 byte[] buffer = new byte[1024]; 568 // Read from the InputStream 569 bytes = mmInStream.read(buffer); 570 571 // Send the obtained bytes to the UI Activity 572 mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer) 573 .sendToTarget(); 574 } catch (IOException e) { 575 Log.e(TAG, "disconnected", e); 576 connectionLost(); 577 break; 578 } 579 } 580 } 581 582 /** 583 * Write to the connected OutStream. 584 * @param buffer The bytes to write 585 */ write(byte[] buffer)586 public void write(byte[] buffer) { 587 try { 588 mmOutStream.write(buffer); 589 mmOutStream.flush(); 590 591 // Share the sent message back to the UI Activity 592 mHandler.obtainMessage(MESSAGE_WRITE, -1, -1, buffer) 593 .sendToTarget(); 594 } catch (IOException e) { 595 Log.e(TAG, "Exception during write", e); 596 } 597 } 598 cancel()599 public void cancel() { 600 try { 601 mmSocket.close(); 602 } catch (IOException e) { 603 Log.e(TAG, "close() of connect socket failed", e); 604 } 605 } 606 } 607 } 608