blob: 1deef1e512df5a5a4fe19d2236b30eef101f7048 [file] [log] [blame]
package com.googlecode.android_scripting.facade.bluetooth;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.app.Service;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothInputDevice;
import android.bluetooth.BluetoothUuid;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.ParcelUuid;
import com.googlecode.android_scripting.Log;
import com.googlecode.android_scripting.facade.EventFacade;
import com.googlecode.android_scripting.facade.FacadeManager;
import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
import com.googlecode.android_scripting.rpc.Rpc;
import com.googlecode.android_scripting.rpc.RpcParameter;
public class BluetoothConnectionFacade extends RpcReceiver {
private final Service mService;
private final BluetoothAdapter mBluetoothAdapter;
private final BluetoothPairingHelper mPairingHelper;
private final Map<String, BroadcastReceiver> listeningDevices;
private final EventFacade mEventFacade;
private final IntentFilter mDiscoverConnectFilter;
private final IntentFilter mPairingFilter;
private final IntentFilter mBondFilter;
private final IntentFilter mA2dpStateChangeFilter;
private final IntentFilter mHidStateChangeFilter;
private final IntentFilter mHspStateChangeFilter;
private final Bundle mGoodNews;
private final Bundle mBadNews;
private BluetoothA2dpFacade mA2dpProfile;
private BluetoothHidFacade mHidProfile;
private BluetoothHspFacade mHspProfile;
public BluetoothConnectionFacade(FacadeManager manager) {
super(manager);
mService = manager.getService();
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mPairingHelper = new BluetoothPairingHelper();
// Use a synchronized map to avoid racing problems
listeningDevices = Collections.synchronizedMap(new HashMap<String, BroadcastReceiver>());
mEventFacade = manager.getReceiver(EventFacade.class);
mA2dpProfile = manager.getReceiver(BluetoothA2dpFacade.class);
mHidProfile = manager.getReceiver(BluetoothHidFacade.class);
mHspProfile = manager.getReceiver(BluetoothHspFacade.class);
mDiscoverConnectFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
mDiscoverConnectFilter.addAction(BluetoothDevice.ACTION_UUID);
mDiscoverConnectFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
mPairingFilter = new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST);
mPairingFilter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
mPairingFilter.setPriority(999);
mBondFilter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
mBondFilter.addAction(BluetoothDevice.ACTION_FOUND);
mBondFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
mA2dpStateChangeFilter = new IntentFilter(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
mHidStateChangeFilter = new IntentFilter(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
mHspStateChangeFilter = new IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
mGoodNews = new Bundle();
mGoodNews.putBoolean("Status", true);
mBadNews = new Bundle();
mBadNews.putBoolean("Status", false);
mService.registerReceiver(mPairingHelper, mPairingFilter);
}
/**
* Connect to a specific device upon its discovery
*/
public class DiscoverConnectReceiver extends BroadcastReceiver {
private final String mDeviceID;
private BluetoothDevice mDevice;
/**
* Constructor
*
* @param deviceID Either the device alias name or mac address.
* @param bond If true, bond the device only.
*/
public DiscoverConnectReceiver(String deviceID) {
super();
mDeviceID = deviceID;
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// The specified device is found.
if (action.equals(BluetoothDevice.ACTION_FOUND)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (BluetoothFacade.deviceMatch(device, mDeviceID)) {
Log.d("Found device " + device.getAliasName() + " for connection.");
mBluetoothAdapter.cancelDiscovery();
mDevice = device;
}
// After discovery stops.
} else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
if (mDevice == null) {
Log.d("Device " + mDeviceID + " not discovered.");
mEventFacade.postEvent("Bond" + mDeviceID, mBadNews);
}
boolean status = mDevice.fetchUuidsWithSdp();
Log.d("Initiated ACL connection: " + status);
} else if (action.equals(BluetoothDevice.ACTION_UUID)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (BluetoothFacade.deviceMatch(device, mDeviceID)) {
Log.d("Initiating connections.");
connectProfile(device, mDeviceID);
mService.unregisterReceiver(listeningDevices.remove("Connect" + mDeviceID));
}
}
}
}
/**
* Connect to a specific device upon its discovery
*/
public class DiscoverBondReceiver extends BroadcastReceiver {
private final String mDeviceID;
private BluetoothDevice mDevice = null;
private boolean started = false;
/**
* Constructor
*
* @param deviceID Either the device alias name or Mac address.
*/
public DiscoverBondReceiver(String deviceID) {
super();
mDeviceID = deviceID;
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// The specified device is found.
if (action.equals(BluetoothDevice.ACTION_FOUND)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (BluetoothFacade.deviceMatch(device, mDeviceID)) {
Log.d("Found device " + device.getAliasName() + " for connection.");
mBluetoothAdapter.cancelDiscovery();
mDevice = device;
}
// After discovery stops.
} else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
if (mDevice == null) {
Log.d("Device " + mDeviceID + " was not discovered.");
mEventFacade.postEvent("Bond", mBadNews);
return;
}
// Attempt to initiate bonding.
if (!started) {
Log.d("Bond with " + mDevice.getAliasName());
if (mDevice.createBond()) {
started = true;
Log.d("Bonding started.");
} else {
Log.e("Failed to bond with " + mDevice.getAliasName());
mEventFacade.postEvent("Bond", mBadNews);
mService.unregisterReceiver(listeningDevices.remove("Bond" + mDeviceID));
}
}
} else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
Log.d("Bond state changing.");
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (BluetoothFacade.deviceMatch(device, mDeviceID)) {
int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
Log.d("New state is " + state);
if (state == BluetoothDevice.BOND_BONDED) {
Log.d("Bonding with " + mDeviceID + " successful.");
mEventFacade.postEvent("Bond" + mDeviceID, mGoodNews);
mService.unregisterReceiver(listeningDevices.remove("Bond" + mDeviceID));
}
}
}
}
}
public class ConnectStateChangeReceiver extends BroadcastReceiver {
private final String mDeviceID;
public ConnectStateChangeReceiver(String deviceID) {
mDeviceID = deviceID;
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// Check if received the specified device
if (!BluetoothFacade.deviceMatch(device, mDeviceID)) {
return;
}
if (action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) {
int state = intent.getIntExtra(BluetoothA2dp.EXTRA_STATE, -1);
if (state == BluetoothA2dp.STATE_CONNECTED) {
Bundle a2dpGoodNews = (Bundle) mGoodNews.clone();
a2dpGoodNews.putString("Type", "a2dp");
mEventFacade.postEvent("A2dpConnect" + mDeviceID, a2dpGoodNews);
mService.unregisterReceiver(listeningDevices.remove("A2dpConnecting"
+ mDeviceID));
} else if (state == BluetoothA2dp.STATE_CONNECTING) {
}
}else if (action.equals(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED)) {
int state = intent.getIntExtra(BluetoothInputDevice.EXTRA_STATE, -1);
if (state == BluetoothInputDevice.STATE_CONNECTED) {
mEventFacade.postEvent("HidConnect" + mDeviceID, mGoodNews);
mService.unregisterReceiver(listeningDevices
.remove("HidConnecting" + mDeviceID));
}
} else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
int state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, -1);
if (state == BluetoothHeadset.STATE_CONNECTED) {
mEventFacade.postEvent("HspConnect" + mDeviceID, mGoodNews);
mService.unregisterReceiver(listeningDevices
.remove("HspConnecting" + mDeviceID));
}
}
}
}
private void connectProfile(BluetoothDevice device, String deviceID) {
mService.registerReceiver(mPairingHelper, mPairingFilter);
ParcelUuid[] deviceUuids = device.getUuids();
Log.d("Device uuid is " + deviceUuids);
if (deviceUuids == null) {
mEventFacade.postEvent("Connect", mBadNews);
}
if (BluetoothUuid.containsAnyUuid(BluetoothA2dpFacade.SINK_UUIDS, deviceUuids)) {
Log.d("Connecting to " + device.getAliasName());
boolean status = mA2dpProfile.a2dpConnect(device);
if (status) {
Log.d("Connecting A2dp...");
ConnectStateChangeReceiver receiver = new ConnectStateChangeReceiver(deviceID);
mService.registerReceiver(receiver, mA2dpStateChangeFilter);
listeningDevices.put("A2dpConnecting" + deviceID, receiver);
} else {
Log.d("Failed starting A2dp connection.");
Bundle a2dpBadNews = (Bundle) mBadNews.clone();
a2dpBadNews.putString("Type", "a2dp");
mEventFacade.postEvent("Connect", a2dpBadNews);
}
}
if (BluetoothUuid.containsAnyUuid(BluetoothHidFacade.UUIDS, deviceUuids)) {
boolean status = mHidProfile.hidConnect(device);
if (status) {
Log.d("Connecting Hid...");
ConnectStateChangeReceiver receiver = new ConnectStateChangeReceiver(deviceID);
mService.registerReceiver(receiver, mHidStateChangeFilter);
listeningDevices.put("HidConnecting" + deviceID, receiver);
} else {
Log.d("Failed starting Hid connection.");
mEventFacade.postEvent("HidConnect", mBadNews);
}
}
if (BluetoothUuid.containsAnyUuid(BluetoothHspFacade.UUIDS, deviceUuids)) {
boolean status = mHspProfile.hspConnect(device);
if (status) {
Log.d("Connecting Hsp...");
ConnectStateChangeReceiver receiver = new ConnectStateChangeReceiver(deviceID);
mService.registerReceiver(receiver, mHspStateChangeFilter);
listeningDevices.put("HspConnecting" + deviceID, receiver);
} else {
Log.d("Failed starting Hsp connection.");
mEventFacade.postEvent("HspConnect", mBadNews);
}
}
mService.unregisterReceiver(mPairingHelper);
}
@Rpc(description = "Return a list of devices connected through bluetooth")
public List<BluetoothDevice> bluetoothGetConnectedDevices() {
ArrayList<BluetoothDevice> results = new ArrayList<BluetoothDevice>();
for (BluetoothDevice bd : mBluetoothAdapter.getBondedDevices()) {
if (bd.isConnected()) {
results.add(bd);
}
}
return results;
}
@Rpc(description = "Return true if a bluetooth device is connected.")
public Boolean bluetoothIsDeviceConnected(String deviceID) {
for (BluetoothDevice bd : mBluetoothAdapter.getBondedDevices()) {
if (BluetoothFacade.deviceMatch(bd, deviceID)) {
return bd.isConnected();
}
}
return false;
}
@Rpc(description = "Connect to a specified device once it's discovered.",
returns = "Whether discovery started successfully.")
public Boolean bluetoothDiscoverAndConnect(
@RpcParameter(name = "deviceID",
description = "Name or MAC address of a bluetooth device.")
String deviceID) {
mBluetoothAdapter.cancelDiscovery();
if (listeningDevices.containsKey(deviceID)) {
Log.d("This device is already in the process of discovery and connecting.");
return true;
}
DiscoverConnectReceiver receiver = new DiscoverConnectReceiver(deviceID);
listeningDevices.put("Connect" + deviceID, receiver);
mService.registerReceiver(receiver, mDiscoverConnectFilter);
return mBluetoothAdapter.startDiscovery();
}
@Rpc(description = "Bond to a specified device once it's discovered.",
returns = "Whether discovery started successfully. ")
public Boolean bluetoothDiscoverAndBond(
@RpcParameter(name = "deviceID",
description = "Name or MAC address of a bluetooth device.")
String deviceID) {
mBluetoothAdapter.cancelDiscovery();
if (listeningDevices.containsKey(deviceID)) {
Log.d("This device is already in the process of discovery and bonding.");
return true;
}
if (BluetoothFacade.deviceExists(mBluetoothAdapter.getBondedDevices(), deviceID)) {
Log.d("Device " + deviceID + " is already bonded.");
mEventFacade.postEvent("Bond" + deviceID, mGoodNews);
return true;
}
DiscoverBondReceiver receiver = new DiscoverBondReceiver(deviceID);
if (listeningDevices.containsKey("Bond" + deviceID)) {
mService.unregisterReceiver(listeningDevices.remove("Bond" + deviceID));
}
listeningDevices.put("Bond" + deviceID, receiver);
mService.registerReceiver(receiver, mBondFilter);
Log.d("Start discovery for bonding.");
return mBluetoothAdapter.startDiscovery();
}
@Rpc(description = "Unbond a device.",
returns = "Whether the device was successfully unbonded.")
public Boolean bluetoothUnbond(
@RpcParameter(name = "deviceID",
description = "Name or MAC address of a bluetooth device.")
String deviceID) throws Exception {
BluetoothDevice mDevice = BluetoothFacade.getDevice(mBluetoothAdapter.getBondedDevices(),
deviceID);
return mDevice.removeBond();
}
@Rpc(description = "Connect to a device that is already bonded.")
public void bluetoothConnectBonded(
@RpcParameter(name = "deviceID",
description = "Name or MAC address of a bluetooth device.")
String deviceID) throws Exception {
BluetoothDevice mDevice = BluetoothFacade.getDevice(mBluetoothAdapter.getBondedDevices(),
deviceID);
mDevice.setTrust(true);
connectProfile(mDevice, deviceID);
}
//
// @Rpc(description = "Register pairing helper.")
// public void bluetoothAutoAccept() {
// mService.registerReceiver(mPairingHelper, mPairingFilter);
// }
@Override
public void shutdown() {
for(BroadcastReceiver receiver : listeningDevices.values()) {
mService.unregisterReceiver(receiver);
}
listeningDevices.clear();
mService.unregisterReceiver(mPairingHelper);
}
}