1 /* 2 * Copyright 2021 HIMSA II K/S - www.himsa.com. 3 * Represented by EHIMA - www.ehima.com 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.bluetooth.le_audio; 19 20 import static org.mockito.Mockito.*; 21 22 import android.annotation.Nullable; 23 import android.bluetooth.*; 24 import android.content.BroadcastReceiver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.media.AudioManager; 29 import android.os.Looper; 30 31 import android.os.ParcelUuid; 32 import androidx.test.InstrumentationRegistry; 33 import androidx.test.filters.MediumTest; 34 import androidx.test.rule.ServiceTestRule; 35 import androidx.test.runner.AndroidJUnit4; 36 37 import com.android.bluetooth.TestUtils; 38 import com.android.bluetooth.btservice.AdapterService; 39 import com.android.bluetooth.btservice.storage.DatabaseManager; 40 41 import org.junit.After; 42 import org.junit.Assert; 43 import org.junit.Before; 44 import org.junit.Rule; 45 import org.junit.Test; 46 import org.junit.runner.RunWith; 47 import org.mockito.Mock; 48 import org.mockito.MockitoAnnotations; 49 import org.mockito.Spy; 50 51 import java.util.concurrent.LinkedBlockingQueue; 52 import java.util.concurrent.TimeoutException; 53 import java.util.List; 54 55 @MediumTest 56 @RunWith(AndroidJUnit4.class) 57 public class LeAudioBroadcastServiceTest { 58 private static final int TIMEOUT_MS = 1000; 59 @Rule 60 public final ServiceTestRule mServiceRule = new ServiceTestRule(); 61 private BluetoothAdapter mAdapter; 62 private BluetoothDevice mDevice; 63 private Context mTargetContext; 64 private LeAudioService mService; 65 private LeAudioIntentReceiver mLeAudioIntentReceiver; 66 private LinkedBlockingQueue<Intent> mIntentQueue; 67 @Mock 68 private AdapterService mAdapterService; 69 @Mock 70 private DatabaseManager mDatabaseManager; 71 @Mock 72 private AudioManager mAudioManager; 73 @Mock 74 private LeAudioBroadcasterNativeInterface mNativeInterface; 75 @Mock private LeAudioTmapGattServer mTmapGattServer; 76 @Spy private LeAudioObjectsFactory mObjectsFactory = LeAudioObjectsFactory.getInstance(); 77 78 private static final String TEST_MAC_ADDRESS = "00:11:22:33:44:55"; 79 private static final int TEST_BROADCAST_ID = 42; 80 private static final int TEST_ADVERTISER_SID = 1234; 81 private static final int TEST_PA_SYNC_INTERVAL = 100; 82 private static final int TEST_PRESENTATION_DELAY_MS = 345; 83 84 private static final int TEST_CODEC_ID = 42; 85 private static final int TEST_CHANNEL_INDEX = 56; 86 87 // For BluetoothLeAudioCodecConfigMetadata 88 private static final long TEST_AUDIO_LOCATION_FRONT_LEFT = 0x01; 89 private static final long TEST_AUDIO_LOCATION_FRONT_RIGHT = 0x02; 90 91 // For BluetoothLeAudioContentMetadata 92 private static final String TEST_PROGRAM_INFO = "Test"; 93 // German language code in ISO 639-3 94 private static final String TEST_LANGUAGE = "deu"; 95 private static final String TEST_BROADCAST_NAME = "Name Test"; 96 97 private boolean mOnBroadcastStartedCalled = false; 98 private boolean mOnBroadcastStartFailedCalled = false; 99 private boolean mOnBroadcastStoppedCalled = false; 100 private boolean mOnBroadcastStopFailedCalled = false; 101 private boolean mOnPlaybackStartedCalled = false; 102 private boolean mOnPlaybackStoppedCalled = false; 103 private boolean mOnBroadcastUpdatedCalled = false; 104 private boolean mOnBroadcastUpdateFailedCalled = false; 105 private boolean mOnBroadcastMetadataChangedCalled = false; 106 107 private final IBluetoothLeBroadcastCallback mCallbacks = 108 new IBluetoothLeBroadcastCallback.Stub() { 109 @Override 110 public void onBroadcastStarted(int reason, int broadcastId) { 111 mOnBroadcastStartedCalled = true; 112 } 113 114 @Override 115 public void onBroadcastStartFailed(int reason) { 116 mOnBroadcastStartFailedCalled = true; 117 } 118 119 @Override 120 public void onBroadcastStopped(int reason, int broadcastId) { 121 mOnBroadcastStoppedCalled = true; 122 } 123 124 @Override 125 public void onBroadcastStopFailed(int reason) { 126 mOnBroadcastStopFailedCalled = true; 127 } 128 129 @Override 130 public void onPlaybackStarted(int reason, int broadcastId) { 131 mOnPlaybackStartedCalled = true; 132 } 133 134 @Override 135 public void onPlaybackStopped(int reason, int broadcastId) { 136 mOnPlaybackStoppedCalled = true; 137 } 138 139 @Override 140 public void onBroadcastUpdated(int reason, int broadcastId) { 141 mOnBroadcastUpdatedCalled = true; 142 } 143 144 @Override 145 public void onBroadcastUpdateFailed(int reason, int broadcastId) { 146 mOnBroadcastUpdateFailedCalled = true; 147 } 148 149 @Override 150 public void onBroadcastMetadataChanged(int broadcastId, 151 BluetoothLeBroadcastMetadata metadata) { 152 mOnBroadcastMetadataChangedCalled = true; 153 } 154 }; 155 156 @Before setUp()157 public void setUp() throws Exception { 158 mTargetContext = InstrumentationRegistry.getTargetContext(); 159 160 // Set up mocks and test assets 161 MockitoAnnotations.initMocks(this); 162 163 // Use spied objects factory 164 doNothing().when(mTmapGattServer).start(anyInt()); 165 doNothing().when(mTmapGattServer).stop(); 166 LeAudioObjectsFactory.setInstanceForTesting(mObjectsFactory); 167 doReturn(mTmapGattServer).when(mObjectsFactory).getTmapGattServer(any()); 168 169 if (Looper.myLooper() == null) { 170 Looper.prepare(); 171 } 172 173 TestUtils.setAdapterService(mAdapterService); 174 doReturn(mDatabaseManager).when(mAdapterService).getDatabase(); 175 doReturn(true, false).when(mAdapterService).isStartedProfile(anyString()); 176 doReturn(true).when(mAdapterService).isLeAudioBroadcastSourceSupported(); 177 doReturn((long)(1 << BluetoothProfile.LE_AUDIO_BROADCAST) | (1 << BluetoothProfile.LE_AUDIO)) 178 .when(mAdapterService).getSupportedProfilesBitMask(); 179 180 mAdapter = BluetoothAdapter.getDefaultAdapter(); 181 182 startService(); 183 mService.mAudioManager = mAudioManager; 184 mService.mLeAudioBroadcasterNativeInterface = mNativeInterface; 185 186 // Set up the State Changed receiver 187 IntentFilter filter = new IntentFilter(); 188 filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 189 190 mLeAudioIntentReceiver = new LeAudioIntentReceiver(); 191 mTargetContext.registerReceiver(mLeAudioIntentReceiver, filter); 192 193 mDevice = TestUtils.getTestDevice(mAdapter, 0); 194 when(mNativeInterface.getDevice(any(byte[].class))).thenReturn(mDevice); 195 196 mIntentQueue = new LinkedBlockingQueue<Intent>(); 197 } 198 199 @After tearDown()200 public void tearDown() throws Exception { 201 if (mService == null) { 202 return; 203 } 204 205 stopService(); 206 mTargetContext.unregisterReceiver(mLeAudioIntentReceiver); 207 TestUtils.clearAdapterService(mAdapterService); 208 reset(mAudioManager); 209 } 210 startService()211 private void startService() throws TimeoutException { 212 TestUtils.startService(mServiceRule, LeAudioService.class); 213 mService = LeAudioService.getLeAudioService(); 214 Assert.assertNotNull(mService); 215 } 216 stopService()217 private void stopService() throws TimeoutException { 218 TestUtils.stopService(mServiceRule, LeAudioService.class); 219 mService = LeAudioService.getLeAudioService(); 220 Assert.assertNull(mService); 221 } 222 223 /** 224 * Test getting LeAudio Service 225 */ 226 @Test testGetLeAudioService()227 public void testGetLeAudioService() { 228 Assert.assertEquals(mService, LeAudioService.getLeAudioService()); 229 } 230 231 @Test testStopLeAudioService()232 public void testStopLeAudioService() { 233 Assert.assertEquals(mService, LeAudioService.getLeAudioService()); 234 235 InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { 236 public void run() { 237 Assert.assertTrue(mService.stop()); 238 } 239 }); 240 InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { 241 public void run() { 242 Assert.assertTrue(mService.start()); 243 } 244 }); 245 } 246 verifyBroadcastStarted(int broadcastId, BluetoothLeBroadcastSettings settings)247 void verifyBroadcastStarted(int broadcastId, BluetoothLeBroadcastSettings settings) { 248 mService.createBroadcast(settings); 249 250 List<BluetoothLeBroadcastSubgroupSettings> settingsList = 251 settings.getSubgroupSettings(); 252 253 int[] expectedQualityArray = 254 settingsList.stream() 255 .mapToInt(setting -> setting.getPreferredQuality()).toArray(); 256 byte[][] expectedDataArray = 257 settingsList.stream() 258 .map(setting -> setting.getContentMetadata().getRawMetadata()) 259 .toArray(byte[][]::new); 260 261 verify(mNativeInterface, times(1)).createBroadcast(eq(true), eq(TEST_BROADCAST_NAME), 262 eq(settings.getBroadcastCode()), 263 eq(settings.getPublicBroadcastMetadata().getRawMetadata()), 264 eq(expectedQualityArray), 265 eq(expectedDataArray)); 266 267 // Check if broadcast is started automatically when created 268 LeAudioStackEvent create_event = 269 new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_CREATED); 270 create_event.valueInt1 = broadcastId; 271 create_event.valueBool1 = true; 272 mService.messageFromNative(create_event); 273 274 // Verify if broadcast is auto-started on start 275 verify(mNativeInterface, times(1)).startBroadcast(eq(broadcastId)); 276 277 // Notify initial paused state 278 LeAudioStackEvent state_event = 279 new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_STATE); 280 state_event.valueInt1 = broadcastId; 281 state_event.valueInt2 = LeAudioStackEvent.BROADCAST_STATE_PAUSED; 282 mService.messageFromNative(state_event); 283 284 // Switch to active streaming 285 state_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_STATE); 286 state_event.valueInt1 = broadcastId; 287 state_event.valueInt2 = LeAudioStackEvent.BROADCAST_STATE_STREAMING; 288 mService.messageFromNative(state_event); 289 290 // Check if metadata is requested when the broadcast starts to stream 291 verify(mNativeInterface, times(1)).getBroadcastMetadata(eq(broadcastId)); 292 Assert.assertFalse(mOnBroadcastStartFailedCalled); 293 Assert.assertTrue(mOnBroadcastStartedCalled); 294 } 295 verifyBroadcastStopped(int broadcastId)296 void verifyBroadcastStopped(int broadcastId) { 297 mService.stopBroadcast(broadcastId); 298 verify(mNativeInterface, times(1)).stopBroadcast(eq(broadcastId)); 299 300 LeAudioStackEvent state_event = 301 new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_STATE); 302 state_event.valueInt1 = broadcastId; 303 state_event.valueInt2 = LeAudioStackEvent.BROADCAST_STATE_STOPPED; 304 mService.messageFromNative(state_event); 305 306 // Verify if broadcast is auto-destroyed on stop 307 verify(mNativeInterface, times(1)).destroyBroadcast(eq(broadcastId)); 308 309 state_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_DESTROYED); 310 state_event.valueInt1 = broadcastId; 311 mService.messageFromNative(state_event); 312 313 Assert.assertTrue(mOnBroadcastStoppedCalled); 314 Assert.assertFalse(mOnBroadcastStopFailedCalled); 315 } 316 317 @Test testCreateBroadcastNative()318 public void testCreateBroadcastNative() { 319 int broadcastId = 243; 320 byte[] code = {0x00, 0x01, 0x00, 0x02}; 321 322 mService.mBroadcastCallbacks.register(mCallbacks); 323 324 BluetoothLeAudioContentMetadata.Builder meta_builder = 325 new BluetoothLeAudioContentMetadata.Builder(); 326 meta_builder.setLanguage("deu"); 327 meta_builder.setProgramInfo("Subgroup broadcast info"); 328 BluetoothLeAudioContentMetadata meta = meta_builder.build(); 329 330 verifyBroadcastStarted(broadcastId, buildBroadcastSettingsFromMetadata(meta, code, 1)); 331 } 332 333 @Test testCreateBroadcastNativeMultiGroups()334 public void testCreateBroadcastNativeMultiGroups() { 335 int broadcastId = 243; 336 byte[] code = {0x00, 0x01, 0x00, 0x02}; 337 338 mService.mBroadcastCallbacks.register(mCallbacks); 339 340 BluetoothLeAudioContentMetadata.Builder meta_builder = 341 new BluetoothLeAudioContentMetadata.Builder(); 342 meta_builder.setLanguage("deu"); 343 meta_builder.setProgramInfo("Subgroup broadcast info"); 344 BluetoothLeAudioContentMetadata meta = meta_builder.build(); 345 346 verifyBroadcastStarted(broadcastId, buildBroadcastSettingsFromMetadata(meta, code, 3)); 347 } 348 349 @Test testCreateBroadcastNativeFailed()350 public void testCreateBroadcastNativeFailed() { 351 int broadcastId = 243; 352 byte[] code = {0x00, 0x01, 0x00, 0x02}; 353 354 mService.mBroadcastCallbacks.register(mCallbacks); 355 356 BluetoothLeAudioContentMetadata.Builder meta_builder = 357 new BluetoothLeAudioContentMetadata.Builder(); 358 meta_builder.setLanguage("deu"); 359 meta_builder.setProgramInfo("Public broadcast info"); 360 BluetoothLeAudioContentMetadata meta = meta_builder.build(); 361 BluetoothLeBroadcastSettings settings = buildBroadcastSettingsFromMetadata(meta, code, 1); 362 mService.createBroadcast(settings); 363 364 // Test data with only one subgroup 365 int[] expectedQualityArray = 366 {settings.getSubgroupSettings().get(0).getPreferredQuality()}; 367 byte[][] expectedDataArray = 368 {settings.getSubgroupSettings().get(0).getContentMetadata().getRawMetadata()}; 369 370 verify(mNativeInterface, times(1)).createBroadcast(eq(true), eq(TEST_BROADCAST_NAME), 371 eq(code), eq(settings.getPublicBroadcastMetadata().getRawMetadata()), 372 eq(expectedQualityArray), 373 eq(expectedDataArray)); 374 375 LeAudioStackEvent create_event = 376 new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_CREATED); 377 create_event.valueInt1 = broadcastId; 378 create_event.valueBool1 = false; 379 mService.messageFromNative(create_event); 380 381 Assert.assertFalse(mOnBroadcastStartedCalled); 382 Assert.assertTrue(mOnBroadcastStartFailedCalled); 383 } 384 385 @Test testStartStopBroadcastNative()386 public void testStartStopBroadcastNative() { 387 int broadcastId = 243; 388 byte[] code = {0x00, 0x01, 0x00, 0x02}; 389 390 mService.mBroadcastCallbacks.register(mCallbacks); 391 392 BluetoothLeAudioContentMetadata.Builder meta_builder = 393 new BluetoothLeAudioContentMetadata.Builder(); 394 meta_builder.setLanguage("deu"); 395 meta_builder.setProgramInfo("Subgroup broadcast info"); 396 BluetoothLeAudioContentMetadata meta = meta_builder.build(); 397 398 verifyBroadcastStarted(broadcastId, buildBroadcastSettingsFromMetadata(meta, code, 1)); 399 verifyBroadcastStopped(broadcastId); 400 } 401 402 @Test testBroadcastInvalidBroadcastIdRequest()403 public void testBroadcastInvalidBroadcastIdRequest() { 404 int broadcastId = 243; 405 406 mService.mBroadcastCallbacks.register(mCallbacks); 407 408 // Stop non-existing broadcast 409 mService.stopBroadcast(broadcastId); 410 Assert.assertFalse(mOnBroadcastStoppedCalled); 411 Assert.assertTrue(mOnBroadcastStopFailedCalled); 412 413 // Update metadata for non-existing broadcast 414 BluetoothLeAudioContentMetadata.Builder meta_builder = 415 new BluetoothLeAudioContentMetadata.Builder(); 416 meta_builder.setLanguage("eng"); 417 meta_builder.setProgramInfo("Public broadcast info"); 418 mService.updateBroadcast(broadcastId, 419 buildBroadcastSettingsFromMetadata(meta_builder.build(), null, 1)); 420 Assert.assertFalse(mOnBroadcastUpdatedCalled); 421 Assert.assertTrue(mOnBroadcastUpdateFailedCalled); 422 } 423 createBroadcastSubgroup()424 private BluetoothLeBroadcastSubgroup createBroadcastSubgroup() { 425 BluetoothLeAudioCodecConfigMetadata codecMetadata = 426 new BluetoothLeAudioCodecConfigMetadata.Builder() 427 .setAudioLocation(TEST_AUDIO_LOCATION_FRONT_LEFT).build(); 428 BluetoothLeAudioContentMetadata contentMetadata = 429 new BluetoothLeAudioContentMetadata.Builder() 430 .setProgramInfo(TEST_PROGRAM_INFO).setLanguage(TEST_LANGUAGE).build(); 431 BluetoothLeBroadcastSubgroup.Builder builder = new BluetoothLeBroadcastSubgroup.Builder() 432 .setCodecId(TEST_CODEC_ID) 433 .setCodecSpecificConfig(codecMetadata) 434 .setContentMetadata(contentMetadata); 435 436 BluetoothLeAudioCodecConfigMetadata channelCodecMetadata = 437 new BluetoothLeAudioCodecConfigMetadata.Builder() 438 .setAudioLocation(TEST_AUDIO_LOCATION_FRONT_RIGHT).build(); 439 440 // builder expect at least one channel 441 BluetoothLeBroadcastChannel channel = 442 new BluetoothLeBroadcastChannel.Builder() 443 .setSelected(true) 444 .setChannelIndex(TEST_CHANNEL_INDEX) 445 .setCodecMetadata(channelCodecMetadata) 446 .build(); 447 builder.addChannel(channel); 448 return builder.build(); 449 } 450 createBroadcastMetadata()451 private BluetoothLeBroadcastMetadata createBroadcastMetadata() { 452 BluetoothDevice testDevice = 453 mAdapter.getRemoteLeDevice(TEST_MAC_ADDRESS, BluetoothDevice.ADDRESS_TYPE_RANDOM); 454 455 BluetoothLeBroadcastMetadata.Builder builder = new BluetoothLeBroadcastMetadata.Builder() 456 .setEncrypted(false) 457 .setSourceDevice(testDevice, BluetoothDevice.ADDRESS_TYPE_RANDOM) 458 .setSourceAdvertisingSid(TEST_ADVERTISER_SID) 459 .setBroadcastId(TEST_BROADCAST_ID) 460 .setBroadcastCode(null) 461 .setPaSyncInterval(TEST_PA_SYNC_INTERVAL) 462 .setPresentationDelayMicros(TEST_PRESENTATION_DELAY_MS); 463 // builder expect at least one subgroup 464 builder.addSubgroup(createBroadcastSubgroup()); 465 return builder.build(); 466 } 467 468 @Test testGetAllBroadcastMetadata()469 public void testGetAllBroadcastMetadata() { 470 int broadcastId = 243; 471 byte[] code = {0x00, 0x01, 0x00}; 472 473 BluetoothLeAudioContentMetadata.Builder meta_builder = 474 new BluetoothLeAudioContentMetadata.Builder(); 475 meta_builder.setLanguage("ENG"); 476 meta_builder.setProgramInfo("Public broadcast info"); 477 BluetoothLeAudioContentMetadata meta = meta_builder.build(); 478 mService.createBroadcast(buildBroadcastSettingsFromMetadata(meta, code, 1)); 479 480 // Inject metadata stack event and verify if getter API works as expected 481 LeAudioStackEvent state_event = 482 new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_METADATA_CHANGED); 483 state_event.valueInt1 = broadcastId; 484 state_event.broadcastMetadata = createBroadcastMetadata(); 485 mService.messageFromNative(state_event); 486 487 List<BluetoothLeBroadcastMetadata> meta_list = mService.getAllBroadcastMetadata(); 488 Assert.assertNotNull(meta_list); 489 Assert.assertNotEquals(meta_list.size(), 0); 490 Assert.assertEquals(meta_list.get(0), state_event.broadcastMetadata); 491 } 492 493 private class LeAudioIntentReceiver extends BroadcastReceiver { 494 @Override onReceive(Context context, Intent intent)495 public void onReceive(Context context, Intent intent) { 496 try { 497 mIntentQueue.put(intent); 498 } catch (InterruptedException e) { 499 Assert.fail("Cannot add Intent to the queue: " + e.getMessage()); 500 } 501 } 502 } 503 buildBroadcastSettingsFromMetadata( BluetoothLeAudioContentMetadata contentMetadata, @Nullable byte[] broadcastCode, int numOfGroups)504 private BluetoothLeBroadcastSettings buildBroadcastSettingsFromMetadata( 505 BluetoothLeAudioContentMetadata contentMetadata, 506 @Nullable byte[] broadcastCode, 507 int numOfGroups) { 508 BluetoothLeAudioContentMetadata.Builder publicMetaBuilder = 509 new BluetoothLeAudioContentMetadata.Builder(); 510 publicMetaBuilder.setProgramInfo("Public broadcast info"); 511 512 BluetoothLeBroadcastSubgroupSettings.Builder subgroupBuilder = 513 new BluetoothLeBroadcastSubgroupSettings.Builder() 514 .setContentMetadata(contentMetadata); 515 516 BluetoothLeBroadcastSettings.Builder builder = new BluetoothLeBroadcastSettings.Builder() 517 .setPublicBroadcast(true) 518 .setBroadcastName(TEST_BROADCAST_NAME) 519 .setBroadcastCode(broadcastCode) 520 .setPublicBroadcastMetadata(publicMetaBuilder.build()); 521 // builder expect at least one subgroup setting 522 for (int i = 0; i < numOfGroups; i++) { 523 // add subgroup settings with the same content 524 builder.addSubgroupSettings(subgroupBuilder.build()); 525 } 526 return builder.build(); 527 } 528 } 529