/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.bluetooth import android.bluetooth.DckTestRule.LeScanResult import android.bluetooth.le.ScanFilter import android.bluetooth.le.ScanSettings import android.content.Context import android.os.ParcelUuid import androidx.test.core.app.ApplicationProvider import com.android.compatibility.common.util.AdoptShellPermissionsRule import com.google.common.truth.Truth.assertThat import com.google.testing.junit.testparameterinjector.TestParameter import com.google.testing.junit.testparameterinjector.TestParameterInjector import java.util.UUID import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeout import org.junit.Assume.assumeTrue import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith /** DCK LE Scan Tests */ @RunWith(TestParameterInjector::class) class DckScanTest( private @TestParameter val isBluetoothToggled: Boolean, private @TestParameter val isRemoteAdvertisingWithUuid: Boolean, private @TestParameter val isGattConnected: Boolean ) { // TODO(315852141): Include variations for LE only vs. Dual mode Bumble when supported // TODO(315852141): Include variations for two advertisements at the same time // TODO(303502437): Include variations for other callback types when supported in rootcanal private val context: Context = ApplicationProvider.getApplicationContext() // Gives shell permissions during the test. @Rule(order = 0) @JvmField val shellPermissionRule = AdoptShellPermissionsRule() // Setup a Bumble Pandora device for the duration of the test. // Acting as a Pandora client, it can be interacted with through the Pandora APIs. @Rule(order = 1) @JvmField val bumble = PandoraDevice() // Test rule for common DCK test setup and teardown procedures, along with utility APIs. @get:Rule(order = 2) public val dck = DckTestRule( context, bumble, isBluetoothToggled = isBluetoothToggled, isRemoteAdvertisingWithUuid = isRemoteAdvertisingWithUuid, isGattConnected = isGattConnected ) @Test fun scanForIrkAndIdentityAddress_remoteFound() { // TODO(316001793): Retrieve identity address from Bumble val scanFilter = ScanFilter.Builder() .setDeviceAddress( TEST_ADDRESS_RANDOM_STATIC, BluetoothDevice.ADDRESS_TYPE_RANDOM, Utils.BUMBLE_IRK ) .build() val scanSettings = ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY) .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) .setMatchMode(ScanSettings.MATCH_MODE_STICKY) .build() val result: LeScanResult = runBlocking { withTimeout(TIMEOUT_MS) { dck.scanWithPendingIntent(scanFilter, scanSettings).first() } } assertThat(result).isInstanceOf(LeScanResult.Success::class.java) assertThat((result as LeScanResult.Success).callbackType) .isEqualTo(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) assertThat((result as LeScanResult.Success).scanResult.device.address) .isEqualTo(TEST_ADDRESS_RANDOM_STATIC) } @Test fun scanForUuid_remoteFound() { // Assume isRemoteAdvertisingWithUuid is true to skip tests in which // device is not advertising with UUID assumeTrue(isRemoteAdvertisingWithUuid) val scanFilter = ScanFilter.Builder().setServiceUuid(ParcelUuid(CCC_DK_UUID)).build() val scanSettings = ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE) .build() val result: LeScanResult = runBlocking { withTimeout(TIMEOUT_MS) { dck.scanWithCallback(scanFilter, scanSettings).first() } } assertThat(result).isInstanceOf(LeScanResult.Success::class.java) assertThat((result as LeScanResult.Success).callbackType) .isEqualTo(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) assertThat((result as LeScanResult.Success).scanResult.device.address) .isEqualTo(Utils.BUMBLE_RANDOM_ADDRESS) } companion object { private const val TIMEOUT_MS = 3000L private const val TEST_ADDRESS_RANDOM_STATIC = "F0:43:A8:23:10:11" private val CCC_DK_UUID = UUID.fromString("0000FFF5-0000-1000-8000-00805f9b34fb") } }