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