/* * Copyright (C) 2010 Google Inc. * * 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.google.android.apps.mytracks.services.sensors; 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; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; 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.util.UUID; /** * 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 { // My Tracks UUID public static final UUID MY_TRACKS_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); // Message types sent to hander public static final int MESSAGE_DEVICE_NAME = 1; public static final int MESSAGE_READ = 2; // Key for storing the device name public static final String KEY_DEVICE_NAME = "device_name"; private static final String TAG = BluetoothConnectionManager.class.getSimpleName(); private final BluetoothAdapter bluetoothAdapter; private final Handler handler; private final MessageParser messageParser; private SensorState sensorState; private ConnectThread connectThread; private ConnectedThread connectedThread; /** * Constructor. * * @param bluetoothAdapter the bluetooth adapter * @param handler a hander for sending messages back to the UI activity * @param messageParser a message parser */ public BluetoothConnectionManager( BluetoothAdapter bluetoothAdapter, Handler handler, MessageParser messageParser) { this.bluetoothAdapter = bluetoothAdapter; this.handler = handler; this.messageParser = messageParser; this.sensorState = SensorState.NONE; } /** * Gets the sensor state. */ public synchronized SensorState getSensorState() { return sensorState; } /** * Sets the sensor state. * * @param sensorState the sensor state */ private synchronized void setState(Sensor.SensorState sensorState) { this.sensorState = sensorState; } /** * Resets the bluetooth connection manager. */ public synchronized void reset() { cancelThreads(); setState(Sensor.SensorState.NONE); } /** * Cancels all the threads. */ private void cancelThreads() { if (connectThread != null) { connectThread.cancel(); connectThread = null; } if (connectedThread != null) { connectedThread.cancel(); connectedThread = null; } } /** * 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); } /** * Starts the ConnectedThread to read data. * * @param bluetoothSocket the bluetooth socket * @param bluetoothDevice the bluetooth device */ private synchronized void connected( BluetoothSocket bluetoothSocket, BluetoothDevice bluetoothDevice) { cancelThreads(); connectedThread = new ConnectedThread(bluetoothSocket); connectedThread.start(); // Send the device name to the handler Message message = handler.obtainMessage(MESSAGE_DEVICE_NAME); Bundle bundle = new Bundle(); bundle.putString(KEY_DEVICE_NAME, bluetoothDevice.getName()); message.setData(bundle); handler.sendMessage(message); setState(Sensor.SensorState.CONNECTED); } /** * A thread to connect to a bluetooth device. */ private class ConnectThread extends Thread { private final BluetoothSocket bluetoothSocket; private final BluetoothDevice bluetoothDevice; public ConnectThread(BluetoothDevice device) { setName("ConnectThread-" + device.getName()); this.bluetoothDevice = device; BluetoothSocket tmp = null; try { tmp = ApiAdapterFactory.getApiAdapter().getBluetoothSocket(device); } catch (IOException e) { Log.e(TAG, "Unable to get blueooth socket.", e); } bluetoothSocket = tmp; } @Override public void run() { if (bluetoothAdapter == null) { BluetoothConnectionManager.this.reset(); return; } // Cancel discovery to prevent slow down bluetoothAdapter.cancelDiscovery(); try { bluetoothSocket.connect(); } catch (IOException connectException) { Log.i(TAG, "Unable to connect.", connectException); setState(Sensor.SensorState.DISCONNECTED); try { bluetoothSocket.close(); } catch (IOException e) { Log.e(TAG, "Unable to close blueooth socket.", e); } // Reset the bluetooth connection manager BluetoothConnectionManager.this.reset(); return; } // Reset the ConnectThread since we are done synchronized (BluetoothConnectionManager.this) { connectThread = null; } // Start the connected thread connected(bluetoothSocket, bluetoothDevice); } /** * Cancels this thread. */ public void cancel() { try { bluetoothSocket.close(); } catch (IOException e) { Log.e(TAG, "Unable to close bluetooth socket.", e); } } } /** * This thread handles data transmission when connected. */ private class ConnectedThread extends Thread { private final BluetoothSocket bluetoothSSocket; private final InputStream inputStream; public ConnectedThread(BluetoothSocket bluetoothSocket) { this.bluetoothSSocket = bluetoothSocket; InputStream tmp = null; try { tmp = bluetoothSocket.getInputStream(); } catch (IOException e) { Log.e(TAG, "Unable to get input stream.", e); } inputStream = tmp; } @Override public void run() { byte[] buffer = new byte[messageParser.getFrameSize()]; int bytes; // bytes read int offset = 0; // Keep listening to the inputStream while connected while (true) { try { // Read from the inputStream bytes = inputStream.read(buffer, offset, messageParser.getFrameSize() - offset); if (bytes == -1) { throw new IOException("EOF reached."); } offset += bytes; if (offset != messageParser.getFrameSize()) { // Partial frame received. Call read again to receive the rest. continue; } 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(TAG, "Misaligned data. Found new message at " + index + ". Recovering..."); offset = messageParser.getFrameSize() - index; System.arraycopy(buffer, index, buffer, 0, offset); continue; } offset = 0; // 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.i(TAG, "Bluetooth connection lost.", e); setState(Sensor.SensorState.DISCONNECTED); break; } } } /** * Cancels this thread. */ public void cancel() { try { bluetoothSSocket.close(); } catch (IOException e) { Log.e(TAG, "Unable to close bluetooth socket.", e); } } } }