Loading android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java +84 −28 Original line number Diff line number Diff line Loading @@ -62,6 +62,8 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Queue; import java.util.SortedMap; import java.util.TreeMap; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; Loading @@ -75,7 +77,7 @@ import java.util.concurrent.Executors; public class BluetoothInCallService extends InCallService { private static final String TAG = "BluetoothInCallService"; private static final String CLCC_INFERENCE = "ConferenceCallInference"; @VisibleForTesting static final String CLCC_INFERENCE = "ConferenceCallInference"; // match up with bthf_call_state_t of bt_hf.h private static final int CALL_STATE_ACTIVE = 0; Loading Loading @@ -164,11 +166,13 @@ public class BluetoothInCallService extends InCallService { public void onServiceConnected(int profile, BluetoothProfile proxy) { synchronized (LOCK) { if (profile == BluetoothProfile.HEADSET) { setBluetoothHeadset(new BluetoothHeadsetProxy((BluetoothHeadset) proxy)); setBluetoothHeadset( new BluetoothHeadsetProxy((BluetoothHeadset) proxy)); updateHeadsetWithCallState(true /* force */); } else { setBluetoothLeCallControl(new BluetoothLeCallControlProxy(( BluetoothLeCallControl) proxy)); setBluetoothLeCallControl( new BluetoothLeCallControlProxy( (BluetoothLeCallControl) proxy)); sendTbsCurrentCallsList(); } } Loading Loading @@ -639,9 +643,16 @@ public class BluetoothInCallService extends InCallService { if (mBluetoothCallHashMap.containsKey(call.getId())) { mBluetoothCallHashMap.remove(call.getId()); DisconnectCause cause = call.getDisconnectCause(); if (cause != null && cause.getCode() == DisconnectCause.OTHER) { Log.d(TAG, "add inference call with reason: " + cause.getReason()); mBluetoothCallQueue.add(call.getId()); mBluetoothConferenceCallInference.put(call.getId(), call); mClccInferenceIndexMap.put(getClccMapKey(call), mClccIndexMap.get(getClccMapKey(call))); Integer indexValue = mClccIndexMap.get(getClccMapKey(call)); mClccInferenceIndexMap.put(getClccMapKey(call), indexValue); if (indexValue == null) { Log.w(TAG, "CLCC index value is null"); } // queue size limited to 2 because merge operation only happens on 2 calls // we are only interested in last 2 calls merged if (mBluetoothCallQueue.size() > 2) { Loading @@ -650,6 +661,14 @@ public class BluetoothInCallService extends InCallService { mBluetoothConferenceCallInference.remove(callId); } } // As there is at most 1 conference call, so clear inference when parent call ends if (call.isConference()) { Log.d(TAG, "conference call ends, clear inference"); mBluetoothConferenceCallInference.clear(); mClccInferenceIndexMap.clear(); mBluetoothCallQueue.clear(); } } mClccIndexMap.remove(getClccMapKey(call)); updateHeadsetWithCallState(false /* force */); Loading Loading @@ -745,18 +764,34 @@ public class BluetoothInCallService extends InCallService { Log.d(TAG, "is conference call inference enabled: " + isInferenceEnabled); for (BluetoothCall call : calls) { if (isInferenceEnabled && call.isConference() && call.getChildrenIds().size() < 2 && !mBluetoothConferenceCallInference.isEmpty()) { Log.d(TAG, "conference call inferred size: " SortedMap<Integer, Object[]> clccResponseMap = new TreeMap<>(); Log.d( TAG, "conference call inferred size: " + mBluetoothConferenceCallInference.size() + "current size: " + mBluetoothCallHashMap.size()); + " current size: " + mBluetoothCallHashMap.size()); // Do conference call inference until at least 2 children arrive // If carrier does send children info, then inference will end when info arrives. // If carrier does not send children info, then inference won't impact actual value. if (call.getChildrenIds().size() >= 2) { mBluetoothConferenceCallInference.clear(); break; } for (BluetoothCall inferredCall : mBluetoothConferenceCallInference.values()) { int index = mClccInferenceIndexMap.get(getClccMapKey(inferredCall)); String clccMapKey = getClccMapKey(inferredCall); if (!mClccInferenceIndexMap.containsKey(clccMapKey)) { Log.w(TAG, "Inference Index Map does not have: " + clccMapKey); continue; } if (mClccInferenceIndexMap.get(clccMapKey) == null) { Log.w(TAG, "inferred index is null"); continue; } int index = mClccInferenceIndexMap.get(clccMapKey); // save the index so later on when real children arrive, index is the same mClccIndexMap.put(getClccMapKey(inferredCall), index); mClccIndexMap.put(clccMapKey, index); int direction = inferredCall.isIncoming() ? 1 : 0; int state = CALL_STATE_ACTIVE; boolean isPartOfConference = true; Loading @@ -770,17 +805,38 @@ public class BluetoothInCallService extends InCallService { if (address != null) { address = PhoneNumberUtils.stripSeparators(address); } int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address); Log.i(TAG, "sending inferred clcc for BluetoothCall " + index + ", " + direction + ", " + state + ", " + isPartOfConference + ", " + addressType); clccResponseMap.put( index, new Object[] { index, direction, state, 0, isPartOfConference, address, addressType }); } // ensure response is sorted by index for (Object[] response : clccResponseMap.values()) { if (response.length < 7) { Log.e(TAG, "clccResponseMap entry too short"); continue; } Log.i( TAG, String.format( "sending inferred clcc for BluetoothCall: index %d, direction" + " %d, state %d, isPartOfConference %b, addressType %d", (int) response[0], (int) response[1], (int) response[2], (boolean) response[4], (int) response[6])); mBluetoothHeadset.clccResponse( index, direction, state, 0, isPartOfConference, address, addressType); (int) response[0], (int) response[1], (int) response[2], (int) response[3], (boolean) response[4], (String) response[5], (int) response[6]); } sendClccEndMarker(); return; Loading android/app/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java +140 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.bluetooth.telephony; import static com.android.bluetooth.telephony.BluetoothInCallService.CLCC_INFERENCE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; Loading @@ -31,6 +32,7 @@ import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.provider.DeviceConfig; import android.telecom.BluetoothCallQualityReport; import android.telecom.Call; import android.telecom.Connection; Loading Loading @@ -766,6 +768,144 @@ public class BluetoothInCallServiceTest { eq(1), eq(1), eq(0), eq(0), eq(true), eq("5551234"), eq(129)); } @Test public void testListCurrentCallsConferenceEmptyChildrenInference() throws Exception { DeviceConfig.setProperty(DeviceConfig.NAMESPACE_BLUETOOTH, CLCC_INFERENCE, "true", false); ArrayList<BluetoothCall> calls = new ArrayList<>(); when(mMockCallInfo.getBluetoothCalls()).thenReturn(calls); // active call is added BluetoothCall activeCall = createActiveCall(UUID.randomUUID()); calls.add(activeCall); mBluetoothInCallService.onCallAdded(activeCall); when(activeCall.getState()).thenReturn(Call.STATE_ACTIVE); when(activeCall.isIncoming()).thenReturn(false); when(activeCall.isConference()).thenReturn(false); when(activeCall.getHandle()).thenReturn(Uri.parse("tel:555-0001")); when(activeCall.getGatewayInfo()) .thenReturn(new GatewayInfo(null, null, Uri.parse("tel:555-0001"))); // holding call is added BluetoothCall holdingCall = createHeldCall(UUID.randomUUID()); calls.add(holdingCall); mBluetoothInCallService.onCallAdded(holdingCall); when(holdingCall.getState()).thenReturn(Call.STATE_HOLDING); when(holdingCall.isIncoming()).thenReturn(true); when(holdingCall.isConference()).thenReturn(false); when(holdingCall.getHandle()).thenReturn(Uri.parse("tel:555-0002")); when(holdingCall.getGatewayInfo()) .thenReturn(new GatewayInfo(null, null, Uri.parse("tel:555-0002"))); // needs to have at least one CLCC response before merge to enable call inference clearInvocations(mMockBluetoothHeadset); mBluetoothInCallService.listCurrentCalls(); verify(mMockBluetoothHeadset) .clccResponse( 1, 0, CALL_STATE_ACTIVE, 0, false, "5550001", PhoneNumberUtils.TOA_Unknown); verify(mMockBluetoothHeadset) .clccResponse( 2, 1, CALL_STATE_HELD, 0, false, "5550002", PhoneNumberUtils.TOA_Unknown); calls.clear(); // calls merged for conference call DisconnectCause cause = new DisconnectCause(DisconnectCause.OTHER); when(activeCall.getDisconnectCause()).thenReturn(cause); when(holdingCall.getDisconnectCause()).thenReturn(cause); mBluetoothInCallService.onCallRemoved(activeCall, true); mBluetoothInCallService.onCallRemoved(holdingCall, true); BluetoothCall conferenceCall = createActiveCall(UUID.randomUUID()); addCallCapability(conferenceCall, Connection.CAPABILITY_MANAGE_CONFERENCE); when(conferenceCall.getHandle()).thenReturn(Uri.parse("tel:555-1234")); when(conferenceCall.isConference()).thenReturn(true); when(conferenceCall.getState()).thenReturn(Call.STATE_ACTIVE); when(conferenceCall.hasProperty(Call.Details.PROPERTY_GENERIC_CONFERENCE)).thenReturn(true); when(conferenceCall.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN)) .thenReturn(false); when(conferenceCall.isIncoming()).thenReturn(true); when(mMockCallInfo.getBluetoothCalls()).thenReturn(calls); // parent call arrived, but children have not, then do inference on children calls.add(conferenceCall); Assert.assertEquals(calls.size(), 1); mBluetoothInCallService.onCallAdded(conferenceCall); clearInvocations(mMockBluetoothHeadset); mBluetoothInCallService.listCurrentCalls(); verify(mMockBluetoothHeadset) .clccResponse( 1, 0, CALL_STATE_ACTIVE, 0, true, "5550001", PhoneNumberUtils.TOA_Unknown); verify(mMockBluetoothHeadset) .clccResponse( 2, 1, CALL_STATE_ACTIVE, 0, true, "5550002", PhoneNumberUtils.TOA_Unknown); // real children arrive, no change on CLCC response calls.add(activeCall); mBluetoothInCallService.onCallAdded(activeCall); when(activeCall.isConference()).thenReturn(true); calls.add(holdingCall); mBluetoothInCallService.onCallAdded(holdingCall); when(holdingCall.getState()).thenReturn(Call.STATE_ACTIVE); when(holdingCall.isConference()).thenReturn(true); when(conferenceCall.getChildrenIds()).thenReturn(new ArrayList<>(Arrays.asList(1, 2))); clearInvocations(mMockBluetoothHeadset); mBluetoothInCallService.listCurrentCalls(); verify(mMockBluetoothHeadset) .clccResponse( 1, 0, CALL_STATE_ACTIVE, 0, true, "5550001", PhoneNumberUtils.TOA_Unknown); verify(mMockBluetoothHeadset) .clccResponse( 2, 1, CALL_STATE_ACTIVE, 0, true, "5550002", PhoneNumberUtils.TOA_Unknown); // when call is terminated, children first removed, then parent cause = new DisconnectCause(DisconnectCause.LOCAL); when(activeCall.getDisconnectCause()).thenReturn(cause); when(holdingCall.getDisconnectCause()).thenReturn(cause); mBluetoothInCallService.onCallRemoved(activeCall, true); mBluetoothInCallService.onCallRemoved(holdingCall, true); calls.remove(activeCall); calls.remove(holdingCall); Assert.assertEquals(calls.size(), 1); clearInvocations(mMockBluetoothHeadset); mBluetoothInCallService.listCurrentCalls(); verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0); verify(mMockBluetoothHeadset, times(1)) .clccResponse( anyInt(), anyInt(), anyInt(), anyInt(), anyBoolean(), nullable(String.class), anyInt()); // when parent is removed when(conferenceCall.getDisconnectCause()).thenReturn(cause); calls.remove(conferenceCall); mBluetoothInCallService.onCallRemoved(conferenceCall, true); clearInvocations(mMockBluetoothHeadset); mBluetoothInCallService.listCurrentCalls(); verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0); verify(mMockBluetoothHeadset, times(1)) .clccResponse( anyInt(), anyInt(), anyInt(), anyInt(), anyBoolean(), nullable(String.class), anyInt()); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_BLUETOOTH, CLCC_INFERENCE, "false", false); } @Test public void testQueryPhoneState() throws Exception { BluetoothCall ringingCall = createRingingCall(UUID.randomUUID()); Loading Loading
android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java +84 −28 Original line number Diff line number Diff line Loading @@ -62,6 +62,8 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Queue; import java.util.SortedMap; import java.util.TreeMap; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; Loading @@ -75,7 +77,7 @@ import java.util.concurrent.Executors; public class BluetoothInCallService extends InCallService { private static final String TAG = "BluetoothInCallService"; private static final String CLCC_INFERENCE = "ConferenceCallInference"; @VisibleForTesting static final String CLCC_INFERENCE = "ConferenceCallInference"; // match up with bthf_call_state_t of bt_hf.h private static final int CALL_STATE_ACTIVE = 0; Loading Loading @@ -164,11 +166,13 @@ public class BluetoothInCallService extends InCallService { public void onServiceConnected(int profile, BluetoothProfile proxy) { synchronized (LOCK) { if (profile == BluetoothProfile.HEADSET) { setBluetoothHeadset(new BluetoothHeadsetProxy((BluetoothHeadset) proxy)); setBluetoothHeadset( new BluetoothHeadsetProxy((BluetoothHeadset) proxy)); updateHeadsetWithCallState(true /* force */); } else { setBluetoothLeCallControl(new BluetoothLeCallControlProxy(( BluetoothLeCallControl) proxy)); setBluetoothLeCallControl( new BluetoothLeCallControlProxy( (BluetoothLeCallControl) proxy)); sendTbsCurrentCallsList(); } } Loading Loading @@ -639,9 +643,16 @@ public class BluetoothInCallService extends InCallService { if (mBluetoothCallHashMap.containsKey(call.getId())) { mBluetoothCallHashMap.remove(call.getId()); DisconnectCause cause = call.getDisconnectCause(); if (cause != null && cause.getCode() == DisconnectCause.OTHER) { Log.d(TAG, "add inference call with reason: " + cause.getReason()); mBluetoothCallQueue.add(call.getId()); mBluetoothConferenceCallInference.put(call.getId(), call); mClccInferenceIndexMap.put(getClccMapKey(call), mClccIndexMap.get(getClccMapKey(call))); Integer indexValue = mClccIndexMap.get(getClccMapKey(call)); mClccInferenceIndexMap.put(getClccMapKey(call), indexValue); if (indexValue == null) { Log.w(TAG, "CLCC index value is null"); } // queue size limited to 2 because merge operation only happens on 2 calls // we are only interested in last 2 calls merged if (mBluetoothCallQueue.size() > 2) { Loading @@ -650,6 +661,14 @@ public class BluetoothInCallService extends InCallService { mBluetoothConferenceCallInference.remove(callId); } } // As there is at most 1 conference call, so clear inference when parent call ends if (call.isConference()) { Log.d(TAG, "conference call ends, clear inference"); mBluetoothConferenceCallInference.clear(); mClccInferenceIndexMap.clear(); mBluetoothCallQueue.clear(); } } mClccIndexMap.remove(getClccMapKey(call)); updateHeadsetWithCallState(false /* force */); Loading Loading @@ -745,18 +764,34 @@ public class BluetoothInCallService extends InCallService { Log.d(TAG, "is conference call inference enabled: " + isInferenceEnabled); for (BluetoothCall call : calls) { if (isInferenceEnabled && call.isConference() && call.getChildrenIds().size() < 2 && !mBluetoothConferenceCallInference.isEmpty()) { Log.d(TAG, "conference call inferred size: " SortedMap<Integer, Object[]> clccResponseMap = new TreeMap<>(); Log.d( TAG, "conference call inferred size: " + mBluetoothConferenceCallInference.size() + "current size: " + mBluetoothCallHashMap.size()); + " current size: " + mBluetoothCallHashMap.size()); // Do conference call inference until at least 2 children arrive // If carrier does send children info, then inference will end when info arrives. // If carrier does not send children info, then inference won't impact actual value. if (call.getChildrenIds().size() >= 2) { mBluetoothConferenceCallInference.clear(); break; } for (BluetoothCall inferredCall : mBluetoothConferenceCallInference.values()) { int index = mClccInferenceIndexMap.get(getClccMapKey(inferredCall)); String clccMapKey = getClccMapKey(inferredCall); if (!mClccInferenceIndexMap.containsKey(clccMapKey)) { Log.w(TAG, "Inference Index Map does not have: " + clccMapKey); continue; } if (mClccInferenceIndexMap.get(clccMapKey) == null) { Log.w(TAG, "inferred index is null"); continue; } int index = mClccInferenceIndexMap.get(clccMapKey); // save the index so later on when real children arrive, index is the same mClccIndexMap.put(getClccMapKey(inferredCall), index); mClccIndexMap.put(clccMapKey, index); int direction = inferredCall.isIncoming() ? 1 : 0; int state = CALL_STATE_ACTIVE; boolean isPartOfConference = true; Loading @@ -770,17 +805,38 @@ public class BluetoothInCallService extends InCallService { if (address != null) { address = PhoneNumberUtils.stripSeparators(address); } int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address); Log.i(TAG, "sending inferred clcc for BluetoothCall " + index + ", " + direction + ", " + state + ", " + isPartOfConference + ", " + addressType); clccResponseMap.put( index, new Object[] { index, direction, state, 0, isPartOfConference, address, addressType }); } // ensure response is sorted by index for (Object[] response : clccResponseMap.values()) { if (response.length < 7) { Log.e(TAG, "clccResponseMap entry too short"); continue; } Log.i( TAG, String.format( "sending inferred clcc for BluetoothCall: index %d, direction" + " %d, state %d, isPartOfConference %b, addressType %d", (int) response[0], (int) response[1], (int) response[2], (boolean) response[4], (int) response[6])); mBluetoothHeadset.clccResponse( index, direction, state, 0, isPartOfConference, address, addressType); (int) response[0], (int) response[1], (int) response[2], (int) response[3], (boolean) response[4], (String) response[5], (int) response[6]); } sendClccEndMarker(); return; Loading
android/app/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java +140 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.bluetooth.telephony; import static com.android.bluetooth.telephony.BluetoothInCallService.CLCC_INFERENCE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; Loading @@ -31,6 +32,7 @@ import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.provider.DeviceConfig; import android.telecom.BluetoothCallQualityReport; import android.telecom.Call; import android.telecom.Connection; Loading Loading @@ -766,6 +768,144 @@ public class BluetoothInCallServiceTest { eq(1), eq(1), eq(0), eq(0), eq(true), eq("5551234"), eq(129)); } @Test public void testListCurrentCallsConferenceEmptyChildrenInference() throws Exception { DeviceConfig.setProperty(DeviceConfig.NAMESPACE_BLUETOOTH, CLCC_INFERENCE, "true", false); ArrayList<BluetoothCall> calls = new ArrayList<>(); when(mMockCallInfo.getBluetoothCalls()).thenReturn(calls); // active call is added BluetoothCall activeCall = createActiveCall(UUID.randomUUID()); calls.add(activeCall); mBluetoothInCallService.onCallAdded(activeCall); when(activeCall.getState()).thenReturn(Call.STATE_ACTIVE); when(activeCall.isIncoming()).thenReturn(false); when(activeCall.isConference()).thenReturn(false); when(activeCall.getHandle()).thenReturn(Uri.parse("tel:555-0001")); when(activeCall.getGatewayInfo()) .thenReturn(new GatewayInfo(null, null, Uri.parse("tel:555-0001"))); // holding call is added BluetoothCall holdingCall = createHeldCall(UUID.randomUUID()); calls.add(holdingCall); mBluetoothInCallService.onCallAdded(holdingCall); when(holdingCall.getState()).thenReturn(Call.STATE_HOLDING); when(holdingCall.isIncoming()).thenReturn(true); when(holdingCall.isConference()).thenReturn(false); when(holdingCall.getHandle()).thenReturn(Uri.parse("tel:555-0002")); when(holdingCall.getGatewayInfo()) .thenReturn(new GatewayInfo(null, null, Uri.parse("tel:555-0002"))); // needs to have at least one CLCC response before merge to enable call inference clearInvocations(mMockBluetoothHeadset); mBluetoothInCallService.listCurrentCalls(); verify(mMockBluetoothHeadset) .clccResponse( 1, 0, CALL_STATE_ACTIVE, 0, false, "5550001", PhoneNumberUtils.TOA_Unknown); verify(mMockBluetoothHeadset) .clccResponse( 2, 1, CALL_STATE_HELD, 0, false, "5550002", PhoneNumberUtils.TOA_Unknown); calls.clear(); // calls merged for conference call DisconnectCause cause = new DisconnectCause(DisconnectCause.OTHER); when(activeCall.getDisconnectCause()).thenReturn(cause); when(holdingCall.getDisconnectCause()).thenReturn(cause); mBluetoothInCallService.onCallRemoved(activeCall, true); mBluetoothInCallService.onCallRemoved(holdingCall, true); BluetoothCall conferenceCall = createActiveCall(UUID.randomUUID()); addCallCapability(conferenceCall, Connection.CAPABILITY_MANAGE_CONFERENCE); when(conferenceCall.getHandle()).thenReturn(Uri.parse("tel:555-1234")); when(conferenceCall.isConference()).thenReturn(true); when(conferenceCall.getState()).thenReturn(Call.STATE_ACTIVE); when(conferenceCall.hasProperty(Call.Details.PROPERTY_GENERIC_CONFERENCE)).thenReturn(true); when(conferenceCall.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN)) .thenReturn(false); when(conferenceCall.isIncoming()).thenReturn(true); when(mMockCallInfo.getBluetoothCalls()).thenReturn(calls); // parent call arrived, but children have not, then do inference on children calls.add(conferenceCall); Assert.assertEquals(calls.size(), 1); mBluetoothInCallService.onCallAdded(conferenceCall); clearInvocations(mMockBluetoothHeadset); mBluetoothInCallService.listCurrentCalls(); verify(mMockBluetoothHeadset) .clccResponse( 1, 0, CALL_STATE_ACTIVE, 0, true, "5550001", PhoneNumberUtils.TOA_Unknown); verify(mMockBluetoothHeadset) .clccResponse( 2, 1, CALL_STATE_ACTIVE, 0, true, "5550002", PhoneNumberUtils.TOA_Unknown); // real children arrive, no change on CLCC response calls.add(activeCall); mBluetoothInCallService.onCallAdded(activeCall); when(activeCall.isConference()).thenReturn(true); calls.add(holdingCall); mBluetoothInCallService.onCallAdded(holdingCall); when(holdingCall.getState()).thenReturn(Call.STATE_ACTIVE); when(holdingCall.isConference()).thenReturn(true); when(conferenceCall.getChildrenIds()).thenReturn(new ArrayList<>(Arrays.asList(1, 2))); clearInvocations(mMockBluetoothHeadset); mBluetoothInCallService.listCurrentCalls(); verify(mMockBluetoothHeadset) .clccResponse( 1, 0, CALL_STATE_ACTIVE, 0, true, "5550001", PhoneNumberUtils.TOA_Unknown); verify(mMockBluetoothHeadset) .clccResponse( 2, 1, CALL_STATE_ACTIVE, 0, true, "5550002", PhoneNumberUtils.TOA_Unknown); // when call is terminated, children first removed, then parent cause = new DisconnectCause(DisconnectCause.LOCAL); when(activeCall.getDisconnectCause()).thenReturn(cause); when(holdingCall.getDisconnectCause()).thenReturn(cause); mBluetoothInCallService.onCallRemoved(activeCall, true); mBluetoothInCallService.onCallRemoved(holdingCall, true); calls.remove(activeCall); calls.remove(holdingCall); Assert.assertEquals(calls.size(), 1); clearInvocations(mMockBluetoothHeadset); mBluetoothInCallService.listCurrentCalls(); verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0); verify(mMockBluetoothHeadset, times(1)) .clccResponse( anyInt(), anyInt(), anyInt(), anyInt(), anyBoolean(), nullable(String.class), anyInt()); // when parent is removed when(conferenceCall.getDisconnectCause()).thenReturn(cause); calls.remove(conferenceCall); mBluetoothInCallService.onCallRemoved(conferenceCall, true); clearInvocations(mMockBluetoothHeadset); mBluetoothInCallService.listCurrentCalls(); verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0); verify(mMockBluetoothHeadset, times(1)) .clccResponse( anyInt(), anyInt(), anyInt(), anyInt(), anyBoolean(), nullable(String.class), anyInt()); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_BLUETOOTH, CLCC_INFERENCE, "false", false); } @Test public void testQueryPhoneState() throws Exception { BluetoothCall ringingCall = createRingingCall(UUID.randomUUID()); Loading