From 62a716cfc747d41368bba2911a90c46720219131 Mon Sep 17 00:00:00 2001 From: Jimmy Shih Date: Tue, 29 May 2012 15:22:48 -0700 Subject: [PATCH] Clean up BluetoothConnectionManager. --- .../sensors/BluetoothConnectionManager.java | 374 ++++++------------ .../sensors/BluetoothSensorManager.java | 24 +- .../apps/mytracks/util/Api10Adapter.java | 4 +- .../apps/mytracks/util/Api7Adapter.java | 2 +- 4 files changed, 141 insertions(+), 263 deletions(-) diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java index 558bfe423..76b29623e 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java @@ -16,8 +16,8 @@ package com.google.android.apps.mytracks.services.sensors; -import com.google.android.apps.mytracks.Constants; import com.google.android.apps.mytracks.content.Sensor; +import com.google.android.apps.mytracks.content.Sensor.SensorState; import com.google.android.apps.mytracks.util.ApiAdapterFactory; import android.bluetooth.BluetoothAdapter; @@ -30,382 +30,262 @@ 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. - * + * Manages bluetooth connection. It has a thread for connecting with a bluetooth + * device and a thread for performing data transmission when connected. + * * @author Sandor Dornbush */ public class BluetoothConnectionManager { - // Unique Bluetooth UUID for My Tracks - public static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); + // My Tracks UUID + public static final UUID MY_TRACKS_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); - private MessageParser parser; + // Message types sent to hander + public static final int MESSAGE_STATE_CHANGE = 1; + public static final int MESSAGE_DEVICE_NAME = 2; + public static final int MESSAGE_READ = 3; + + // Key for storing the device name + public static final String KEY_DEVICE_NAME = "device_name"; + + private static final String TAG = BluetoothConnectionManager.class.getSimpleName(); - // Member fields - private final BluetoothAdapter adapter; private final Handler handler; + private final MessageParser messageParser; + private final BluetoothAdapter bluetoothAdapter; + private SensorState sensorState; + private ConnectThread connectThread; private ConnectedThread connectedThread; - private Sensor.SensorState state; - - // Message types sent from the BluetoothSenorService 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; - - // Key names received from the BluetoothSenorService Handler - public static final String DEVICE_NAME = "device_name"; - /** - * Constructor. Prepares a new BluetoothSensor session. - * - * @param handler A Handler to send messages back to the UI Activity - * @param parser A message parser + * Constructor. + * + * @param handler a hander for sending messages back to the UI activity + * @param messageParser a message parser */ - public BluetoothConnectionManager(Handler handler, MessageParser parser) { - this.adapter = BluetoothAdapter.getDefaultAdapter(); - this.state = Sensor.SensorState.NONE; + public BluetoothConnectionManager(Handler handler, MessageParser messageParser) { this.handler = handler; - this.parser = parser; + this.messageParser = messageParser; + this.bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + this.sensorState = SensorState.NONE; } /** - * Set the current state of the sensor connection - * - * @param state An integer defining the current connection state + * Gets the sensor state. */ - private synchronized void setState(Sensor.SensorState state) { - // TODO pretty print this. - Log.d(Constants.TAG, "setState(" + state + ")"); - this.state = state; - - // Give the new state to the Handler so the UI Activity can update - handler.obtainMessage(MESSAGE_STATE_CHANGE, state.getNumber(), -1).sendToTarget(); + public synchronized SensorState getSensorState() { + return sensorState; } /** - * Return the current connection state. + * Sets the sensor state. + * + * @param sensorState the sensor state */ - public synchronized Sensor.SensorState getState() { - return state; + private synchronized void setState(Sensor.SensorState sensorState) { + this.sensorState = sensorState; + + // Send the sensor state to the handler + handler.obtainMessage(MESSAGE_STATE_CHANGE, sensorState.getNumber(), -1).sendToTarget(); } /** - * Start the sensor service. Specifically start AcceptThread to begin a session - * in listening (server) mode. Called by the Activity onResume() + * Resets the bluetooth connection manager. */ - public synchronized void start() { - Log.d(Constants.TAG, "BluetoothConnectionManager.start()"); - - // Cancel any thread attempting to make a connection - if (connectThread != null) { - connectThread.cancel(); - connectThread = null; - } - - // Cancel any thread currently running a connection - if (connectedThread != null) { - connectedThread.cancel(); - connectedThread = null; - } - + public synchronized void reset() { + cancelThreads(); setState(Sensor.SensorState.NONE); } /** - * Start the ConnectThread to initiate a connection to a remote device. - * - * @param device The BluetoothDevice to connect + * Cancels all the threads. */ - public synchronized void connect(BluetoothDevice device) { - Log.d(Constants.TAG, "connect to: " + device); - - // Cancel any thread attempting to make a connection - if (state == Sensor.SensorState.CONNECTING) { - if (connectThread != null) { - connectThread.cancel(); - connectThread = null; - } + private void cancelThreads() { + if (connectThread != null) { + connectThread.cancel(); + connectThread = null; } - - // Cancel any thread currently running a connection if (connectedThread != null) { connectedThread.cancel(); connectedThread = null; } + } - // Start the thread to connect with the given device - connectThread = new ConnectThread(device); + /** + * Connects to a bluetooth device. + * + * @param bluetoothDevice the bluetooth device + */ + public synchronized void connect(BluetoothDevice bluetoothDevice) { + Log.d(TAG, "connect to: " + bluetoothDevice); + cancelThreads(); + + connectThread = new ConnectThread(bluetoothDevice); connectThread.start(); setState(Sensor.SensorState.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 + * Starts the ConnectedThread to read data. + * + * @param bluetoothSocket the bluetooth socket + * @param bluetoothDevice the bluetooth device */ - public synchronized void connected(BluetoothSocket socket, - BluetoothDevice device) { - Log.d(Constants.TAG, "connected"); + private synchronized void connected( + BluetoothSocket bluetoothSocket, BluetoothDevice bluetoothDevice) { + cancelThreads(); - // Cancel the thread that completed the connection - if (connectThread != null) { - connectThread.cancel(); - connectThread = null; - } - - // Cancel any thread currently running a connection - if (connectedThread != null) { - connectedThread.cancel(); - connectedThread = null; - } - - // Start the thread to manage the connection and perform transmissions - connectedThread = new ConnectedThread(socket); + connectedThread = new ConnectedThread(bluetoothSocket); connectedThread.start(); - // Send the name of the connected device back to the UI Activity - Message msg = handler.obtainMessage(MESSAGE_DEVICE_NAME); + // Send the device name to the handler + Message message = handler.obtainMessage(MESSAGE_DEVICE_NAME); Bundle bundle = new Bundle(); - bundle.putString(DEVICE_NAME, device.getName()); - msg.setData(bundle); - handler.sendMessage(msg); + bundle.putString(KEY_DEVICE_NAME, bluetoothDevice.getName()); + message.setData(bundle); + handler.sendMessage(message); setState(Sensor.SensorState.CONNECTED); } /** - * Stop all threads - */ - public synchronized void stop() { - Log.d(Constants.TAG, "stop()"); - if (connectThread != null) { - connectThread.cancel(); - connectThread = null; - } - if (connectedThread != null) { - connectedThread.cancel(); - connectedThread = null; - } - setState(Sensor.SensorState.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 (state != Sensor.SensorState.CONNECTED) { - return; - } - r = connectedThread; - } - // Perform the write unsynchronized - r.write(out); - } - - /** - * Indicate that the connection attempt failed and notify the UI Activity. - */ - private void connectionFailed() { - setState(Sensor.SensorState.DISCONNECTED); - Log.i(Constants.TAG, "Bluetooth connection failed."); - } - - /** - * Indicate that the connection was lost and notify the UI Activity. - */ - private void connectionLost() { - setState(Sensor.SensorState.DISCONNECTED); - Log.i(Constants.TAG, "Bluetooth connection lost."); - } - - /** - * This thread runs while attempting to make an outgoing connection with a - * device. It runs straight through; the connection either succeeds or fails. + * A thread to connect to a bluetooth device. */ private class ConnectThread extends Thread { - private final BluetoothSocket socket; - private final BluetoothDevice device; + private final BluetoothSocket bluetoothSocket; + private final BluetoothDevice bluetoothDevice; public ConnectThread(BluetoothDevice device) { setName("ConnectThread-" + device.getName()); - this.device = device; + this.bluetoothDevice = device; BluetoothSocket tmp = null; - - // Get a BluetoothSocket for a connection with the - // given BluetoothDevice try { tmp = ApiAdapterFactory.getApiAdapter().getBluetoothSocket(device); } catch (IOException e) { - Log.e(Constants.TAG, "create() failed", e); + Log.e(TAG, "Unable to get blueooth socket.", e); } - socket = tmp; + bluetoothSocket = tmp; } @Override public void run() { - Log.d(Constants.TAG, "BEGIN mConnectThread"); + // Cancel discovery to prevent slow down + bluetoothAdapter.cancelDiscovery(); - // Always cancel discovery because it will slow down a connection - adapter.cancelDiscovery(); - - // Make a connection to the BluetoothSocket try { - // This is a blocking call and will only return on a - // successful connection or an exception - socket.connect(); - } catch (IOException e) { - connectionFailed(); - // Close the socket + bluetoothSocket.connect(); + } catch (IOException connectException) { + Log.i(TAG, "Unable to connect.", connectException); + setState(Sensor.SensorState.DISCONNECTED); try { - socket.close(); - } catch (IOException e2) { - Log.e(Constants.TAG, - "unable to close() socket during connection failure", e2); + bluetoothSocket.close(); + } catch (IOException e) { + Log.e(TAG, "Unable to close blueooth socket.", e); } - // Start the service over to restart listening mode - BluetoothConnectionManager.this.start(); + // Reset the bluetooth connection manager + BluetoothConnectionManager.this.reset(); return; } - // Reset the ConnectThread because we're done + // Reset the ConnectThread since we are done synchronized (BluetoothConnectionManager.this) { connectThread = null; } // Start the connected thread - connected(socket, device); + connected(bluetoothSocket, bluetoothDevice); } + /** + * Cancels this thread. + */ public void cancel() { try { - socket.close(); + bluetoothSocket.close(); } catch (IOException e) { - Log.e(Constants.TAG, "close() of connect socket failed", e); + Log.e(TAG, "Unable to close bluetooth socket.", e); } } } /** - * This thread runs during a connection with a remote device. It handles all - * incoming and outgoing transmissions. + * This thread handles data transmission when connected. */ private class ConnectedThread extends Thread { - private final BluetoothSocket btSocket; - private final InputStream mmInStream; - private final OutputStream mmOutStream; + private final BluetoothSocket bluetoothSSocket; + private final InputStream inputStream; - public ConnectedThread(BluetoothSocket socket) { - Log.d(Constants.TAG, "create ConnectedThread"); - btSocket = socket; - InputStream tmpIn = null; - OutputStream tmpOut = null; + public ConnectedThread(BluetoothSocket bluetoothSocket) { + this.bluetoothSSocket = bluetoothSocket; + InputStream tmp = null; - // Get the BluetoothSocket input and output streams try { - tmpIn = socket.getInputStream(); - tmpOut = socket.getOutputStream(); + tmp = bluetoothSocket.getInputStream(); } catch (IOException e) { - Log.e(Constants.TAG, "temp sockets not created", e); + Log.e(TAG, "Unable to get input stream.", e); } - - mmInStream = tmpIn; - mmOutStream = tmpOut; + inputStream = tmp; } @Override public void run() { - Log.i(Constants.TAG, "BEGIN mConnectedThread"); - byte[] buffer = new byte[parser.getFrameSize()]; - int bytes; + byte[] buffer = new byte[messageParser.getFrameSize()]; + int bytes; // bytes read int offset = 0; - // Keep listening to the InputStream while connected + // Keep listening to the inputStream while connected while (true) { try { - // Read from the InputStream - bytes = mmInStream.read(buffer, offset, parser.getFrameSize() - offset); + // Read from the inputStream + bytes = inputStream.read(buffer, offset, messageParser.getFrameSize() - offset); - if (bytes < 0) { - throw new IOException("EOF reached"); - } + if (bytes == -1) { throw new IOException("EOF reached."); } offset += bytes; - if (offset != parser.getFrameSize()) { - // partial frame received, call read() again to receive the rest + if (offset != messageParser.getFrameSize()) { + // Partial frame received. Call read again to receive the rest. continue; } - // check if its a valid frame - if (!parser.isValid(buffer)) { - int index = parser.findNextAlignment(buffer); - if (index > 0) { - // re-align - offset = parser.getFrameSize() - index; - System.arraycopy(buffer, index, buffer, 0, offset); - Log.w(Constants.TAG, "Misaligned data, found new message at " + - index + " recovering..."); + if (!messageParser.isValid(buffer)) { + int index = messageParser.findNextAlignment(buffer); + if (index == -1) { + Log.w(TAG, "Could not find any valid data. Drop data."); + offset = 0; continue; } - Log.w(Constants.TAG, "Could not find valid data, dropping data"); - offset = 0; + Log.w(TAG, "Misaligned data. Found new message at " + index + ". Recovering..."); + offset = messageParser.getFrameSize() - index; + System.arraycopy(buffer, index, buffer, 0, offset); continue; } offset = 0; - // Send copy of the obtained bytes to the UI Activity. - // Avoids memory inconsistency issues. - handler.obtainMessage(MESSAGE_READ, bytes, -1, buffer.clone()) - .sendToTarget(); + // Send a copy of the obtained bytes to the handler to avoid memory + // inconsistency issues + handler.obtainMessage(MESSAGE_READ, bytes, -1, buffer.clone()).sendToTarget(); } catch (IOException e) { - Log.e(Constants.TAG, "disconnected", e); - connectionLost(); + Log.i(TAG, "Bluetooth connection lost.", e); + setState(Sensor.SensorState.DISCONNECTED); break; } } } /** - * Write to the connected OutStream. - * - * @param buffer The bytes to write + * Cancels this thread. */ - public void write(byte[] buffer) { - try { - mmOutStream.write(buffer); - - // Share the sent message back to the UI Activity - handler.obtainMessage(MESSAGE_WRITE, -1, -1, buffer).sendToTarget(); - } catch (IOException e) { - Log.e(Constants.TAG, "Exception during write", e); - } - } - public void cancel() { try { - btSocket.close(); + bluetoothSSocket.close(); } catch (IOException e) { - Log.e(Constants.TAG, "close() of connect socket failed", e); + Log.e(TAG, "Unable to close bluetooth socket.", e); } } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothSensorManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothSensorManager.java index 99b0b4fb2..19ba46c24 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothSensorManager.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothSensorManager.java @@ -154,10 +154,10 @@ public class BluetoothSensorManager extends SensorManager { if (connectionManager != null) { // Only if the state is STATE_NONE, do we know that we haven't started // already - if (connectionManager.getState() == Sensor.SensorState.NONE) { - // Start the Bluetooth sensor services + if (connectionManager.getSensorState() == Sensor.SensorState.NONE) { + // Reset the connection manager Log.w(Constants.TAG, "Disabled manager onStartTrack"); - connectionManager.start(); + connectionManager.reset(); } } } @@ -166,7 +166,7 @@ public class BluetoothSensorManager extends SensorManager { protected void tearDownChannel() { // Stop the Bluetooth sensor services if (connectionManager != null) { - connectionManager.stop(); + connectionManager.reset(); } } @@ -177,7 +177,7 @@ public class BluetoothSensorManager extends SensorManager { @Override public SensorState getSensorState() { - return connectionManager == null ? Sensor.SensorState.NONE : connectionManager.getState(); + return connectionManager == null ? Sensor.SensorState.NONE : connectionManager.getSensorState(); } // The Handler that gets information back from the BluetoothSensorService @@ -189,7 +189,12 @@ public class BluetoothSensorManager extends SensorManager { // TODO should we update the SensorManager state var? Log.i(Constants.TAG, "MESSAGE_STATE_CHANGE: " + msg.arg1); break; - case BluetoothConnectionManager.MESSAGE_WRITE: + case BluetoothConnectionManager.MESSAGE_DEVICE_NAME: + // Save the connected device name + connectedDeviceName = msg.getData().getString(BluetoothConnectionManager.KEY_DEVICE_NAME); + Toast.makeText(context, + context.getString(R.string.settings_sensor_bluetooth_connected, connectedDeviceName), + Toast.LENGTH_SHORT).show(); break; case BluetoothConnectionManager.MESSAGE_READ: byte[] readBuf = null; @@ -207,13 +212,6 @@ public class BluetoothSensorManager extends SensorManager { Log.i(Constants.TAG, "Unexpected exception on read.", re); } break; - case BluetoothConnectionManager.MESSAGE_DEVICE_NAME: - // Save the connected device name - connectedDeviceName = msg.getData().getString(BluetoothConnectionManager.DEVICE_NAME); - Toast.makeText(context, - context.getString(R.string.settings_sensor_bluetooth_connected, connectedDeviceName), - Toast.LENGTH_SHORT).show(); - break; } } }; diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/Api10Adapter.java b/MyTracks/src/com/google/android/apps/mytracks/util/Api10Adapter.java index b346a802e..9a8d16ae4 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/util/Api10Adapter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/util/Api10Adapter.java @@ -37,10 +37,10 @@ public class Api10Adapter extends Api9Adapter { public BluetoothSocket getBluetoothSocket(BluetoothDevice bluetoothDevice) throws IOException { try { return bluetoothDevice.createInsecureRfcommSocketToServiceRecord( - BluetoothConnectionManager.SPP_UUID); + BluetoothConnectionManager.MY_TRACKS_UUID); } catch (IOException e) { Log.d(Constants.TAG, "Unable to create insecure connection", e); } - return bluetoothDevice.createRfcommSocketToServiceRecord(BluetoothConnectionManager.SPP_UUID); + return bluetoothDevice.createRfcommSocketToServiceRecord(BluetoothConnectionManager.MY_TRACKS_UUID); }; } diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/Api7Adapter.java b/MyTracks/src/com/google/android/apps/mytracks/util/Api7Adapter.java index 78233be64..13b9963ee 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/util/Api7Adapter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/util/Api7Adapter.java @@ -104,7 +104,7 @@ public class Api7Adapter implements ApiAdapter { } catch (InvocationTargetException e) { Log.d(Constants.TAG, "Unable to create insecure connection", e); } - return bluetoothDevice.createRfcommSocketToServiceRecord(BluetoothConnectionManager.SPP_UUID); + return bluetoothDevice.createRfcommSocketToServiceRecord(BluetoothConnectionManager.MY_TRACKS_UUID); } @Override