Loading src/com/android/bluetooth/hfpclient/HeadsetClientService.java +33 −13 Original line number Original line Diff line number Diff line Loading @@ -646,6 +646,24 @@ public class HeadsetClientService extends ProfileService { boolean acceptCall(BluetoothDevice device, int flag) { boolean acceptCall(BluetoothDevice device, int flag) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); /* Phonecalls from a single device are supported, hang up any calls on the other phone */ synchronized (this) { for (Map.Entry<BluetoothDevice, HeadsetClientStateMachine> entry : mStateMachineMap.entrySet()) { if (entry.getValue() == null) { continue; } int connectionState = entry.getValue().getConnectionState(entry.getKey()); if (DBG) { Log.d(TAG, "Accepting a call on device " + device + ". Possibly disconnecting on " + entry.getValue()); } if (connectionState == BluetoothProfile.STATE_CONNECTED) entry.getValue() .obtainMessage(HeadsetClientStateMachine.TERMINATE_CALL) .sendToTarget(); } } HeadsetClientStateMachine sm = getStateMachine(device); HeadsetClientStateMachine sm = getStateMachine(device); if (sm == null) { if (sm == null) { Log.e(TAG, "Cannot allocate SM for device " + device); Log.e(TAG, "Cannot allocate SM for device " + device); Loading @@ -653,8 +671,7 @@ public class HeadsetClientService extends ProfileService { } } int connectionState = sm.getConnectionState(device); int connectionState = sm.getConnectionState(device); if (connectionState != BluetoothProfile.STATE_CONNECTED && if (connectionState != BluetoothProfile.STATE_CONNECTED) { connectionState != BluetoothProfile.STATE_CONNECTING) { return false; return false; } } Message msg = sm.obtainMessage(HeadsetClientStateMachine.ACCEPT_CALL); Message msg = sm.obtainMessage(HeadsetClientStateMachine.ACCEPT_CALL); Loading Loading @@ -872,20 +889,23 @@ public class HeadsetClientService extends ProfileService { return sm; return sm; } } // Check if any of the state machines are currently holding the SCO audio stream // Check if any of the state machines have routed the SCO audio stream. // This function is *only* called from the SMs which are themselves run the same thread and synchronized boolean isScoRouted() { // hence we do not need synchronization here for (Map.Entry<BluetoothDevice, HeadsetClientStateMachine> entry : boolean isScoAvailable() { mStateMachineMap.entrySet()) { for (BluetoothDevice bd : mStateMachineMap.keySet()) { if (entry.getValue() != null) { HeadsetClientStateMachine sm = mStateMachineMap.get(bd); int audioState = entry.getValue().getAudioState(entry.getKey()); int audioState = sm.getAudioState(bd); if (audioState == BluetoothHeadsetClient.STATE_AUDIO_CONNECTED) { if (audioState != BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED) { if (DBG) { Log.w(TAG, "Device " + bd + " audio state " + audioState + " not disconnected"); Log.d(TAG, "Device " + entry.getKey() + " audio state " + audioState return false; + " Connected"); } } } return true; return true; } } } } return false; } @Override @Override public synchronized void dump(StringBuilder sb) { public synchronized void dump(StringBuilder sb) { Loading src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java +23 −6 Original line number Original line Diff line number Diff line Loading @@ -104,6 +104,8 @@ public class HeadsetClientStateMachine extends StateMachine { // Timeouts. // Timeouts. static final int CONNECTING_TIMEOUT_MS = 10000; // 10s static final int CONNECTING_TIMEOUT_MS = 10000; // 10s static final int ROUTING_DELAY_MS = 250; static final int SCO_DISCONNECT_TIMEOUT_MS = 750; static final int MAX_HFP_SCO_VOICE_CALL_VOLUME = 15; // HFP 1.5 spec. static final int MAX_HFP_SCO_VOICE_CALL_VOLUME = 15; // HFP 1.5 spec. static final int MIN_HFP_SCO_VOICE_CALL_VOLUME = 1; // HFP 1.5 spec. static final int MIN_HFP_SCO_VOICE_CALL_VOLUME = 1; // HFP 1.5 spec. Loading Loading @@ -460,6 +462,8 @@ public class HeadsetClientStateMachine extends StateMachine { action = HeadsetClientHalConstants.CALL_ACTION_CHLD_1; action = HeadsetClientHalConstants.CALL_ACTION_CHLD_1; } else if (getCall(BluetoothHeadsetClientCall.CALL_STATE_ACTIVE) != null) { } else if (getCall(BluetoothHeadsetClientCall.CALL_STATE_ACTIVE) != null) { action = HeadsetClientHalConstants.CALL_ACTION_CHLD_3; action = HeadsetClientHalConstants.CALL_ACTION_CHLD_3; } else if (flag == BluetoothHeadsetClient.CALL_ACCEPT_NONE) { action = HeadsetClientHalConstants.CALL_ACTION_CHLD_2; } else { } else { action = HeadsetClientHalConstants.CALL_ACTION_CHLD_2; action = HeadsetClientHalConstants.CALL_ACTION_CHLD_2; } } Loading Loading @@ -581,6 +585,7 @@ public class HeadsetClientStateMachine extends StateMachine { if (c != null) { if (c != null) { if (NativeInterface.handleCallActionNative(getByteAddress(mCurrentDevice), action, 0)) { if (NativeInterface.handleCallActionNative(getByteAddress(mCurrentDevice), action, 0)) { addQueuedAction(TERMINATE_CALL, action); addQueuedAction(TERMINATE_CALL, action); sendMessageDelayed(DISCONNECT_AUDIO, SCO_DISCONNECT_TIMEOUT_MS); } else { } else { Log.e(TAG, "ERROR: Couldn't terminate outgoing call"); Log.e(TAG, "ERROR: Couldn't terminate outgoing call"); } } Loading Loading @@ -1065,7 +1070,6 @@ public class HeadsetClientStateMachine extends StateMachine { } } NativeInterface.connectNative(getByteAddress(device)); NativeInterface.connectNative(getByteAddress(device)); // deferMessage(message); break; break; case DISCONNECT: case DISCONNECT: BluetoothDevice dev = (BluetoothDevice) message.obj; BluetoothDevice dev = (BluetoothDevice) message.obj; Loading @@ -1083,11 +1087,8 @@ public class HeadsetClientStateMachine extends StateMachine { break; break; case CONNECT_AUDIO: case CONNECT_AUDIO: if (!mService.isScoAvailable() if (!NativeInterface.connectAudioNative(getByteAddress(mCurrentDevice))) { || !NativeInterface.connectAudioNative( Log.e(TAG, "ERROR: Couldn't connect Audio for device " + mCurrentDevice); getByteAddress(mCurrentDevice))) { Log.e(TAG, "ERROR: Couldn't connect Audio for device " + mCurrentDevice + " isScoAvailable " + mService.isScoAvailable()); broadcastAudioState(mCurrentDevice, broadcastAudioState(mCurrentDevice, BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED, BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED, BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED); BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED); Loading Loading @@ -1398,6 +1399,16 @@ public class HeadsetClientStateMachine extends StateMachine { // for routing and volume purposes. // for routing and volume purposes. // NOTE: All calls here are routed via the setParameters which changes the // NOTE: All calls here are routed via the setParameters which changes the // routing at the Audio HAL level. // routing at the Audio HAL level. if (mService.isScoRouted()) { StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED); event.valueInt = state; event.device = device; sendMessageDelayed(StackEvent.STACK_EVENT, event, ROUTING_DELAY_MS); break; } mAudioState = BluetoothHeadsetClient.STATE_AUDIO_CONNECTED; mAudioState = BluetoothHeadsetClient.STATE_AUDIO_CONNECTED; // We need to set the volume after switching into HFP mode as some Audio HALs // We need to set the volume after switching into HFP mode as some Audio HALs Loading Loading @@ -1501,6 +1512,11 @@ public class HeadsetClientStateMachine extends StateMachine { mAudioManager.setParameters("hfp_enable=false"); mAudioManager.setParameters("hfp_enable=false"); } } break; break; case HOLD_CALL: holdCall(); break; case StackEvent.STACK_EVENT: case StackEvent.STACK_EVENT: StackEvent event = (StackEvent) message.obj; StackEvent event = (StackEvent) message.obj; if (DBG) { if (DBG) { Loading Loading @@ -1561,6 +1577,7 @@ public class HeadsetClientStateMachine extends StateMachine { switch (state) { switch (state) { case HeadsetClientHalConstants.AUDIO_STATE_DISCONNECTED: case HeadsetClientHalConstants.AUDIO_STATE_DISCONNECTED: removeMessages(DISCONNECT_AUDIO); mAudioState = BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED; mAudioState = BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED; // Audio focus may still be held by the entity controlling the actual call // Audio focus may still be held by the entity controlling the actual call // (such as Telecom) and hence this will still keep the call around, there // (such as Telecom) and hence this will still keep the call around, there Loading src/com/android/bluetooth/hfpclient/connserv/HfpClientConnection.java +2 −0 Original line number Original line Diff line number Diff line Loading @@ -81,6 +81,7 @@ public class HfpClientConnection extends Connection { return; return; } } mHeadsetProfile.connectAudio(device); setInitializing(); setInitializing(); setDialing(); setDialing(); finishInitializing(); finishInitializing(); Loading Loading @@ -256,6 +257,7 @@ public class HfpClientConnection extends Connection { if (!mClosed) { if (!mClosed) { mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE); mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE); } } mHeadsetProfile.connectAudio(mDevice); } } @Override @Override Loading Loading
src/com/android/bluetooth/hfpclient/HeadsetClientService.java +33 −13 Original line number Original line Diff line number Diff line Loading @@ -646,6 +646,24 @@ public class HeadsetClientService extends ProfileService { boolean acceptCall(BluetoothDevice device, int flag) { boolean acceptCall(BluetoothDevice device, int flag) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); /* Phonecalls from a single device are supported, hang up any calls on the other phone */ synchronized (this) { for (Map.Entry<BluetoothDevice, HeadsetClientStateMachine> entry : mStateMachineMap.entrySet()) { if (entry.getValue() == null) { continue; } int connectionState = entry.getValue().getConnectionState(entry.getKey()); if (DBG) { Log.d(TAG, "Accepting a call on device " + device + ". Possibly disconnecting on " + entry.getValue()); } if (connectionState == BluetoothProfile.STATE_CONNECTED) entry.getValue() .obtainMessage(HeadsetClientStateMachine.TERMINATE_CALL) .sendToTarget(); } } HeadsetClientStateMachine sm = getStateMachine(device); HeadsetClientStateMachine sm = getStateMachine(device); if (sm == null) { if (sm == null) { Log.e(TAG, "Cannot allocate SM for device " + device); Log.e(TAG, "Cannot allocate SM for device " + device); Loading @@ -653,8 +671,7 @@ public class HeadsetClientService extends ProfileService { } } int connectionState = sm.getConnectionState(device); int connectionState = sm.getConnectionState(device); if (connectionState != BluetoothProfile.STATE_CONNECTED && if (connectionState != BluetoothProfile.STATE_CONNECTED) { connectionState != BluetoothProfile.STATE_CONNECTING) { return false; return false; } } Message msg = sm.obtainMessage(HeadsetClientStateMachine.ACCEPT_CALL); Message msg = sm.obtainMessage(HeadsetClientStateMachine.ACCEPT_CALL); Loading Loading @@ -872,20 +889,23 @@ public class HeadsetClientService extends ProfileService { return sm; return sm; } } // Check if any of the state machines are currently holding the SCO audio stream // Check if any of the state machines have routed the SCO audio stream. // This function is *only* called from the SMs which are themselves run the same thread and synchronized boolean isScoRouted() { // hence we do not need synchronization here for (Map.Entry<BluetoothDevice, HeadsetClientStateMachine> entry : boolean isScoAvailable() { mStateMachineMap.entrySet()) { for (BluetoothDevice bd : mStateMachineMap.keySet()) { if (entry.getValue() != null) { HeadsetClientStateMachine sm = mStateMachineMap.get(bd); int audioState = entry.getValue().getAudioState(entry.getKey()); int audioState = sm.getAudioState(bd); if (audioState == BluetoothHeadsetClient.STATE_AUDIO_CONNECTED) { if (audioState != BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED) { if (DBG) { Log.w(TAG, "Device " + bd + " audio state " + audioState + " not disconnected"); Log.d(TAG, "Device " + entry.getKey() + " audio state " + audioState return false; + " Connected"); } } } return true; return true; } } } } return false; } @Override @Override public synchronized void dump(StringBuilder sb) { public synchronized void dump(StringBuilder sb) { Loading
src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java +23 −6 Original line number Original line Diff line number Diff line Loading @@ -104,6 +104,8 @@ public class HeadsetClientStateMachine extends StateMachine { // Timeouts. // Timeouts. static final int CONNECTING_TIMEOUT_MS = 10000; // 10s static final int CONNECTING_TIMEOUT_MS = 10000; // 10s static final int ROUTING_DELAY_MS = 250; static final int SCO_DISCONNECT_TIMEOUT_MS = 750; static final int MAX_HFP_SCO_VOICE_CALL_VOLUME = 15; // HFP 1.5 spec. static final int MAX_HFP_SCO_VOICE_CALL_VOLUME = 15; // HFP 1.5 spec. static final int MIN_HFP_SCO_VOICE_CALL_VOLUME = 1; // HFP 1.5 spec. static final int MIN_HFP_SCO_VOICE_CALL_VOLUME = 1; // HFP 1.5 spec. Loading Loading @@ -460,6 +462,8 @@ public class HeadsetClientStateMachine extends StateMachine { action = HeadsetClientHalConstants.CALL_ACTION_CHLD_1; action = HeadsetClientHalConstants.CALL_ACTION_CHLD_1; } else if (getCall(BluetoothHeadsetClientCall.CALL_STATE_ACTIVE) != null) { } else if (getCall(BluetoothHeadsetClientCall.CALL_STATE_ACTIVE) != null) { action = HeadsetClientHalConstants.CALL_ACTION_CHLD_3; action = HeadsetClientHalConstants.CALL_ACTION_CHLD_3; } else if (flag == BluetoothHeadsetClient.CALL_ACCEPT_NONE) { action = HeadsetClientHalConstants.CALL_ACTION_CHLD_2; } else { } else { action = HeadsetClientHalConstants.CALL_ACTION_CHLD_2; action = HeadsetClientHalConstants.CALL_ACTION_CHLD_2; } } Loading Loading @@ -581,6 +585,7 @@ public class HeadsetClientStateMachine extends StateMachine { if (c != null) { if (c != null) { if (NativeInterface.handleCallActionNative(getByteAddress(mCurrentDevice), action, 0)) { if (NativeInterface.handleCallActionNative(getByteAddress(mCurrentDevice), action, 0)) { addQueuedAction(TERMINATE_CALL, action); addQueuedAction(TERMINATE_CALL, action); sendMessageDelayed(DISCONNECT_AUDIO, SCO_DISCONNECT_TIMEOUT_MS); } else { } else { Log.e(TAG, "ERROR: Couldn't terminate outgoing call"); Log.e(TAG, "ERROR: Couldn't terminate outgoing call"); } } Loading Loading @@ -1065,7 +1070,6 @@ public class HeadsetClientStateMachine extends StateMachine { } } NativeInterface.connectNative(getByteAddress(device)); NativeInterface.connectNative(getByteAddress(device)); // deferMessage(message); break; break; case DISCONNECT: case DISCONNECT: BluetoothDevice dev = (BluetoothDevice) message.obj; BluetoothDevice dev = (BluetoothDevice) message.obj; Loading @@ -1083,11 +1087,8 @@ public class HeadsetClientStateMachine extends StateMachine { break; break; case CONNECT_AUDIO: case CONNECT_AUDIO: if (!mService.isScoAvailable() if (!NativeInterface.connectAudioNative(getByteAddress(mCurrentDevice))) { || !NativeInterface.connectAudioNative( Log.e(TAG, "ERROR: Couldn't connect Audio for device " + mCurrentDevice); getByteAddress(mCurrentDevice))) { Log.e(TAG, "ERROR: Couldn't connect Audio for device " + mCurrentDevice + " isScoAvailable " + mService.isScoAvailable()); broadcastAudioState(mCurrentDevice, broadcastAudioState(mCurrentDevice, BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED, BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED, BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED); BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED); Loading Loading @@ -1398,6 +1399,16 @@ public class HeadsetClientStateMachine extends StateMachine { // for routing and volume purposes. // for routing and volume purposes. // NOTE: All calls here are routed via the setParameters which changes the // NOTE: All calls here are routed via the setParameters which changes the // routing at the Audio HAL level. // routing at the Audio HAL level. if (mService.isScoRouted()) { StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED); event.valueInt = state; event.device = device; sendMessageDelayed(StackEvent.STACK_EVENT, event, ROUTING_DELAY_MS); break; } mAudioState = BluetoothHeadsetClient.STATE_AUDIO_CONNECTED; mAudioState = BluetoothHeadsetClient.STATE_AUDIO_CONNECTED; // We need to set the volume after switching into HFP mode as some Audio HALs // We need to set the volume after switching into HFP mode as some Audio HALs Loading Loading @@ -1501,6 +1512,11 @@ public class HeadsetClientStateMachine extends StateMachine { mAudioManager.setParameters("hfp_enable=false"); mAudioManager.setParameters("hfp_enable=false"); } } break; break; case HOLD_CALL: holdCall(); break; case StackEvent.STACK_EVENT: case StackEvent.STACK_EVENT: StackEvent event = (StackEvent) message.obj; StackEvent event = (StackEvent) message.obj; if (DBG) { if (DBG) { Loading Loading @@ -1561,6 +1577,7 @@ public class HeadsetClientStateMachine extends StateMachine { switch (state) { switch (state) { case HeadsetClientHalConstants.AUDIO_STATE_DISCONNECTED: case HeadsetClientHalConstants.AUDIO_STATE_DISCONNECTED: removeMessages(DISCONNECT_AUDIO); mAudioState = BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED; mAudioState = BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED; // Audio focus may still be held by the entity controlling the actual call // Audio focus may still be held by the entity controlling the actual call // (such as Telecom) and hence this will still keep the call around, there // (such as Telecom) and hence this will still keep the call around, there Loading
src/com/android/bluetooth/hfpclient/connserv/HfpClientConnection.java +2 −0 Original line number Original line Diff line number Diff line Loading @@ -81,6 +81,7 @@ public class HfpClientConnection extends Connection { return; return; } } mHeadsetProfile.connectAudio(device); setInitializing(); setInitializing(); setDialing(); setDialing(); finishInitializing(); finishInitializing(); Loading Loading @@ -256,6 +257,7 @@ public class HfpClientConnection extends Connection { if (!mClosed) { if (!mClosed) { mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE); mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE); } } mHeadsetProfile.connectAudio(mDevice); } } @Override @Override Loading