Add HID to the state machine and add native call backs.

Change-Id: Ib9f3e476d4176bc04e23e7674dc54aa5a6417308
This commit is contained in:
Jaikumar Ganesh
2010-07-19 16:28:27 -07:00
parent c3ee99d9eb
commit de07503a38
6 changed files with 367 additions and 22 deletions

View File

@@ -58,19 +58,24 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
private static final String TAG = "BluetoothDeviceProfileState";
private static final boolean DBG = true; //STOPSHIP - Change to false
// TODO(): Restructure the state machine to make it scalable with regard to profiles.
public static final int CONNECT_HFP_OUTGOING = 1;
public static final int CONNECT_HFP_INCOMING = 2;
public static final int CONNECT_A2DP_OUTGOING = 3;
public static final int CONNECT_A2DP_INCOMING = 4;
public static final int CONNECT_HID_OUTGOING = 5;
public static final int CONNECT_HID_INCOMING = 6;
public static final int DISCONNECT_HFP_OUTGOING = 5;
private static final int DISCONNECT_HFP_INCOMING = 6;
public static final int DISCONNECT_A2DP_OUTGOING = 7;
public static final int DISCONNECT_A2DP_INCOMING = 8;
public static final int DISCONNECT_HFP_OUTGOING = 50;
private static final int DISCONNECT_HFP_INCOMING = 51;
public static final int DISCONNECT_A2DP_OUTGOING = 52;
public static final int DISCONNECT_A2DP_INCOMING = 53;
public static final int DISCONNECT_HID_OUTGOING = 54;
public static final int DISCONNECT_HID_INCOMING = 55;
public static final int UNPAIR = 9;
public static final int AUTO_CONNECT_PROFILES = 10;
public static final int TRANSITION_TO_STABLE = 11;
public static final int UNPAIR = 100;
public static final int AUTO_CONNECT_PROFILES = 101;
public static final int TRANSITION_TO_STABLE = 102;
private static final int AUTO_CONNECT_DELAY = 6000; // 6 secs
@@ -79,6 +84,8 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
private IncomingHandsfree mIncomingHandsfree = new IncomingHandsfree();
private IncomingA2dp mIncomingA2dp = new IncomingA2dp();
private OutgoingA2dp mOutgoingA2dp = new OutgoingA2dp();
private OutgoingHid mOutgoingHid = new OutgoingHid();
private IncomingHid mIncomingHid = new IncomingHid();
private Context mContext;
private BluetoothService mService;
@@ -89,6 +96,7 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
private BluetoothDevice mDevice;
private int mHeadsetState;
private int mA2dpState;
private int mHidState;
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
@@ -125,6 +133,19 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
newState == BluetoothA2dp.STATE_DISCONNECTED) {
sendMessage(TRANSITION_TO_STABLE);
}
} else if (action.equals(BluetoothInputDevice.ACTION_INPUT_DEVICE_STATE_CHANGED)) {
int newState = intent.getIntExtra(BluetoothInputDevice.EXTRA_INPUT_DEVICE_STATE, 0);
int oldState =
intent.getIntExtra(BluetoothInputDevice.EXTRA_PREVIOUS_INPUT_DEVICE_STATE, 0);
mHidState = newState;
if (oldState == BluetoothInputDevice.STATE_CONNECTED &&
newState == BluetoothInputDevice.STATE_DISCONNECTED) {
sendMessage(DISCONNECT_HID_INCOMING);
}
if (newState == BluetoothInputDevice.STATE_CONNECTED ||
newState == BluetoothInputDevice.STATE_DISCONNECTED) {
sendMessage(TRANSITION_TO_STABLE);
}
} else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
if (!getCurrentState().equals(mBondedDevice)) {
Log.e(TAG, "State is: " + getCurrentState());
@@ -165,6 +186,8 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
addState(mIncomingHandsfree);
addState(mIncomingA2dp);
addState(mOutgoingA2dp);
addState(mOutgoingHid);
addState(mIncomingHid);
setInitialState(mBondedDevice);
IntentFilter filter = new IntentFilter();
@@ -172,6 +195,7 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED);
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
filter.addAction(BluetoothInputDevice.ACTION_INPUT_DEVICE_STATE_CHANGED);
mContext.registerReceiver(mBroadcastReceiver, filter);
@@ -224,6 +248,14 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
case DISCONNECT_A2DP_INCOMING:
transitionTo(mIncomingA2dp);
break;
case CONNECT_HID_OUTGOING:
case DISCONNECT_HID_OUTGOING:
transitionTo(mOutgoingHid);
break;
case CONNECT_HID_INCOMING:
case DISCONNECT_HID_INCOMING:
transitionTo(mIncomingHid);
break;
case UNPAIR:
if (mHeadsetState != BluetoothHeadset.STATE_DISCONNECTED) {
sendMessage(DISCONNECT_HFP_OUTGOING);
@@ -233,6 +265,10 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
sendMessage(DISCONNECT_A2DP_OUTGOING);
deferMessage(message);
break;
} else if (mHidState != BluetoothInputDevice.STATE_DISCONNECTED) {
sendMessage(DISCONNECT_HID_OUTGOING);
deferMessage(message);
break;
}
processCommand(UNPAIR);
break;
@@ -254,6 +290,10 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
mA2dpService.getConnectedSinks().length == 0) {
mA2dpService.connectSink(mDevice);
}
if (mService.getInputDevicePriority(mDevice) ==
BluetoothInputDevice.PRIORITY_AUTO_CONNECT) {
mService.connectInputDevice(mDevice);
}
}
break;
case TRANSITION_TO_STABLE:
@@ -342,6 +382,23 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
deferMessage(deferMsg);
}
break;
case CONNECT_HID_OUTGOING:
case DISCONNECT_HID_OUTGOING:
deferMessage(message);
break;
case CONNECT_HID_INCOMING:
transitionTo(mIncomingHid);
if (mStatus) {
deferMsg.what = mCommand;
deferMessage(deferMsg);
}
break;
case DISCONNECT_HID_INCOMING:
if (mStatus) {
deferMsg.what = mCommand;
deferMessage(deferMsg);
}
break; // ignore
case UNPAIR:
case AUTO_CONNECT_PROFILES:
deferMessage(message);
@@ -409,6 +466,13 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
// If this causes incoming HFP to fail, it is more of a headset problem
// since both connections are incoming ones.
break;
case CONNECT_HID_OUTGOING:
case DISCONNECT_HID_OUTGOING:
deferMessage(message);
break;
case CONNECT_HID_INCOMING:
case DISCONNECT_HID_INCOMING:
break; // ignore
case UNPAIR:
case AUTO_CONNECT_PROFILES:
deferMessage(message);
@@ -496,6 +560,23 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
case DISCONNECT_A2DP_INCOMING:
// Ignore, will be handled by Bluez
break;
case CONNECT_HID_OUTGOING:
case DISCONNECT_HID_OUTGOING:
deferMessage(message);
break;
case CONNECT_HID_INCOMING:
transitionTo(mIncomingHid);
if (mStatus) {
deferMsg.what = mCommand;
deferMessage(deferMsg);
}
break;
case DISCONNECT_HID_INCOMING:
if (mStatus) {
deferMsg.what = mCommand;
deferMessage(deferMsg);
}
break; // ignore
case UNPAIR:
case AUTO_CONNECT_PROFILES:
deferMessage(message);
@@ -561,6 +642,13 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
case DISCONNECT_A2DP_INCOMING:
// Ignore, will be handled by Bluez
break;
case CONNECT_HID_OUTGOING:
case DISCONNECT_HID_OUTGOING:
deferMessage(message);
break;
case CONNECT_HID_INCOMING:
case DISCONNECT_HID_INCOMING:
break; // ignore
case UNPAIR:
case AUTO_CONNECT_PROFILES:
deferMessage(message);
@@ -576,6 +664,143 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
}
private class OutgoingHid extends HierarchicalState {
private boolean mStatus = false;
private int mCommand;
@Override
protected void enter() {
log("Entering OutgoingHid state with: " + getCurrentMessage().what);
mCommand = getCurrentMessage().what;
if (mCommand != CONNECT_HID_OUTGOING &&
mCommand != DISCONNECT_HID_OUTGOING) {
Log.e(TAG, "Error: OutgoingHid state with command:" + mCommand);
}
mStatus = processCommand(mCommand);
if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
}
@Override
protected boolean processMessage(Message message) {
log("OutgoingHid State->Processing Message: " + message.what);
Message deferMsg = new Message();
switch(message.what) {
// defer all outgoing messages
case CONNECT_HFP_OUTGOING:
case CONNECT_A2DP_OUTGOING:
case CONNECT_HID_OUTGOING:
case DISCONNECT_HFP_OUTGOING:
case DISCONNECT_A2DP_OUTGOING:
case DISCONNECT_HID_OUTGOING:
deferMessage(message);
break;
case CONNECT_HFP_INCOMING:
transitionTo(mIncomingHandsfree);
case CONNECT_A2DP_INCOMING:
transitionTo(mIncomingA2dp);
// Don't cancel HID outgoing as there is no guarantee it
// will get canceled.
// It might already be connected but we might not have got the
// INPUT_DEVICE_STATE_CHANGE. Hence, no point disconnecting here.
// The worst case, the connection will fail, retry.
if (mStatus) {
deferMsg.what = mCommand;
deferMessage(deferMsg);
}
break;
case CONNECT_HID_INCOMING:
// Bluez will take care of the conflicts
transitionTo(mIncomingHid);
break;
case DISCONNECT_HFP_INCOMING:
case DISCONNECT_A2DP_INCOMING:
// At this point, we are already disconnected
// with HFP. Sometimes HID connection can
// fail due to the disconnection of HFP. So add a retry
// for the HID.
if (mStatus) {
deferMsg.what = mCommand;
deferMessage(deferMsg);
}
break;
case DISCONNECT_HID_INCOMING:
// Ignore, will be handled by Bluez
break;
case UNPAIR:
case AUTO_CONNECT_PROFILES:
deferMessage(message);
break;
case TRANSITION_TO_STABLE:
transitionTo(mBondedDevice);
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
private class IncomingHid extends HierarchicalState {
private boolean mStatus = false;
private int mCommand;
@Override
protected void enter() {
log("Entering IncomingHid state with: " + getCurrentMessage().what);
mCommand = getCurrentMessage().what;
if (mCommand != CONNECT_HID_INCOMING &&
mCommand != DISCONNECT_HID_INCOMING) {
Log.e(TAG, "Error: IncomingHid state with command:" + mCommand);
}
mStatus = processCommand(mCommand);
if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
}
@Override
protected boolean processMessage(Message message) {
log("IncomingHid State->Processing Message: " + message.what);
Message deferMsg = new Message();
switch(message.what) {
case CONNECT_HFP_OUTGOING:
case CONNECT_HFP_INCOMING:
case DISCONNECT_HFP_OUTGOING:
case CONNECT_A2DP_INCOMING:
case CONNECT_A2DP_OUTGOING:
case DISCONNECT_A2DP_OUTGOING:
case CONNECT_HID_OUTGOING:
case CONNECT_HID_INCOMING:
case DISCONNECT_HID_OUTGOING:
deferMessage(message);
break;
case DISCONNECT_HFP_INCOMING:
// Shouldn't happen but if does, we can handle it.
// Depends if the headset can handle it.
// Incoming HID will be handled by Bluez, Disconnect HFP
// the socket would have already been closed.
// ignore
break;
case DISCONNECT_HID_INCOMING:
case DISCONNECT_A2DP_INCOMING:
// Ignore, will be handled by Bluez
break;
case UNPAIR:
case AUTO_CONNECT_PROFILES:
deferMessage(message);
break;
case TRANSITION_TO_STABLE:
transitionTo(mBondedDevice);
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
synchronized void cancelCommand(int command) {
if (command == CONNECT_HFP_OUTGOING ) {
@@ -619,6 +844,10 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
case CONNECT_A2DP_INCOMING:
// ignore, Bluez takes care
return true;
case CONNECT_HID_OUTGOING:
return mService.connectInputDeviceInternal(mDevice);
case CONNECT_HID_INCOMING:
return true;
case DISCONNECT_HFP_OUTGOING:
if (!mHeadsetServiceConnected) {
deferHeadsetMessage(command);
@@ -645,6 +874,15 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
return mA2dpService.disconnectSinkInternal(mDevice);
}
break;
case DISCONNECT_HID_INCOMING:
// ignore
return true;
case DISCONNECT_HID_OUTGOING:
if (mService.getInputDevicePriority(mDevice) ==
BluetoothInputDevice.PRIORITY_AUTO_CONNECT) {
mService.setInputDevicePriority(mDevice, BluetoothInputDevice.PRIORITY_ON);
}
return mService.disconnectInputDeviceInternal(mDevice);
case UNPAIR:
return mService.removeBondInternal(mDevice.getAddress());
default:
@@ -653,6 +891,7 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
return false;
}
/*package*/ BluetoothDevice getDevice() {
return mDevice;
}

View File

@@ -43,8 +43,9 @@ public class BluetoothProfileState extends HierarchicalStateMachine {
private static final boolean DBG = true; // STOPSHIP - change to false.
private static final String TAG = "BluetoothProfileState";
public static int HFP = 0;
public static int A2DP = 1;
public static final int HFP = 0;
public static final int A2DP = 1;
public static final int HID = 2;
private static int TRANSITION_TO_STABLE = 100;
@@ -70,6 +71,12 @@ public class BluetoothProfileState extends HierarchicalStateMachine {
newState == BluetoothA2dp.STATE_DISCONNECTED)) {
sendMessage(TRANSITION_TO_STABLE);
}
} else if (action.equals(BluetoothInputDevice.ACTION_INPUT_DEVICE_STATE_CHANGED)) {
int newState = intent.getIntExtra(BluetoothInputDevice.EXTRA_INPUT_DEVICE_STATE, 0);
if (mProfile == HID && (newState == BluetoothInputDevice.STATE_CONNECTED ||
newState == BluetoothInputDevice.STATE_DISCONNECTED)) {
sendMessage(TRANSITION_TO_STABLE);
}
}
}
};
@@ -84,6 +91,7 @@ public class BluetoothProfileState extends HierarchicalStateMachine {
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED);
filter.addAction(BluetoothInputDevice.ACTION_INPUT_DEVICE_STATE_CHANGED);
context.registerReceiver(mBroadcastReceiver, filter);
}

View File

@@ -693,6 +693,26 @@ class BluetoothEventLoop {
}
}
private void onInputDeviceConnectionResult(String path, boolean result) {
// Success case gets handled by Property Change signal
if (!result) {
String address = mBluetoothService.getAddressFromObjectPath(path);
if (address == null) return;
boolean connected = false;
BluetoothDevice device = mAdapter.getRemoteDevice(address);
int state = mBluetoothService.getInputDeviceState(device);
if (state == BluetoothInputDevice.STATE_CONNECTING) {
connected = false;
} else if (state == BluetoothInputDevice.STATE_DISCONNECTING) {
connected = true;
} else {
Log.e(TAG, "Error onInputDeviceConnectionResult. State is:" + state);
}
mBluetoothService.handleInputDevicePropertyChange(address, connected);
}
}
private void onRestartRequired() {
if (mBluetoothService.isEnabled()) {
Log.e(TAG, "*** A serious error occured (did bluetoothd crash?) - " +

View File

@@ -132,6 +132,7 @@ public class BluetoothService extends IBluetooth.Stub {
private final HashMap<String, BluetoothDeviceProfileState> mDeviceProfileState;
private final BluetoothProfileState mA2dpProfileState;
private final BluetoothProfileState mHfpProfileState;
private final BluetoothProfileState mHidProfileState;
private BluetoothA2dpService mA2dpService;
private final HashMap<BluetoothDevice, Integer> mInputDevices;
@@ -196,9 +197,11 @@ public class BluetoothService extends IBluetooth.Stub {
mDeviceProfileState = new HashMap<String, BluetoothDeviceProfileState>();
mA2dpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.A2DP);
mHfpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HFP);
mHidProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HID);
mHfpProfileState.start();
mA2dpProfileState.start();
mHidProfileState.start();
IntentFilter filter = new IntentFilter();
registerForAirplaneMode(filter);
@@ -1241,13 +1244,27 @@ public class BluetoothService extends IBluetooth.Stub {
getInputDevicePriority(device) == BluetoothInputDevice.PRIORITY_OFF) {
return false;
}
if(connectInputDeviceNative(objectPath)) {
handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_CONNECTING);
BluetoothDeviceProfileState state = mDeviceProfileState.get(device.getAddress());
if (state != null) {
Message msg = new Message();
msg.arg1 = BluetoothDeviceProfileState.CONNECT_HID_OUTGOING;
msg.obj = state;
mHidProfileState.sendMessage(msg);
return true;
}
return false;
}
public synchronized boolean connectInputDeviceInternal(BluetoothDevice device) {
String objectPath = getObjectPathFromAddress(device.getAddress());
handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_CONNECTING);
if (!connectInputDeviceNative(objectPath)) {
handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_DISCONNECTED);
return false;
}
return true;
}
public synchronized boolean disconnectInputDevice(BluetoothDevice device) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
@@ -1256,13 +1273,27 @@ public class BluetoothService extends IBluetooth.Stub {
if (objectPath == null || getConnectedInputDevices().length == 0) {
return false;
}
if(disconnectInputDeviceNative(objectPath)) {
handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_DISCONNECTING);
BluetoothDeviceProfileState state = mDeviceProfileState.get(device.getAddress());
if (state != null) {
Message msg = new Message();
msg.arg1 = BluetoothDeviceProfileState.DISCONNECT_HID_OUTGOING;
msg.obj = state;
mHidProfileState.sendMessage(msg);
return true;
}
return false;
}
public synchronized boolean disconnectInputDeviceInternal(BluetoothDevice device) {
String objectPath = getObjectPathFromAddress(device.getAddress());
handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_DISCONNECTING);
if (!disconnectInputDeviceNative(objectPath)) {
handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_CONNECTED);
return false;
}
return true;
}
public synchronized int getInputDeviceState(BluetoothDevice device) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");