Loading core/java/android/bluetooth/BluetoothDeviceProfileState.java +246 −7 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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; Loading @@ -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 Loading Loading @@ -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()); Loading Loading @@ -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(); Loading @@ -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); Loading Loading @@ -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); Loading @@ -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; Loading @@ -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: Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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); Loading @@ -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 ) { Loading Loading @@ -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); Loading @@ -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: Loading @@ -653,6 +891,7 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine return false; } /*package*/ BluetoothDevice getDevice() { return mDevice; } Loading core/java/android/bluetooth/BluetoothProfileState.java +10 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); } } } }; Loading @@ -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); } Loading core/java/android/server/BluetoothEventLoop.java +20 −0 Original line number Diff line number Diff line Loading @@ -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?) - " + Loading core/java/android/server/BluetoothService.java +35 −4 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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"); Loading @@ -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"); Loading core/jni/android_server_BluetoothEventLoop.cpp +29 −1 Original line number Diff line number Diff line Loading @@ -65,6 +65,7 @@ static jmethodID method_onAgentAuthorize; static jmethodID method_onAgentCancel; static jmethodID method_onInputDevicePropertyChanged; static jmethodID method_onInputDeviceConnectionResult; typedef event_loop_native_data_t native_data_t; Loading Loading @@ -120,7 +121,8 @@ static void classInitNative(JNIEnv* env, jclass clazz) { "(Ljava/lang/String;II)V"); method_onInputDevicePropertyChanged = env->GetMethodID(clazz, "onInputDevicePropertyChanged", "(Ljava/lang/String;[Ljava/lang/String;)V"); method_onInputDeviceConnectionResult = env->GetMethodID(clazz, "onInputDeviceConnectionResult", "(Ljava/lang/String;Z)V"); field_mNativeData = env->GetFieldID(clazz, "mNativeData", "I"); #endif Loading Loading @@ -1245,6 +1247,32 @@ done: env->DeleteLocalRef(addr); free(user); } void onInputDeviceConnectionResult(DBusMessage *msg, void *user, void *n) { LOGV(__FUNCTION__); native_data_t *nat = (native_data_t *)n; const char *path = (const char *)user; DBusError err; dbus_error_init(&err); JNIEnv *env; nat->vm->GetEnv((void**)&env, nat->envVer); bool result = JNI_TRUE; if (dbus_set_error_from_message(&err, msg)) { LOG_AND_FREE_DBUS_ERROR(&err); result = JNI_FALSE; } LOGV("... Device Path = %s, result = %d", path, result); jstring jPath = env->NewStringUTF(path); env->CallVoidMethod(nat->me, method_onInputDeviceConnectionResult, jPath, result); env->DeleteLocalRef(jPath); free(user); } #endif static JNINativeMethod sMethods[] = { Loading Loading
core/java/android/bluetooth/BluetoothDeviceProfileState.java +246 −7 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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; Loading @@ -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 Loading Loading @@ -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()); Loading Loading @@ -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(); Loading @@ -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); Loading Loading @@ -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); Loading @@ -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; Loading @@ -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: Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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); Loading @@ -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 ) { Loading Loading @@ -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); Loading @@ -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: Loading @@ -653,6 +891,7 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine return false; } /*package*/ BluetoothDevice getDevice() { return mDevice; } Loading
core/java/android/bluetooth/BluetoothProfileState.java +10 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); } } } }; Loading @@ -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); } Loading
core/java/android/server/BluetoothEventLoop.java +20 −0 Original line number Diff line number Diff line Loading @@ -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?) - " + Loading
core/java/android/server/BluetoothService.java +35 −4 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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"); Loading @@ -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"); Loading
core/jni/android_server_BluetoothEventLoop.cpp +29 −1 Original line number Diff line number Diff line Loading @@ -65,6 +65,7 @@ static jmethodID method_onAgentAuthorize; static jmethodID method_onAgentCancel; static jmethodID method_onInputDevicePropertyChanged; static jmethodID method_onInputDeviceConnectionResult; typedef event_loop_native_data_t native_data_t; Loading Loading @@ -120,7 +121,8 @@ static void classInitNative(JNIEnv* env, jclass clazz) { "(Ljava/lang/String;II)V"); method_onInputDevicePropertyChanged = env->GetMethodID(clazz, "onInputDevicePropertyChanged", "(Ljava/lang/String;[Ljava/lang/String;)V"); method_onInputDeviceConnectionResult = env->GetMethodID(clazz, "onInputDeviceConnectionResult", "(Ljava/lang/String;Z)V"); field_mNativeData = env->GetFieldID(clazz, "mNativeData", "I"); #endif Loading Loading @@ -1245,6 +1247,32 @@ done: env->DeleteLocalRef(addr); free(user); } void onInputDeviceConnectionResult(DBusMessage *msg, void *user, void *n) { LOGV(__FUNCTION__); native_data_t *nat = (native_data_t *)n; const char *path = (const char *)user; DBusError err; dbus_error_init(&err); JNIEnv *env; nat->vm->GetEnv((void**)&env, nat->envVer); bool result = JNI_TRUE; if (dbus_set_error_from_message(&err, msg)) { LOG_AND_FREE_DBUS_ERROR(&err); result = JNI_FALSE; } LOGV("... Device Path = %s, result = %d", path, result); jstring jPath = env->NewStringUTF(path); env->CallVoidMethod(nat->me, method_onInputDeviceConnectionResult, jPath, result); env->DeleteLocalRef(jPath); free(user); } #endif static JNINativeMethod sMethods[] = { Loading