package com.dappervision.wearscript.managers;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.AsyncTask;
import android.os.ParcelUuid;
import android.util.Base64;
import com.dappervision.wearscript.BackgroundService;
import com.dappervision.wearscript.Log;
import com.dappervision.wearscript.events.BluetoothBondEvent;
import com.dappervision.wearscript.events.BluetoothModeEvent;
import com.dappervision.wearscript.events.BluetoothWriteEvent;
import com.dappervision.wearscript.events.CallbackRegistration;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
public class BluetoothManager extends Manager {
public static final String LIST = "LIST";
public static final String READ = "READ:";
public static final String DISCOVERY_START = "DISCOVERY_START";
public static final String DISCOVERY_STOP = "DISCOVERY_STOP";
TreeMap<String, BluetoothSocket> mSockets;
TreeMap<String, BluetoothDevice> mDevices;
TreeMap<String, String> mDevicePins;
static String TAG = "BluetoothManager";
private BluetoothAdapter mBluetoothAdapter;
private boolean isSetup;
// Create a BroadcastReceiver for ACTION_FOUND
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// When discovery finds a device
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// Get the BluetoothDevice object from the Intent
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// Add the name and address to an array adapter to show in a ListView
Log.d(TAG, "Discovery: " + device.getAddress());
mDevices.put(device.getAddress(), device);
JSONObject deviceJS = new JSONObject();
deviceJS.put("name", device.getName());
deviceJS.put("address", device.getAddress());
makeCall(DISCOVERY_START, "'" + deviceJS.toJSONString() + "'");
}
}
};
private BroadcastReceiver mReceiverRequiresPin = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int pairingVariant = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, 0);
Log.d(TAG, "Received pairing request: " + pairingVariant);
try {
BluetoothDevice newDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Class<?> btDeviceInstance = Class.forName(BluetoothDevice.class.getCanonicalName());
Method convert = btDeviceInstance.getMethod("convertPinToBytes", String.class);
String pinString = mDevicePins.get(newDevice.getAddress());
if (pinString == null || pinString.equals("")) {
return;
}
byte[] pin = (byte[]) convert.invoke(newDevice, pinString);
Method setPin = btDeviceInstance.getMethod("setPin", byte[].class);
boolean success = (Boolean) setPin.invoke(newDevice, pin);
} catch (Exception e) {
Log.e(TAG, "Couldn't set pin");
}
}
};
public BluetoothManager(BackgroundService bs) {
super(bs);
isSetup = false;
mSockets = new TreeMap<String, BluetoothSocket>();
reset();
if (mBluetoothAdapter != null)
mBluetoothAdapter.cancelDiscovery();
// Register the BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
bs.registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy
final String actionPinRequested = "android.bluetooth.device.action.PAIRING_REQUEST";
IntentFilter intentFilterPinRequested = new IntentFilter(actionPinRequested);
bs.registerReceiver(mReceiverRequiresPin, intentFilterPinRequested);
}
public void shutdown() {
super.shutdown();
service.unregisterReceiver(mReceiver);
service.unregisterReceiver(mReceiverRequiresPin);
}
public void reset() {
super.reset();
mDevices = new TreeMap<String, BluetoothDevice>();
mDevicePins = new TreeMap<String, String>();
for (BluetoothSocket sock : mSockets.values()) {
try {
sock.close();
} catch (IOException e) {
// TODO(brandyn): Add
}
}
mSockets = new TreeMap<String, BluetoothSocket>();
}
private class BluetoothReadTask extends AsyncTask<String, Void, Void> {
@Override
protected Void doInBackground(String... addresses) {
String address = addresses[0];
String read = READ + address;
byte data[] = new byte[1];
// TODO(brandyn): Ensure that these tasks are getting shutdown on reset
while (true) {
if (!mSockets.containsKey(address)) {
try {
Thread.sleep(250);
} catch (InterruptedException e) {
// TODO(brandyn): Handle
}
}
try {
// TODO(brandyn): Bug here if you access with an unpaired address
data[0] = (byte)mSockets.get(address).getInputStream().read();
makeCall(read, "'" + Base64.encodeToString(data, Base64.NO_WRAP) + "'");
} catch (IOException e) {
Log.w(TAG, "Could not read");
closeDevice(address);
return null;
}
}
}
}
public void closeDevice(String address) {
try {
mSockets.get(address).close();
} catch (IOException e1) {
Log.w(TAG, "Could not close");
}
mSockets.remove(address);
}
public void setup() {
if (isSetup) {
log();
return;
}
isSetup = true;
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
log();
if (mBluetoothAdapter == null) {
// Device does not support Bluetooth
Log.e(TAG, "Bluetooth not available");
return;
}
if (!mBluetoothAdapter.isEnabled()) {
Log.w(TAG, "BT not enabled");
}
}
public void log() {
if (mBluetoothAdapter != null)
Log.d(TAG, String.format("Bluetooth: Mode: %d State: %d Discovering: %s Enabled: %s", mBluetoothAdapter.getScanMode(), mBluetoothAdapter.getState(), mBluetoothAdapter.isDiscovering(), mBluetoothAdapter.isEnabled()));
}
protected void setupCallback(CallbackRegistration e) {
super.setupCallback(e);
setup();
if (e.getEvent() == LIST) {
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
// If there are paired devices
JSONArray devices = new JSONArray();
if (pairedDevices.size() > 0) {
// Loop through paired devices
for (BluetoothDevice device : pairedDevices) {
JSONObject deviceJS = new JSONObject();
deviceJS.put("name", device.getName());
deviceJS.put("address", device.getAddress());
devices.add(deviceJS);
}
}
String devicesJS = devices.toJSONString();
Log.d(TAG, devicesJS);
makeCall(LIST, "'" + devicesJS + "'");
unregisterCallback(LIST);
} else if (e.getEvent() == DISCOVERY_START) {
mBluetoothAdapter.startDiscovery();
} else if (e.getEvent().startsWith(READ)) {
String address = e.getEvent().substring(READ.length());
if (!mSockets.containsKey(address)) {
pairWithDevice(address);
}
BluetoothReadTask task = new BluetoothReadTask();
task.execute(address);
}
}
public void onEventAsync(BluetoothModeEvent e) {
setup();
if (mBluetoothAdapter == null)
return;
if (e.isEnable()) {
mBluetoothAdapter.enable();
} else {
mBluetoothAdapter.disable();
}
}
public BluetoothDevice findBondedDevice(String address) {
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
// If there are paired devices
if (pairedDevices.size() > 0) {
// Loop through paired devices
for (BluetoothDevice device : pairedDevices) {
if (device.getAddress().equals(address)) {
return device;
}
}
}
return null;
}
public void pairWithDevice(String address) {
mBluetoothAdapter.cancelDiscovery();
BluetoothDevice device = findBondedDevice(address);
if (device == null) {
Log.e(TAG, "Could find bonded device: " + address);
return;
}
device.fetchUuidsWithSdp();
if (device.getUuids() == null) {
Log.e(TAG, "No uuids");
return;
}
// TODO(brandyn): Use intent
for (ParcelUuid uid: device.getUuids()) {
Log.d(TAG, "Uuid: " + uid.toString());
}
ParcelUuid uuid = device.getUuids()[0];
Log.d(TAG, "UUID: " + uuid.toString());
Log.d(TAG, "Trying to connect");
try {
mSockets.put(device.getAddress(), device.createInsecureRfcommSocketToServiceRecord(uuid.getUuid()));
} catch (IOException e2) {
Log.e(TAG, "Cannot create rfcom");
return;
}
try {
mSockets.get(device.getAddress()).connect();
} catch (IOException e3) {
mSockets.remove(device.getAddress());
Log.e(TAG, "Cannot connect");
return;
}
Log.d(TAG, "Connected");
}
public void onEventAsync(BluetoothBondEvent e) {
setup();
BluetoothDevice device = (BluetoothDevice)mDevices.get(e.getAddress());
if (device == null) {
Log.w(TAG, "Bond device not found: " + e.getAddress());
return;
}
Log.d(TAG, "Bond device found: " + e.getAddress());
mBluetoothAdapter.cancelDiscovery();
mDevicePins.put(e.getAddress(), e.getPin());
if (device.createBond()) {
Log.d(TAG, "Creating bond");
} else {
Log.w(TAG, "Not creating bond");
}
}
public void onEventAsync(BluetoothWriteEvent e) {
setup();
Log.d(TAG, "Addr: " + e.getAddress() + " Buffer: " + new String(e.getBuffer()));
if (!mSockets.containsKey(e.getAddress()))
pairWithDevice(e.getAddress());
if (!mSockets.containsKey(e.getAddress())) {
Log.e(TAG, "Not paired");
return;
}
Log.d(TAG, "Size:" + mSockets.size());
try {
mSockets.get(e.getAddress()).getOutputStream().write(Base64.decode(e.getBuffer(), Base64.NO_WRAP));
} catch (IOException e1) {
closeDevice(e.getAddress());
Log.e(TAG, "Cannot write");
}
}
}