All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.openxc.interfaces.bluetooth.DeviceManager Maven / Gradle / Ivy

The newest version!
package com.openxc.interfaces.bluetooth;

import java.io.IOException;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.os.Looper;
import android.util.Log;

/**
 * The DeviceManager collects the functions required to connect to and open a
 * socket to the Bluetooth device.
 *
 * The device must be previously bonded, as this class does not initiate
 * discovery.
 */
public class DeviceManager {
    private final static String TAG = "DeviceManager";
    private final static UUID RFCOMM_UUID = UUID.fromString(
            "00001101-0000-1000-8000-00805f9b34fb");

    private Context mContext;
    private BluetoothAdapter mBluetoothAdapter;
    private BluetoothDevice mTargetDevice;
    private final Lock mDeviceLock = new ReentrantLock();
    private final Condition mDeviceChangedCondition =
            mDeviceLock.newCondition();
    private BroadcastReceiver mReceiver;
    private BluetoothSocket mSocket;
    private AtomicBoolean mSocketConnecting = new AtomicBoolean(false);

    /**
     * The DeviceManager requires an Android Context in order to send the intent
     * to enable Bluetooth if it isn't already on.
     */
    public DeviceManager(Context context) throws BluetoothException {
        mContext = context;
        // work around an Android bug, requires that this is called before
        // getting the default adapter
        if(Looper.myLooper() == null) {
            Looper.prepare();
        }
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if(mBluetoothAdapter == null) {
            String message = "This device most likely does not have " +
                    "a Bluetooth adapter";
            Log.w(TAG, message);
            throw new BluetoothException(message);
        }
    }

    /**
     * Connect to the target device and open a socket. This method will block
     * while waiting for the device.
     *
     * Returns a socket connected to the device.
     */
    public BluetoothSocket connect(String targetAddress)
            throws BluetoothException {
        connectDevice(targetAddress);
        mDeviceLock.lock();
        try {
            while(mTargetDevice == null) {
                try {
                    mDeviceChangedCondition.await();
                } catch(InterruptedException e) {}
            }
            mSocket = setupSocket(mTargetDevice);
            connectToSocket(mSocket);
        } finally {
            mDeviceLock.unlock();
        }
        return mSocket;
    }

    private void connectToSocket(BluetoothSocket socket) throws BluetoothException {
        mSocketConnecting.set(true);
        try {
            socket.connect();
        } catch(IOException e) {
            String error = "Could not connect socket to SPP service on " +
                mTargetDevice;
            Log.e(TAG, error);
            try {
                socket.close();
            } catch(IOException e2) {}
            throw new BluetoothException(error, e);
        } finally {
            mSocketConnecting.set(false);
        }
    }

    /**
     * Immediately cancel any pending Bluetooth operations.
     *
     * The BluetoothSocket.connect() function blocks while waiting for a
     * connection, but it's thread safe and we can cancel that by calling
     * close() on it at any time.
     *
     * Importantly we don't want to close the socket any other time, becauase we
     * want to leave that up to the user of the socket - if you call close()
     * twice, or close Input/Ouput streams associated with the socket
     * simultaneously, it can cause a segfault due to a bug in some Android
     * Bluetooth stacks. Awesome!
     */
    public void stop() {
        if(mSocketConnecting.get() && mSocket != null) {
            try {
                mSocket.close();
            } catch(IOException e) { }
        }
    }

    /**
     * Open an RFCOMM socket to the connected Bluetooth device.
     *
     * The DeviceManager must already have a device connected, so connectDevice
     * needs to be called.
     */
    private BluetoothSocket setupSocket(BluetoothDevice device)
            throws BluetoothException {
        if(device == null) {
            Log.w(TAG, "Can't setup socket -- device is " + device);
            throw new BluetoothException();
        }

        if(mBluetoothAdapter.isDiscovering()) {
            mBluetoothAdapter.cancelDiscovery();
        }

        Log.d(TAG, "Scanning services on " + device);
        BluetoothSocket socket = null;
        try {
            socket = device.createRfcommSocketToServiceRecord(
                    RFCOMM_UUID);
        } catch(IOException e) {
            String error = "Unable to open a socket to device " + device;
            Log.w(TAG, error);
            throw new BluetoothException(error, e);
        }
        return socket;
    }

    private void captureDevice(BluetoothDevice device) {
        mDeviceLock.lock();
        try {
            mTargetDevice = device;
            mDeviceChangedCondition.signal();
        } finally {
            mDeviceLock.unlock();
        }

        if(mReceiver != null) {
            mContext.unregisterReceiver(mReceiver);
            if(mBluetoothAdapter.isDiscovering()) {
                mBluetoothAdapter.cancelDiscovery();
            }
        }
    }

    /** Check if a Bluetooth device matches the target address we're looking
     * for.
     *
     * @param device candidate Bluetooth device that's been previously bonded
     * @param targetAddress Bluetooth MAC addres we're looking for
     *
     * @return true if the address matches the candidate device
     */
    private boolean checkCandidateDevice(BluetoothDevice device,
            String targetAddress) {
        Log.d(TAG, "Found Bluetooth device: " + device);
        if(device.getAddress().equals(targetAddress)) {
            Log.d(TAG, "Found matching device: " + device);
            return true;
        }
        return false;
    }

    /**
     * Check the list of previously paired devices for one matching the target
     * address. Once a matching device is found, calls captureDevice to connect
     * with it.
     *
     * This will not attempt to pair with unpaired devices - it's assumed that
     * this step has already been completed by the user when selecting the
     * Bluetooth device to use. If this class is used programatically with a
     * hard-coded target address, you'll need to have previously paired the
     * device.
     */
    private void connectDevice(final String targetAddress) {
        Log.d(TAG, "Starting device enumeration");
        Set pairedDevices =
            mBluetoothAdapter.getBondedDevices();
        for(BluetoothDevice device : pairedDevices) {
            Log.d(TAG, "Found already paired device: " + device);
            if(checkCandidateDevice(device, targetAddress)) {
                captureDevice(device);
                return;
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy