| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.cts.verifier.bluetooth; |
| |
| import android.bluetooth.BluetoothAdapter; |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.BluetoothServerSocket; |
| import android.bluetooth.BluetoothSocket; |
| import android.content.Context; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.util.Log; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.util.UUID; |
| |
| /** |
| * This class does all the work for setting up and managing Bluetooth |
| * connections with other devices. It has a thread that listens for |
| * incoming connections, a thread for connecting with a device, and a |
| * thread for performing data transmissions when connected. |
| */ |
| public class BluetoothChatService { |
| // Message types sent from the BluetoothChatService Handler |
| public static final int MESSAGE_STATE_CHANGE = 1; |
| public static final int MESSAGE_READ = 2; |
| public static final int MESSAGE_WRITE = 3; |
| public static final int MESSAGE_DEVICE_NAME = 4; |
| public static final int MESSAGE_TOAST = 5; |
| |
| // Key names received from the BluetoothChatService Handler |
| public static final String DEVICE_NAME = "device_name"; |
| public static final String TOAST = "toast"; |
| |
| static final UUID SECURE_UUID = |
| UUID.fromString("8591d757-18ee-45e1-9b12-92875d06ba23"); |
| static final UUID INSECURE_UUID = |
| UUID.fromString("301c214f-91a2-43bf-a795-09d1198a81a7"); |
| static final UUID HANDSFREE_INSECURE_UUID = |
| UUID.fromString("0000111F-0000-1000-8000-00805F9B34FB"); |
| |
| // Debugging |
| private static final String TAG = "CtsBluetoothChatService"; |
| private static final boolean D = true; |
| |
| // Name for the SDP record when creating server socket |
| private static final String NAME_SECURE = "CtsBluetoothChatSecure"; |
| private static final String NAME_INSECURE = "CtsBluetoothChatInsecure"; |
| |
| // Member fields |
| private final BluetoothAdapter mAdapter; |
| private final Handler mHandler; |
| private final UUID mUuid; |
| private AcceptThread mSecureAcceptThread; |
| private AcceptThread mInsecureAcceptThread; |
| private ConnectThread mConnectThread; |
| private ConnectedThread mConnectedThread; |
| private int mState; |
| |
| // Constants that indicate the current connection state |
| public static final int STATE_NONE = 0; // we're doing nothing |
| public static final int STATE_LISTEN = 1; // now listening for incoming connections |
| public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection |
| public static final int STATE_CONNECTED = 3; // now connected to a remote device |
| |
| /** |
| * Constructor. Prepares a new BluetoothChat session. |
| * @param context The UI Activity Context |
| * @param handler A Handler to send messages back to the UI Activity |
| */ |
| public BluetoothChatService(Context context, Handler handler, UUID uuid) { |
| mAdapter = BluetoothAdapter.getDefaultAdapter(); |
| mState = STATE_NONE; |
| mHandler = handler; |
| mUuid = uuid; |
| } |
| |
| /** |
| * Set the current state of the chat connection |
| * @param state An integer defining the current connection state |
| */ |
| private synchronized void setState(int state) { |
| if (D) Log.d(TAG, "setState() " + mState + " -> " + state); |
| mState = state; |
| |
| // Give the new state to the Handler so the UI Activity can update |
| mHandler.obtainMessage(MESSAGE_STATE_CHANGE, state, -1).sendToTarget(); |
| } |
| |
| /** |
| * Return the current connection state. */ |
| public synchronized int getState() { |
| return mState; |
| } |
| |
| /** |
| * Start the chat service. Specifically start AcceptThread to begin a |
| * session in listening (server) mode. Called by the Activity onResume() */ |
| public synchronized void start(boolean secure) { |
| if (D) Log.d(TAG, "start secure: " + secure + UUID.randomUUID() + " - " + UUID.randomUUID()); |
| |
| // Cancel any thread attempting to make a connection |
| if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;} |
| |
| // Cancel any thread currently running a connection |
| if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;} |
| |
| setState(STATE_LISTEN); |
| |
| // Start the thread to listen on a BluetoothServerSocket |
| if (secure && mSecureAcceptThread == null) { |
| mSecureAcceptThread = new AcceptThread(true); |
| mSecureAcceptThread.start(); |
| } |
| if (!secure && mInsecureAcceptThread == null) { |
| mInsecureAcceptThread = new AcceptThread(false); |
| mInsecureAcceptThread.start(); |
| } |
| } |
| |
| /** |
| * Start the ConnectThread to initiate a connection to a remote device. |
| * @param device The BluetoothDevice to connect |
| * @param secure Socket Security type - Secure (true) , Insecure (false) |
| */ |
| public synchronized void connect(BluetoothDevice device, boolean secure) { |
| if (D) Log.d(TAG, "connect to: " + device); |
| |
| // Cancel any thread attempting to make a connection |
| if (mState == STATE_CONNECTING) { |
| if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;} |
| } |
| |
| // Cancel any thread currently running a connection |
| if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;} |
| |
| // Start the thread to connect with the given device |
| mConnectThread = new ConnectThread(device, secure); |
| mConnectThread.start(); |
| setState(STATE_CONNECTING); |
| } |
| |
| /** |
| * Start the ConnectedThread to begin managing a Bluetooth connection |
| * @param socket The BluetoothSocket on which the connection was made |
| * @param device The BluetoothDevice that has been connected |
| */ |
| public synchronized void connected(BluetoothSocket socket, BluetoothDevice |
| device, final String socketType) { |
| if (D) Log.d(TAG, "connected, Socket Type: " + socketType); |
| |
| // Cancel the thread that completed the connection |
| if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;} |
| |
| // Cancel any thread currently running a connection |
| if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;} |
| |
| // Cancel the accept thread because we only want to connect to one device |
| if (mSecureAcceptThread != null) { |
| mSecureAcceptThread.cancel(); |
| mSecureAcceptThread = null; |
| } |
| if (mInsecureAcceptThread != null) { |
| mInsecureAcceptThread.cancel(); |
| mInsecureAcceptThread = null; |
| } |
| |
| // Start the thread to manage the connection and perform transmissions |
| mConnectedThread = new ConnectedThread(socket, socketType); |
| mConnectedThread.start(); |
| |
| // Send the name of the connected device back to the UI Activity |
| Message msg = mHandler.obtainMessage(MESSAGE_DEVICE_NAME); |
| Bundle bundle = new Bundle(); |
| bundle.putString(DEVICE_NAME, device.getName()); |
| msg.setData(bundle); |
| mHandler.sendMessage(msg); |
| |
| setState(STATE_CONNECTED); |
| } |
| |
| /** |
| * Stop all threads |
| */ |
| public synchronized void stop() { |
| if (D) Log.d(TAG, "stop"); |
| |
| if (mConnectThread != null) { |
| mConnectThread.cancel(); |
| mConnectThread = null; |
| } |
| |
| if (mConnectedThread != null) { |
| mConnectedThread.cancel(); |
| mConnectedThread = null; |
| } |
| |
| if (mSecureAcceptThread != null) { |
| mSecureAcceptThread.cancel(); |
| mSecureAcceptThread = null; |
| } |
| |
| if (mInsecureAcceptThread != null) { |
| mInsecureAcceptThread.cancel(); |
| mInsecureAcceptThread = null; |
| } |
| setState(STATE_NONE); |
| } |
| |
| /** |
| * Write to the ConnectedThread in an unsynchronized manner |
| * @param out The bytes to write |
| * @see ConnectedThread#write(byte[]) |
| */ |
| public void write(byte[] out) { |
| // Create temporary object |
| ConnectedThread r; |
| // Synchronize a copy of the ConnectedThread |
| synchronized (this) { |
| if (mState != STATE_CONNECTED) return; |
| r = mConnectedThread; |
| } |
| // Perform the write unsynchronized |
| r.write(out); |
| } |
| |
| /** |
| * Indicate that the connection attempt failed and notify the UI Activity. |
| */ |
| private void connectionFailed() { |
| // Send a failure message back to the Activity |
| Message msg = mHandler.obtainMessage(MESSAGE_TOAST); |
| Bundle bundle = new Bundle(); |
| bundle.putString(TOAST, "Unable to connect device"); |
| msg.setData(bundle); |
| mHandler.sendMessage(msg); |
| } |
| |
| /** |
| * Indicate that the connection was lost and notify the UI Activity. |
| */ |
| private void connectionLost() { |
| // Send a failure message back to the Activity |
| Message msg = mHandler.obtainMessage(MESSAGE_TOAST); |
| Bundle bundle = new Bundle(); |
| bundle.putString(TOAST, "Device connection was lost"); |
| msg.setData(bundle); |
| mHandler.sendMessage(msg); |
| } |
| |
| /** |
| * This thread runs while listening for incoming connections. It behaves |
| * like a server-side client. It runs until a connection is accepted |
| * (or until cancelled). |
| */ |
| private class AcceptThread extends Thread { |
| // The local server socket |
| private final BluetoothServerSocket mmServerSocket; |
| private String mSocketType; |
| |
| public AcceptThread(boolean secure) { |
| BluetoothServerSocket tmp = null; |
| mSocketType = secure ? "Secure" : "Insecure"; |
| |
| // Create a new listening server socket |
| try { |
| if (secure) { |
| tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME_SECURE, mUuid); |
| } else { |
| tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord(NAME_INSECURE, mUuid); |
| } |
| } catch (IOException e) { |
| Log.e(TAG, "Socket Type: " + mSocketType + " listen() failed", e); |
| } |
| mmServerSocket = tmp; |
| } |
| |
| public void run() { |
| if (D) Log.d(TAG, "Socket Type: " + mSocketType + |
| " BEGIN mAcceptThread" + this); |
| setName("AcceptThread" + mSocketType); |
| |
| BluetoothSocket socket = null; |
| |
| // Listen to the server socket if we're not connected |
| while (mState != STATE_CONNECTED) { |
| try { |
| // This is a blocking call and will only return on a |
| // successful connection or an exception |
| socket = mmServerSocket.accept(); |
| } catch (IOException e) { |
| Log.e(TAG, "Socket Type: " + mSocketType + " accept() failed", e); |
| break; |
| } |
| |
| // If a connection was accepted |
| if (socket != null) { |
| synchronized (BluetoothChatService.this) { |
| switch (mState) { |
| case STATE_LISTEN: |
| case STATE_CONNECTING: |
| // Situation normal. Start the connected thread. |
| connected(socket, socket.getRemoteDevice(), |
| mSocketType); |
| break; |
| case STATE_NONE: |
| case STATE_CONNECTED: |
| // Either not ready or already connected. Terminate new socket. |
| try { |
| socket.close(); |
| } catch (IOException e) { |
| Log.e(TAG, "Could not close unwanted socket", e); |
| } |
| break; |
| } |
| } |
| } else { |
| Log.i(TAG, "Got null socket"); |
| } |
| } |
| if (D) Log.i(TAG, "END mAcceptThread, socket Type: " + mSocketType); |
| |
| } |
| |
| public void cancel() { |
| if (D) Log.d(TAG, "Socket Type" + mSocketType + "cancel " + this); |
| try { |
| mmServerSocket.close(); |
| } catch (IOException e) { |
| Log.e(TAG, "Socket Type" + mSocketType + "close() of server failed", e); |
| } |
| } |
| } |
| |
| |
| /** |
| * This thread runs while attempting to make an outgoing connection |
| * with a device. It runs straight through; the connection either |
| * succeeds or fails. |
| */ |
| private class ConnectThread extends Thread { |
| private final BluetoothSocket mmSocket; |
| private final BluetoothDevice mmDevice; |
| private String mSocketType; |
| |
| public ConnectThread(BluetoothDevice device, boolean secure) { |
| mmDevice = device; |
| BluetoothSocket tmp = null; |
| mSocketType = secure ? "Secure" : "Insecure"; |
| |
| // Get a BluetoothSocket for a connection with the |
| // given BluetoothDevice |
| try { |
| if (secure) { |
| tmp = device.createRfcommSocketToServiceRecord(mUuid); |
| } else { |
| tmp = device.createInsecureRfcommSocketToServiceRecord(mUuid); |
| } |
| } catch (IOException e) { |
| Log.e(TAG, "Socket Type: " + mSocketType + "create() failed", e); |
| } |
| mmSocket = tmp; |
| } |
| |
| public void run() { |
| Log.i(TAG, "BEGIN mConnectThread SocketType:" + mSocketType); |
| setName("ConnectThread" + mSocketType); |
| |
| // Always cancel discovery because it will slow down a connection |
| mAdapter.cancelDiscovery(); |
| |
| // Make a connection to the BluetoothSocket |
| try { |
| // This is a blocking call and will only return on a |
| // successful connection or an exception |
| mmSocket.connect(); |
| } catch (IOException e) { |
| Log.e(TAG, "connect() failed ", e); |
| // Close the socket |
| try { |
| mmSocket.close(); |
| } catch (IOException e2) { |
| Log.e(TAG, "unable to close() " + mSocketType + |
| " socket during connection failure", e2); |
| } |
| connectionFailed(); |
| return; |
| } |
| |
| // Reset the ConnectThread because we're done |
| synchronized (BluetoothChatService.this) { |
| mConnectThread = null; |
| } |
| |
| // Start the connected thread |
| connected(mmSocket, mmDevice, mSocketType); |
| } |
| |
| public void cancel() { |
| try { |
| mmSocket.close(); |
| } catch (IOException e) { |
| Log.e(TAG, "close() of connect " + mSocketType + " socket failed", e); |
| } |
| } |
| } |
| |
| /** |
| * This thread runs during a connection with a remote device. |
| * It handles all incoming and outgoing transmissions. |
| */ |
| private class ConnectedThread extends Thread { |
| private final BluetoothSocket mmSocket; |
| private final InputStream mmInStream; |
| private final OutputStream mmOutStream; |
| |
| public ConnectedThread(BluetoothSocket socket, String socketType) { |
| Log.d(TAG, "create ConnectedThread: " + socketType); |
| mmSocket = socket; |
| InputStream tmpIn = null; |
| OutputStream tmpOut = null; |
| |
| // Get the BluetoothSocket input and output streams |
| try { |
| tmpIn = socket.getInputStream(); |
| tmpOut = socket.getOutputStream(); |
| } catch (IOException e) { |
| Log.e(TAG, "temp sockets not created", e); |
| } |
| |
| mmInStream = tmpIn; |
| mmOutStream = tmpOut; |
| } |
| |
| public void run() { |
| Log.i(TAG, "BEGIN mConnectedThread"); |
| byte[] buffer = new byte[1024]; |
| int bytes; |
| |
| // Keep listening to the InputStream while connected |
| while (true) { |
| try { |
| // Read from the InputStream |
| bytes = mmInStream.read(buffer); |
| |
| // Send the obtained bytes to the UI Activity |
| mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer) |
| .sendToTarget(); |
| } catch (IOException e) { |
| Log.e(TAG, "disconnected", e); |
| connectionLost(); |
| break; |
| } |
| } |
| } |
| |
| /** |
| * Write to the connected OutStream. |
| * @param buffer The bytes to write |
| */ |
| public void write(byte[] buffer) { |
| try { |
| mmOutStream.write(buffer); |
| mmOutStream.flush(); |
| |
| // Share the sent message back to the UI Activity |
| mHandler.obtainMessage(MESSAGE_WRITE, -1, -1, buffer) |
| .sendToTarget(); |
| } catch (IOException e) { |
| Log.e(TAG, "Exception during write", e); |
| } |
| } |
| |
| public void cancel() { |
| try { |
| mmSocket.close(); |
| } catch (IOException e) { |
| Log.e(TAG, "close() of connect socket failed", e); |
| } |
| } |
| } |
| } |