Loading android/app/src/com/android/bluetooth/hfpclient/HeadsetClientHalConstants.java +6 −0 Original line number Diff line number Diff line Loading @@ -146,6 +146,11 @@ public final class HeadsetClientHalConstants { static final int PEER_FEAT_EXTERR = 0x00000100; // Codec Negotiation static final int PEER_FEAT_CODEC = 0x00000200; // HFP 1.7 features // HF Indicators static final int PEER_FEAT_HF_IND = 0x00000400; // ESCO S4 link setting static final int PEER_FEAT_ESCO_S4 = 0x00000800; // AG's 3WC features masks // match up with masks in bt_hf_client.h Loading @@ -171,6 +176,7 @@ public final class HeadsetClientHalConstants { static final int HANDSFREECLIENT_AT_CMD_NREC = 15; static final int HANDSFREECLIENT_AT_CMD_VENDOR_SPECIFIC_CMD = 16; static final int HANDSFREECLIENT_AT_CMD_BIEV = 17; // Flag to check for local NREC support static final boolean HANDSFREECLIENT_NREC_SUPPORTED = true; Loading android/app/src/com/android/bluetooth/hfpclient/HeadsetClientService.java +44 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioManager; import android.os.BatteryManager; import android.os.Bundle; import android.os.HandlerThread; import android.os.Message; Loading Loading @@ -62,6 +63,8 @@ public class HeadsetClientService extends ProfileService { private HeadsetClientStateMachineFactory mSmFactory = null; private DatabaseManager mDatabaseManager; private AudioManager mAudioManager = null; private BatteryManager mBatteryManager = null; private int mLastBatteryLevel = -1; // Maxinum number of devices we can try connecting to in one session private static final int MAX_STATE_MACHINES_POSSIBLE = 100; Loading Loading @@ -89,6 +92,8 @@ public class HeadsetClientService extends ProfileService { mNativeInterface = NativeInterface.getInstance(); mNativeInterface.initialize(); mBatteryManager = getSystemService(BatteryManager.class); mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); if (mAudioManager == null) { Log.e(TAG, "AudioManager service doesn't exist?"); Loading @@ -101,6 +106,7 @@ public class HeadsetClientService extends ProfileService { mStateMachineMap.clear(); IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION); filter.addAction(Intent.ACTION_BATTERY_CHANGED); registerReceiver(mBroadcastReceiver, filter); // Start the HfpClientConnectionService to create connection with telecom when HFP Loading Loading @@ -183,6 +189,29 @@ public class HeadsetClientService extends ProfileService { } } } } else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { int batteryIndicatorID = 2; int batteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0); if (batteryLevel == mLastBatteryLevel) { return; } mLastBatteryLevel = batteryLevel; if (DBG) { Log.d(TAG, "Send battery level update BIEV(2," + batteryLevel + ") command"); } synchronized (this) { for (HeadsetClientStateMachine sm : mStateMachineMap.values()) { if (sm != null) { sm.sendMessage(HeadsetClientStateMachine.SEND_BIEV, batteryIndicatorID, batteryLevel); } } } } } }; Loading Loading @@ -1016,4 +1045,19 @@ public class HeadsetClientService extends ProfileService { protected AudioManager getAudioManager() { return mAudioManager; } protected void updateBatteryLevel() { int batteryLevel = mBatteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY); int batteryIndicatorID = 2; synchronized (this) { for (HeadsetClientStateMachine sm : mStateMachineMap.values()) { if (sm != null) { sm.sendMessage(HeadsetClientStateMachine.SEND_BIEV, batteryIndicatorID, batteryLevel); } } } } } android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java +19 −0 Original line number Diff line number Diff line Loading @@ -105,6 +105,7 @@ public class HeadsetClientStateMachine extends StateMachine { public static final int EXPLICIT_CALL_TRANSFER = 18; public static final int DISABLE_NREC = 20; public static final int SEND_VENDOR_AT_COMMAND = 21; public static final int SEND_BIEV = 22; // internal actions private static final int QUERY_CURRENT_CALLS = 50; Loading Loading @@ -291,6 +292,8 @@ public class HeadsetClientStateMachine extends StateMachine { return "DISABLE_NREC"; case SEND_VENDOR_AT_COMMAND: return "SEND_VENDOR_AT_COMMAND"; case SEND_BIEV: return "SEND_BIEV"; case QUERY_CURRENT_CALLS: return "QUERY_CURRENT_CALLS"; case QUERY_OPERATOR_NAME: Loading Loading @@ -1151,6 +1154,7 @@ public class HeadsetClientStateMachine extends StateMachine { logD("Enter Connected: " + getCurrentMessage().what); mAudioWbs = false; mCommandedSpeakerVolume = -1; if (mPrevState == mConnecting) { broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING); Loading @@ -1161,6 +1165,7 @@ public class HeadsetClientStateMachine extends StateMachine { Log.e(TAG, "Connected: Illegal state transition from " + prevStateName + " to Connecting, mCurrentDevice=" + mCurrentDevice); } mService.updateBatteryLevel(); } @Override Loading Loading @@ -1239,6 +1244,20 @@ public class HeadsetClientStateMachine extends StateMachine { break; } case SEND_BIEV: { if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_HF_IND) == HeadsetClientHalConstants.PEER_FEAT_HF_IND) { int indicatorID = message.arg1; int value = message.arg2; mNativeInterface.sendATCmd(getByteAddress(mCurrentDevice), HeadsetClientHalConstants.HANDSFREECLIENT_AT_CMD_BIEV, indicatorID, value, null); } break; } // Called only for Mute/Un-mute - Mic volume change is not allowed. case SET_MIC_VOLUME: break; Loading android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java +47 −0 Original line number Diff line number Diff line Loading @@ -16,10 +16,17 @@ package com.android.bluetooth.hfpclient; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.content.Intent; import android.os.BatteryManager; import androidx.test.InstrumentationRegistry; import androidx.test.filters.MediumTest; Loading Loading @@ -48,9 +55,12 @@ public class HeadsetClientServiceTest { private BluetoothAdapter mAdapter = null; private Context mTargetContext; private static final int STANDARD_WAIT_MILLIS = 1000; @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); @Mock private AdapterService mAdapterService; @Mock private HeadsetClientStateMachine mStateMachine; @Mock private DatabaseManager mDatabaseManager; Loading Loading @@ -86,4 +96,41 @@ public class HeadsetClientServiceTest { public void testInitialize() { Assert.assertNotNull(HeadsetClientService.getHeadsetClientService()); } @Test public void testSendBIEVtoStateMachineWhenBatteryChanged() { // Put mock state machine BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:01:02:03:04:05"); mService.getStateMachineMap().put(device, mStateMachine); // Send battery changed intent Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED); intent.putExtra(BatteryManager.EXTRA_LEVEL, 50); mService.sendBroadcast(intent); // Expect send BIEV to state machine verify(mStateMachine, timeout(STANDARD_WAIT_MILLIS).times(1)) .sendMessage( eq(HeadsetClientStateMachine.SEND_BIEV), eq(2), anyInt()); } @Test public void testUpdateBatteryLevel() { // Put mock state machine BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:01:02:03:04:05"); mService.getStateMachineMap().put(device, mStateMachine); mService.updateBatteryLevel(); // Expect send BIEV to state machine verify(mStateMachine, timeout(STANDARD_WAIT_MILLIS).times(1)) .sendMessage( eq(HeadsetClientStateMachine.SEND_BIEV), eq(2), anyInt()); } } android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java +51 −0 Original line number Diff line number Diff line Loading @@ -368,6 +368,7 @@ public class HeadsetClientStateMachineTest { StackEvent slcEvent = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); slcEvent.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED; slcEvent.valueInt2 = HeadsetClientHalConstants.PEER_FEAT_ECS; slcEvent.valueInt2 |= HeadsetClientHalConstants.PEER_FEAT_HF_IND; slcEvent.device = mTestDevice; mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, slcEvent); ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class); Loading Loading @@ -622,4 +623,54 @@ public class HeadsetClientStateMachineTest { Assert.assertEquals(expectedState, state); return expectedBroadcastIndex + 1; } /** * Test send BIEV command */ @MediumTest @Test public void testSendBIEVCommand() { // Setup connection state machine to be in connected state when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn( BluetoothProfile.CONNECTION_POLICY_ALLOWED); int expectedBroadcastIndex = 1; expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex); expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex); int indicator_id = 2; int indicator_value = 50; Message msg = mHeadsetClientStateMachine.obtainMessage(HeadsetClientStateMachine.SEND_BIEV); msg.arg1 = indicator_id; msg.arg2 = indicator_value; mHeadsetClientStateMachine.sendMessage(msg); verify(mNativeInterface, timeout(STANDARD_WAIT_MILLIS).times(1)) .sendATCmd( Utils.getBytesFromAddress(mTestDevice.getAddress()), HeadsetClientHalConstants.HANDSFREECLIENT_AT_CMD_BIEV, indicator_id, indicator_value, null); } /** * Test state machine shall try to send AT+BIEV command to AG * to update an init battery level. */ @MediumTest @Test public void testSendBatteryUpdateIndicatorWhenConnect() { // Setup connection state machine to be in connected state when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn( BluetoothProfile.CONNECTION_POLICY_ALLOWED); int expectedBroadcastIndex = 1; expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex); expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex); verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(1)) .updateBatteryLevel(); } } Loading
android/app/src/com/android/bluetooth/hfpclient/HeadsetClientHalConstants.java +6 −0 Original line number Diff line number Diff line Loading @@ -146,6 +146,11 @@ public final class HeadsetClientHalConstants { static final int PEER_FEAT_EXTERR = 0x00000100; // Codec Negotiation static final int PEER_FEAT_CODEC = 0x00000200; // HFP 1.7 features // HF Indicators static final int PEER_FEAT_HF_IND = 0x00000400; // ESCO S4 link setting static final int PEER_FEAT_ESCO_S4 = 0x00000800; // AG's 3WC features masks // match up with masks in bt_hf_client.h Loading @@ -171,6 +176,7 @@ public final class HeadsetClientHalConstants { static final int HANDSFREECLIENT_AT_CMD_NREC = 15; static final int HANDSFREECLIENT_AT_CMD_VENDOR_SPECIFIC_CMD = 16; static final int HANDSFREECLIENT_AT_CMD_BIEV = 17; // Flag to check for local NREC support static final boolean HANDSFREECLIENT_NREC_SUPPORTED = true; Loading
android/app/src/com/android/bluetooth/hfpclient/HeadsetClientService.java +44 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioManager; import android.os.BatteryManager; import android.os.Bundle; import android.os.HandlerThread; import android.os.Message; Loading Loading @@ -62,6 +63,8 @@ public class HeadsetClientService extends ProfileService { private HeadsetClientStateMachineFactory mSmFactory = null; private DatabaseManager mDatabaseManager; private AudioManager mAudioManager = null; private BatteryManager mBatteryManager = null; private int mLastBatteryLevel = -1; // Maxinum number of devices we can try connecting to in one session private static final int MAX_STATE_MACHINES_POSSIBLE = 100; Loading Loading @@ -89,6 +92,8 @@ public class HeadsetClientService extends ProfileService { mNativeInterface = NativeInterface.getInstance(); mNativeInterface.initialize(); mBatteryManager = getSystemService(BatteryManager.class); mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); if (mAudioManager == null) { Log.e(TAG, "AudioManager service doesn't exist?"); Loading @@ -101,6 +106,7 @@ public class HeadsetClientService extends ProfileService { mStateMachineMap.clear(); IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION); filter.addAction(Intent.ACTION_BATTERY_CHANGED); registerReceiver(mBroadcastReceiver, filter); // Start the HfpClientConnectionService to create connection with telecom when HFP Loading Loading @@ -183,6 +189,29 @@ public class HeadsetClientService extends ProfileService { } } } } else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { int batteryIndicatorID = 2; int batteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0); if (batteryLevel == mLastBatteryLevel) { return; } mLastBatteryLevel = batteryLevel; if (DBG) { Log.d(TAG, "Send battery level update BIEV(2," + batteryLevel + ") command"); } synchronized (this) { for (HeadsetClientStateMachine sm : mStateMachineMap.values()) { if (sm != null) { sm.sendMessage(HeadsetClientStateMachine.SEND_BIEV, batteryIndicatorID, batteryLevel); } } } } } }; Loading Loading @@ -1016,4 +1045,19 @@ public class HeadsetClientService extends ProfileService { protected AudioManager getAudioManager() { return mAudioManager; } protected void updateBatteryLevel() { int batteryLevel = mBatteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY); int batteryIndicatorID = 2; synchronized (this) { for (HeadsetClientStateMachine sm : mStateMachineMap.values()) { if (sm != null) { sm.sendMessage(HeadsetClientStateMachine.SEND_BIEV, batteryIndicatorID, batteryLevel); } } } } }
android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java +19 −0 Original line number Diff line number Diff line Loading @@ -105,6 +105,7 @@ public class HeadsetClientStateMachine extends StateMachine { public static final int EXPLICIT_CALL_TRANSFER = 18; public static final int DISABLE_NREC = 20; public static final int SEND_VENDOR_AT_COMMAND = 21; public static final int SEND_BIEV = 22; // internal actions private static final int QUERY_CURRENT_CALLS = 50; Loading Loading @@ -291,6 +292,8 @@ public class HeadsetClientStateMachine extends StateMachine { return "DISABLE_NREC"; case SEND_VENDOR_AT_COMMAND: return "SEND_VENDOR_AT_COMMAND"; case SEND_BIEV: return "SEND_BIEV"; case QUERY_CURRENT_CALLS: return "QUERY_CURRENT_CALLS"; case QUERY_OPERATOR_NAME: Loading Loading @@ -1151,6 +1154,7 @@ public class HeadsetClientStateMachine extends StateMachine { logD("Enter Connected: " + getCurrentMessage().what); mAudioWbs = false; mCommandedSpeakerVolume = -1; if (mPrevState == mConnecting) { broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING); Loading @@ -1161,6 +1165,7 @@ public class HeadsetClientStateMachine extends StateMachine { Log.e(TAG, "Connected: Illegal state transition from " + prevStateName + " to Connecting, mCurrentDevice=" + mCurrentDevice); } mService.updateBatteryLevel(); } @Override Loading Loading @@ -1239,6 +1244,20 @@ public class HeadsetClientStateMachine extends StateMachine { break; } case SEND_BIEV: { if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_HF_IND) == HeadsetClientHalConstants.PEER_FEAT_HF_IND) { int indicatorID = message.arg1; int value = message.arg2; mNativeInterface.sendATCmd(getByteAddress(mCurrentDevice), HeadsetClientHalConstants.HANDSFREECLIENT_AT_CMD_BIEV, indicatorID, value, null); } break; } // Called only for Mute/Un-mute - Mic volume change is not allowed. case SET_MIC_VOLUME: break; Loading
android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java +47 −0 Original line number Diff line number Diff line Loading @@ -16,10 +16,17 @@ package com.android.bluetooth.hfpclient; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.content.Intent; import android.os.BatteryManager; import androidx.test.InstrumentationRegistry; import androidx.test.filters.MediumTest; Loading Loading @@ -48,9 +55,12 @@ public class HeadsetClientServiceTest { private BluetoothAdapter mAdapter = null; private Context mTargetContext; private static final int STANDARD_WAIT_MILLIS = 1000; @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); @Mock private AdapterService mAdapterService; @Mock private HeadsetClientStateMachine mStateMachine; @Mock private DatabaseManager mDatabaseManager; Loading Loading @@ -86,4 +96,41 @@ public class HeadsetClientServiceTest { public void testInitialize() { Assert.assertNotNull(HeadsetClientService.getHeadsetClientService()); } @Test public void testSendBIEVtoStateMachineWhenBatteryChanged() { // Put mock state machine BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:01:02:03:04:05"); mService.getStateMachineMap().put(device, mStateMachine); // Send battery changed intent Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED); intent.putExtra(BatteryManager.EXTRA_LEVEL, 50); mService.sendBroadcast(intent); // Expect send BIEV to state machine verify(mStateMachine, timeout(STANDARD_WAIT_MILLIS).times(1)) .sendMessage( eq(HeadsetClientStateMachine.SEND_BIEV), eq(2), anyInt()); } @Test public void testUpdateBatteryLevel() { // Put mock state machine BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:01:02:03:04:05"); mService.getStateMachineMap().put(device, mStateMachine); mService.updateBatteryLevel(); // Expect send BIEV to state machine verify(mStateMachine, timeout(STANDARD_WAIT_MILLIS).times(1)) .sendMessage( eq(HeadsetClientStateMachine.SEND_BIEV), eq(2), anyInt()); } }
android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java +51 −0 Original line number Diff line number Diff line Loading @@ -368,6 +368,7 @@ public class HeadsetClientStateMachineTest { StackEvent slcEvent = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); slcEvent.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED; slcEvent.valueInt2 = HeadsetClientHalConstants.PEER_FEAT_ECS; slcEvent.valueInt2 |= HeadsetClientHalConstants.PEER_FEAT_HF_IND; slcEvent.device = mTestDevice; mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, slcEvent); ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class); Loading Loading @@ -622,4 +623,54 @@ public class HeadsetClientStateMachineTest { Assert.assertEquals(expectedState, state); return expectedBroadcastIndex + 1; } /** * Test send BIEV command */ @MediumTest @Test public void testSendBIEVCommand() { // Setup connection state machine to be in connected state when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn( BluetoothProfile.CONNECTION_POLICY_ALLOWED); int expectedBroadcastIndex = 1; expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex); expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex); int indicator_id = 2; int indicator_value = 50; Message msg = mHeadsetClientStateMachine.obtainMessage(HeadsetClientStateMachine.SEND_BIEV); msg.arg1 = indicator_id; msg.arg2 = indicator_value; mHeadsetClientStateMachine.sendMessage(msg); verify(mNativeInterface, timeout(STANDARD_WAIT_MILLIS).times(1)) .sendATCmd( Utils.getBytesFromAddress(mTestDevice.getAddress()), HeadsetClientHalConstants.HANDSFREECLIENT_AT_CMD_BIEV, indicator_id, indicator_value, null); } /** * Test state machine shall try to send AT+BIEV command to AG * to update an init battery level. */ @MediumTest @Test public void testSendBatteryUpdateIndicatorWhenConnect() { // Setup connection state machine to be in connected state when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn( BluetoothProfile.CONNECTION_POLICY_ALLOWED); int expectedBroadcastIndex = 1; expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex); expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex); verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(1)) .updateBatteryLevel(); } }