Loading android/app/src/com/android/bluetooth/hfpclient/HeadsetClientService.java +5 −1 Original line number Diff line number Diff line Loading @@ -263,7 +263,11 @@ public class HeadsetClientService extends ProfileService { } }; private static BluetoothHeadsetClientCall toLegacyCall(HfpClientCall call) { /** * Convert {@code HfpClientCall} to legacy {@code BluetoothHeadsetClientCall} still used by some * clients. */ static BluetoothHeadsetClientCall toLegacyCall(HfpClientCall call) { if (call == null) return null; return new BluetoothHeadsetClientCall(call.getDevice(), call.getId(), call.getUUID(), call.getState(), call.getNumber(), call.isMultiParty(), call.isOutgoing(), Loading android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java +13 −2 Original line number Diff line number Diff line Loading @@ -35,6 +35,9 @@ package com.android.bluetooth.hfpclient; import static android.Manifest.permission.BLUETOOTH_CONNECT; import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; import static android.content.pm.PackageManager.FEATURE_WATCH; import static java.util.Objects.requireNonNull; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; Loading Loading @@ -389,7 +392,15 @@ public class HeadsetClientStateMachine extends StateMachine { logD("sendCallChangedIntent " + c); Intent intent = new Intent(BluetoothHeadsetClient.ACTION_CALL_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); if (mService.getPackageManager().hasSystemFeature(FEATURE_WATCH)) { logD("Send legacy call"); intent.putExtra( BluetoothHeadsetClient.EXTRA_CALL, HeadsetClientService.toLegacyCall(c)); } else { intent.putExtra(BluetoothHeadsetClient.EXTRA_CALL, c); } Utils.sendBroadcast(mService, intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions()); HfpClientConnectionService.onCallChanged(c.getDevice(), c); Loading Loading @@ -872,7 +883,7 @@ public class HeadsetClientStateMachine extends StateMachine { HeadsetClientStateMachine(HeadsetClientService context, HeadsetService headsetService, Looper looper, NativeInterface nativeInterface) { super(TAG, looper); mService = context; mService = requireNonNull(context); mNativeInterface = nativeInterface; mAudioManager = mService.getAudioManager(); mHeadsetService = headsetService; Loading android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java +63 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.bluetooth.hfpclient; import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTED; import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.AT_OK; Loading @@ -32,11 +34,13 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAssignedNumbers; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadsetClient; import android.bluetooth.BluetoothHeadsetClientCall; import android.bluetooth.BluetoothSinkAudioPolicy; import android.bluetooth.BluetoothStatusCodes; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Resources; import android.media.AudioManager; import android.os.Bundle; Loading Loading @@ -93,7 +97,7 @@ public class HeadsetClientStateMachineTest { @Mock private HeadsetClientService mHeadsetClientService; @Mock private AudioManager mAudioManager; @Mock private RemoteDevices mRemoteDevices; @Mock private PackageManager mPackageManager; @Mock private NativeInterface mNativeInterface; private static final int STANDARD_WAIT_MILLIS = 1000; Loading @@ -114,6 +118,8 @@ public class HeadsetClientStateMachineTest { when(mHeadsetClientService.getAudioManager()).thenReturn( mAudioManager); when(mHeadsetClientService.getResources()).thenReturn(mMockHfpResources); when(mHeadsetClientService.getPackageManager()).thenReturn(mPackageManager); when(mPackageManager.hasSystemFeature(FEATURE_WATCH)).thenReturn(false); when(mMockHfpResources.getBoolean(R.bool.hfp_clcc_poll_during_call)).thenReturn(true); when(mMockHfpResources.getInteger(R.integer.hfp_clcc_poll_interval_during_call)) .thenReturn(2000); Loading Loading @@ -462,7 +468,63 @@ public class HeadsetClientStateMachineTest { intentArgument.getValue().getIntExtra(BluetoothHeadsetClient.EXTRA_IN_BAND_RING, -1)); Assert.assertEquals(false, mHeadsetClientStateMachine.getInBandRing()); } /** Test that wearables use {@code BluetoothHeadsetClientCall} in intent. */ @Test public void testWearablesUseBluetoothHeadsetClientCallInIntent() { // Specify the watch form factor when package manager is asked when(mPackageManager.hasSystemFeature(FEATURE_WATCH)).thenReturn(true); // Skip over the Android AT commands to test this code path doReturn(false).when(mNativeInterface).sendAndroidAt(anyObject(), anyString()); // Return true for connection policy to allow connections when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))) .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); // Send an incoming connection event StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); event.device = mTestDevice; event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED; mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, event); // Send a message to trigger service level connection using the required ECS feature event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); event.device = mTestDevice; event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED; event.valueInt2 = HeadsetClientHalConstants.PEER_FEAT_ECS; mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, event); // Dial a phone call, which will fail as @{code dial} method is not specified in @{code // mNativeInterface} mock and trigger a call state changed broadcast mHeadsetClientStateMachine.sendMessage( HeadsetClientStateMachine.DIAL_NUMBER, new HfpClientCall( mTestDevice, 0, HfpClientCall.CALL_STATE_WAITING, "1", false, false, false)); // Wait for processing TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); // Verify the broadcast ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class); verify(mHeadsetClientService, times(1)) .sendBroadcast(intentArgument.capture(), anyString(), any(Bundle.class)); // Verify that the parcelable extra has a legacy {@code BluetoothHeadsetClientCall} type for // wearables. Assert.assertThat( intentArgument.getValue().getParcelableExtra(BluetoothHeadsetClient.EXTRA_CALL), IsInstanceOf.instanceOf(BluetoothHeadsetClientCall.class)); // To satisfy the @After verification verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(true)); } /* Utility function to simulate HfpClient is connected. */ Loading Loading
android/app/src/com/android/bluetooth/hfpclient/HeadsetClientService.java +5 −1 Original line number Diff line number Diff line Loading @@ -263,7 +263,11 @@ public class HeadsetClientService extends ProfileService { } }; private static BluetoothHeadsetClientCall toLegacyCall(HfpClientCall call) { /** * Convert {@code HfpClientCall} to legacy {@code BluetoothHeadsetClientCall} still used by some * clients. */ static BluetoothHeadsetClientCall toLegacyCall(HfpClientCall call) { if (call == null) return null; return new BluetoothHeadsetClientCall(call.getDevice(), call.getId(), call.getUUID(), call.getState(), call.getNumber(), call.isMultiParty(), call.isOutgoing(), Loading
android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java +13 −2 Original line number Diff line number Diff line Loading @@ -35,6 +35,9 @@ package com.android.bluetooth.hfpclient; import static android.Manifest.permission.BLUETOOTH_CONNECT; import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; import static android.content.pm.PackageManager.FEATURE_WATCH; import static java.util.Objects.requireNonNull; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; Loading Loading @@ -389,7 +392,15 @@ public class HeadsetClientStateMachine extends StateMachine { logD("sendCallChangedIntent " + c); Intent intent = new Intent(BluetoothHeadsetClient.ACTION_CALL_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); if (mService.getPackageManager().hasSystemFeature(FEATURE_WATCH)) { logD("Send legacy call"); intent.putExtra( BluetoothHeadsetClient.EXTRA_CALL, HeadsetClientService.toLegacyCall(c)); } else { intent.putExtra(BluetoothHeadsetClient.EXTRA_CALL, c); } Utils.sendBroadcast(mService, intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions()); HfpClientConnectionService.onCallChanged(c.getDevice(), c); Loading Loading @@ -872,7 +883,7 @@ public class HeadsetClientStateMachine extends StateMachine { HeadsetClientStateMachine(HeadsetClientService context, HeadsetService headsetService, Looper looper, NativeInterface nativeInterface) { super(TAG, looper); mService = context; mService = requireNonNull(context); mNativeInterface = nativeInterface; mAudioManager = mService.getAudioManager(); mHeadsetService = headsetService; Loading
android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java +63 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.bluetooth.hfpclient; import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTED; import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.AT_OK; Loading @@ -32,11 +34,13 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAssignedNumbers; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadsetClient; import android.bluetooth.BluetoothHeadsetClientCall; import android.bluetooth.BluetoothSinkAudioPolicy; import android.bluetooth.BluetoothStatusCodes; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Resources; import android.media.AudioManager; import android.os.Bundle; Loading Loading @@ -93,7 +97,7 @@ public class HeadsetClientStateMachineTest { @Mock private HeadsetClientService mHeadsetClientService; @Mock private AudioManager mAudioManager; @Mock private RemoteDevices mRemoteDevices; @Mock private PackageManager mPackageManager; @Mock private NativeInterface mNativeInterface; private static final int STANDARD_WAIT_MILLIS = 1000; Loading @@ -114,6 +118,8 @@ public class HeadsetClientStateMachineTest { when(mHeadsetClientService.getAudioManager()).thenReturn( mAudioManager); when(mHeadsetClientService.getResources()).thenReturn(mMockHfpResources); when(mHeadsetClientService.getPackageManager()).thenReturn(mPackageManager); when(mPackageManager.hasSystemFeature(FEATURE_WATCH)).thenReturn(false); when(mMockHfpResources.getBoolean(R.bool.hfp_clcc_poll_during_call)).thenReturn(true); when(mMockHfpResources.getInteger(R.integer.hfp_clcc_poll_interval_during_call)) .thenReturn(2000); Loading Loading @@ -462,7 +468,63 @@ public class HeadsetClientStateMachineTest { intentArgument.getValue().getIntExtra(BluetoothHeadsetClient.EXTRA_IN_BAND_RING, -1)); Assert.assertEquals(false, mHeadsetClientStateMachine.getInBandRing()); } /** Test that wearables use {@code BluetoothHeadsetClientCall} in intent. */ @Test public void testWearablesUseBluetoothHeadsetClientCallInIntent() { // Specify the watch form factor when package manager is asked when(mPackageManager.hasSystemFeature(FEATURE_WATCH)).thenReturn(true); // Skip over the Android AT commands to test this code path doReturn(false).when(mNativeInterface).sendAndroidAt(anyObject(), anyString()); // Return true for connection policy to allow connections when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))) .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); // Send an incoming connection event StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); event.device = mTestDevice; event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED; mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, event); // Send a message to trigger service level connection using the required ECS feature event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); event.device = mTestDevice; event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED; event.valueInt2 = HeadsetClientHalConstants.PEER_FEAT_ECS; mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, event); // Dial a phone call, which will fail as @{code dial} method is not specified in @{code // mNativeInterface} mock and trigger a call state changed broadcast mHeadsetClientStateMachine.sendMessage( HeadsetClientStateMachine.DIAL_NUMBER, new HfpClientCall( mTestDevice, 0, HfpClientCall.CALL_STATE_WAITING, "1", false, false, false)); // Wait for processing TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); // Verify the broadcast ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class); verify(mHeadsetClientService, times(1)) .sendBroadcast(intentArgument.capture(), anyString(), any(Bundle.class)); // Verify that the parcelable extra has a legacy {@code BluetoothHeadsetClientCall} type for // wearables. Assert.assertThat( intentArgument.getValue().getParcelableExtra(BluetoothHeadsetClient.EXTRA_CALL), IsInstanceOf.instanceOf(BluetoothHeadsetClientCall.class)); // To satisfy the @After verification verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(true)); } /* Utility function to simulate HfpClient is connected. */ Loading