/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Helper class to manage the Bluetooth connection
*/
package lu.fisch.canze.bluetooth;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.os.Build;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.security.InvalidParameterException;
import java.util.UUID;
import lu.fisch.canze.activities.MainActivity;
import lu.fisch.canze.interfaces.BluetoothEvent;
/**
* Created by robertfisch on 03.09.2015.
*/
public class BluetoothManager {
/* --------------------------------
* Sigleton stuff
\ ------------------------------ */
private static BluetoothManager bluetoothManager = null;
private InputStream inputStream = null;
private OutputStream outputStream = null;
public static BluetoothManager getInstance()
{
if(bluetoothManager ==null)
bluetoothManager = new BluetoothManager();
return bluetoothManager;
}
/* --------------------------------
* Attributes
\ ------------------------------ */
// SPP UUID service
private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
public static final int STATE_BLUETOOTH_NOT_AVAILABLE = -1;
public static final int STATE_BLUETOOTH_ACTIVE = 1;
public static final int STATE_BLUETOOTH_NOT_ACTIVE = 0;
private BluetoothAdapter bluetoothAdapter = null;
private BluetoothSocket bluetoothSocket = null;
public boolean isDummyMode() {
return dummyMode;
}
private boolean dummyMode = false;
private BluetoothEvent bluetoothEvent;
public static final int RETRIES_NONE = 0;
public static final int RETRIES_INFINITE = -1;
private Thread retryThread = null;
private String connectBluetoothAddress = null;
private boolean connectSecure;
private int connectRetries;
private boolean retry = true;
private void debug(String text)
{
MainActivity.debug(this.getClass().getSimpleName() + ": " + text);
}
/**
* Create a new manager
*/
private BluetoothManager()
{
// get Bluetooth adapter
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
/**
* Determine the state of the Bluetooth hardware
* @return the state of the Bluetooth hardware
*/
public int getHardwareState() {
// Check for Bluetooth support and then check to make sure it is turned on
// Emulator doesn't support Bluetooth and will return null
if (dummyMode) return STATE_BLUETOOTH_ACTIVE;
if(bluetoothAdapter ==null)
{
return STATE_BLUETOOTH_NOT_AVAILABLE;
}
else
{
if (bluetoothAdapter.isEnabled())
{
return STATE_BLUETOOTH_ACTIVE;
}
else
{
return STATE_BLUETOOTH_NOT_ACTIVE;
}
}
}
/**
* Creates a new Bluetooth socket from a given device
* @param device
* @return
* @throws IOException
*/
private BluetoothSocket createBluetoothSocket(BluetoothDevice device, boolean secure) throws IOException {
if(Build.VERSION.SDK_INT >= 10){
try {
if(!secure) {
// insecure connection
final Method m = device.getClass().getMethod("createInsecureRfcommSocketToServiceRecord", new Class[]{UUID.class});
return (BluetoothSocket) m.invoke(device, MY_UUID);
}
else {
// secure connection
final Method m = device.getClass().getMethod("createRfcommSocketToServiceRecord", new Class[]{UUID.class});
return (BluetoothSocket) m.invoke(device, MY_UUID);
}
}
catch (Exception e)
{
debug("Could not create RFComm Connection");
}
}
return device.createRfcommSocketToServiceRecord(MY_UUID);
}
public void connect()
{
if (dummyMode) return;
if(connectBluetoothAddress==null) throw new InvalidParameterException("connect() has to be called at least once with parameters!");
connect(connectBluetoothAddress, connectSecure, connectRetries);
}
public void connect(final String bluetoothAddress, final boolean secure, final int retries) {
if (dummyMode) return;
retry = true;
privateConnect(bluetoothAddress, secure, retries);
}
private void privateConnect(final String bluetoothAddress, final boolean secure, final int retries)
{
if (!(retryThread == null || (retryThread != null && !retryThread.isAlive()))) {
debug("BT: aborting connect (another one is in progress ...)");
return;
}
if(retry) {
// remember parameters
connectBluetoothAddress = bluetoothAddress;
connectSecure = secure;
connectRetries = retries;
// only continue if we got an address
if (bluetoothAddress != null && !bluetoothAddress.isEmpty() && getHardwareState() == STATE_BLUETOOTH_ACTIVE) {
// make sure there is no more active connection
if (bluetoothSocket!=null && bluetoothSocket.isConnected()) {
try {
debug("Closing previous socket");
bluetoothSocket.close();
bluetoothSocket=null;
} catch (Exception e) {
e.printStackTrace();
}
}
// execute attached event
if (bluetoothEvent != null) bluetoothEvent.onBeforeConnect();
// set up a pointer to the remote node using it's address.
debug("Get remote device: " + bluetoothAddress);
BluetoothDevice device = bluetoothAdapter.getRemoteDevice(bluetoothAddress);
// create a socket
try {
debug("Create new socket");
bluetoothSocket = createBluetoothSocket(device, secure);
} catch (IOException e) {
e.printStackTrace();
}
// discovery is resource intensive so make sure it is stopped
debug("Cancel discovery");
bluetoothAdapter.cancelDiscovery();
try {
debug("Connect the socket");
bluetoothSocket.connect();
debug("Connect the streams");
// connect the streams
try {
inputStream = bluetoothSocket.getInputStream();
outputStream = bluetoothSocket.getOutputStream();
}
catch (IOException e) {
inputStream = null;
outputStream = null;
}
// execute attached event
if (bluetoothEvent != null)
bluetoothEvent.onAfterConnect(bluetoothSocket);
debug("Connected");
return;
} catch (IOException e) {
//e.printStackTrace();
}
}
// if we reach this line, something went wrong and no connection has been established
debug("Something went wrong");
if (bluetoothAddress == null || bluetoothAddress.isEmpty())
debug("No device address given");
else if (getHardwareState() == STATE_BLUETOOTH_NOT_ACTIVE)
debug("Bluetooth not active");
if(bluetoothSocket!=null)
try {
debug("Closing socket again ...");
bluetoothSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
debug(retries + " tries left");
if (retries != RETRIES_NONE) {
if (retryThread == null || (retryThread != null && !retryThread.isAlive())) {
if(retryThread!=null)
{
retryThread.interrupt();
}
debug("Starting new try");
retryThread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2 * 1000);
} catch (Exception e) {
}
(new Thread(new Runnable() {
@Override
public void run() {
BluetoothManager.this.getInstance().privateConnect(bluetoothAddress, secure, retries - 1);
}
})).start();
}
});
retryThread.start();
} else {
debug("Another try is still running --> abort this one");
debug("Alive: " + retryThread.isAlive());
}
}
}
}
public void disconnect()
{
if (dummyMode) return;
try {
// execute attached event
if(bluetoothEvent!=null) bluetoothEvent.onBeforeDisconnect(bluetoothSocket);
retry=false;
if(retryThread!=null && retryThread.isAlive()) {
debug("Waiting for retry-thread to stop ...");
retryThread.join();
}
debug("Closing socket");
// close the socket
if (bluetoothSocket != null)
bluetoothSocket.close();
// execute attached event
if(bluetoothEvent!=null) bluetoothEvent.onAfterDisconnect();
debug("Closed");
}
catch (IOException e)
{
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/* --------------------------------
* input / output
\ ------------------------------ */
// write a message to the output stream
public void write(String message) {
if (dummyMode) return;
if(bluetoothSocket.isConnected()) {
byte[] msgBuffer = message.getBytes();
try {
outputStream.write(msgBuffer);
} catch (IOException e) {
Log.d(MainActivity.TAG, "BT: Error sending > " + e.getMessage());
//Log.d(MainActivity.TAG, "BT: Error sending > restaring BT");
/*
(new Thread(new Runnable() {
@Override
public void run() {
disconnect();
connect(connectBluetoothAddress, true, BluetoothManager.RETRIES_INFINITE);
}
})).start();
*/
}
}
else MainActivity.debug("Write failed! Socket is closed ... M = "+message);
}
public int read(byte[] buffer) throws IOException {
if (dummyMode) return 0;
if(bluetoothSocket.isConnected())
return inputStream.read(buffer);
else
return 0;
}
public int read() throws IOException {
if (dummyMode) return -1;
if(bluetoothSocket.isConnected())
return inputStream.read();
else
return -1;
}
public int available() throws IOException {
if (dummyMode) return 0;
if(bluetoothSocket.isConnected())
return inputStream.available();
else
return 0;
}
public boolean isConnected()
{
if (dummyMode) return true;
if(bluetoothSocket==null) return false;
return bluetoothSocket.isConnected();
}
/* --------------------------------
* Events
\ ------------------------------ */
public void setBluetoothEvent(BluetoothEvent bluetoothEvent) {
if (dummyMode) return;
this.bluetoothEvent = bluetoothEvent;
}
public void setDummyMode (boolean dummyMode) {
this.dummyMode = dummyMode;
}
}